1<?php 2 3namespace MediaWiki\Tests\Storage; 4 5use Content; 6use MediaWiki\Revision\MutableRevisionSlots; 7use MediaWiki\Revision\RevisionAccessException; 8use MediaWiki\Revision\RevisionSlots; 9use MediaWiki\Revision\SlotRecord; 10use MediaWiki\Storage\RevisionSlotsUpdate; 11use MediaWikiIntegrationTestCase; 12use WikitextContent; 13 14/** 15 * @covers \MediaWiki\Storage\RevisionSlotsUpdate 16 */ 17class RevisionSlotsUpdateTest extends MediaWikiIntegrationTestCase { 18 19 public function provideNewFromRevisionSlots() { 20 $slotA = SlotRecord::newUnsaved( 'A', new WikitextContent( 'A' ) ); 21 $slotB = SlotRecord::newUnsaved( 'B', new WikitextContent( 'B' ) ); 22 $slotC = SlotRecord::newUnsaved( 'C', new WikitextContent( 'C' ) ); 23 24 $slotB2 = SlotRecord::newUnsaved( 'B', new WikitextContent( 'B2' ) ); 25 26 $parentSlots = new RevisionSlots( [ 27 'A' => $slotA, 28 'B' => $slotB, 29 'C' => $slotC, 30 ] ); 31 32 $newSlots = new RevisionSlots( [ 33 'A' => $slotA, 34 'B' => $slotB2, 35 ] ); 36 37 yield [ $newSlots, null, [ 'A', 'B' ], [] ]; 38 yield [ $newSlots, $parentSlots, [ 'B' ], [ 'C' ] ]; 39 } 40 41 /** 42 * @dataProvider provideNewFromRevisionSlots 43 */ 44 public function testNewFromRevisionSlots( 45 RevisionSlots $newSlots, 46 ?RevisionSlots $parentSlots, 47 array $modified, 48 array $removed 49 ) { 50 $update = RevisionSlotsUpdate::newFromRevisionSlots( $newSlots, $parentSlots ); 51 52 $this->assertEquals( $modified, $update->getModifiedRoles() ); 53 $this->assertEquals( $removed, $update->getRemovedRoles() ); 54 55 foreach ( $modified as $role ) { 56 $this->assertSame( $newSlots->getSlot( $role ), $update->getModifiedSlot( $role ) ); 57 } 58 } 59 60 public function provideNewFromContent() { 61 $slotA = SlotRecord::newUnsaved( 'A', new WikitextContent( 'A' ) ); 62 $slotB = SlotRecord::newUnsaved( 'B', new WikitextContent( 'B' ) ); 63 $slotC = SlotRecord::newUnsaved( 'C', new WikitextContent( 'C' ) ); 64 65 $parentSlots = new RevisionSlots( [ 66 'A' => $slotA, 67 'B' => $slotB, 68 'C' => $slotC, 69 ] ); 70 71 $newContent = [ 72 'A' => new WikitextContent( 'A' ), 73 'B' => new WikitextContent( 'B2' ), 74 ]; 75 76 yield [ $newContent, null, [ 'A', 'B' ] ]; 77 yield [ $newContent, $parentSlots, [ 'B' ] ]; 78 } 79 80 /** 81 * @dataProvider provideNewFromContent 82 */ 83 public function testNewFromContent( 84 array $newContent, 85 RevisionSlots $parentSlots = null, 86 array $modified = [] 87 ) { 88 $update = RevisionSlotsUpdate::newFromContent( $newContent, $parentSlots ); 89 90 $this->assertEquals( $modified, $update->getModifiedRoles() ); 91 $this->assertSame( [], $update->getRemovedRoles() ); 92 } 93 94 public function testConstructor() { 95 $update = new RevisionSlotsUpdate(); 96 97 $this->assertSame( [], $update->getModifiedRoles() ); 98 $this->assertSame( [], $update->getRemovedRoles() ); 99 100 $slotA = SlotRecord::newUnsaved( 'A', new WikitextContent( 'A' ) ); 101 $update = new RevisionSlotsUpdate( [ 'A' => $slotA ] ); 102 103 $this->assertEquals( [ 'A' ], $update->getModifiedRoles() ); 104 $this->assertSame( [], $update->getRemovedRoles() ); 105 106 $update = new RevisionSlotsUpdate( [ 'A' => $slotA ], [ 'X' ] ); 107 108 $this->assertEquals( [ 'A' ], $update->getModifiedRoles() ); 109 $this->assertEquals( [ 'X' ], $update->getRemovedRoles() ); 110 } 111 112 public function testModifySlot() { 113 $slots = new RevisionSlotsUpdate(); 114 115 $this->assertSame( [], $slots->getModifiedRoles() ); 116 $this->assertSame( [], $slots->getRemovedRoles() ); 117 118 $slotA = SlotRecord::newUnsaved( 'some', new WikitextContent( 'A' ) ); 119 $slots->modifySlot( $slotA ); 120 $this->assertTrue( $slots->isModifiedSlot( 'some' ) ); 121 $this->assertFalse( $slots->isRemovedSlot( 'some' ) ); 122 $this->assertSame( $slotA, $slots->getModifiedSlot( 'some' ) ); 123 $this->assertSame( [ 'some' ], $slots->getModifiedRoles() ); 124 $this->assertSame( [], $slots->getRemovedRoles() ); 125 126 $slotB = SlotRecord::newUnsaved( 'other', new WikitextContent( 'B' ) ); 127 $slots->modifySlot( $slotB ); 128 $this->assertTrue( $slots->isModifiedSlot( 'other' ) ); 129 $this->assertFalse( $slots->isRemovedSlot( 'other' ) ); 130 $this->assertSame( $slotB, $slots->getModifiedSlot( 'other' ) ); 131 $this->assertSame( [ 'some', 'other' ], $slots->getModifiedRoles() ); 132 $this->assertSame( [], $slots->getRemovedRoles() ); 133 134 // modify slot A again 135 $slots->modifySlot( $slotA ); 136 $this->assertArrayEquals( [ 'some', 'other' ], $slots->getModifiedRoles() ); 137 138 // remove modified slot 139 $slots->removeSlot( 'some' ); 140 $this->assertSame( [ 'other' ], $slots->getModifiedRoles() ); 141 $this->assertSame( [ 'some' ], $slots->getRemovedRoles() ); 142 143 // modify removed slot 144 $slots->modifySlot( $slotA ); 145 $this->assertArrayEquals( [ 'some', 'other' ], $slots->getModifiedRoles() ); 146 $this->assertSame( [], $slots->getRemovedRoles() ); 147 } 148 149 public function testRemoveSlot() { 150 $slots = new RevisionSlotsUpdate(); 151 152 $slotA = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) ); 153 $slots->modifySlot( $slotA ); 154 155 $this->assertSame( [ 'main' ], $slots->getModifiedRoles() ); 156 157 $slots->removeSlot( SlotRecord::MAIN ); 158 $slots->removeSlot( 'other' ); 159 $this->assertSame( [], $slots->getModifiedRoles() ); 160 $this->assertSame( [ 'main', 'other' ], $slots->getRemovedRoles() ); 161 $this->assertTrue( $slots->isRemovedSlot( SlotRecord::MAIN ) ); 162 $this->assertTrue( $slots->isRemovedSlot( 'other' ) ); 163 $this->assertFalse( $slots->isModifiedSlot( SlotRecord::MAIN ) ); 164 165 // removing the same slot again should not trigger an error 166 $slots->removeSlot( SlotRecord::MAIN ); 167 168 // getting a slot after removing it should fail 169 $this->expectException( RevisionAccessException::class ); 170 $slots->getModifiedSlot( SlotRecord::MAIN ); 171 } 172 173 public function testGetModifiedRoles() { 174 $slots = new RevisionSlotsUpdate( [], [ 'xyz' ] ); 175 176 $this->assertSame( [], $slots->getModifiedRoles() ); 177 178 $slots->modifyContent( SlotRecord::MAIN, new WikitextContent( 'A' ) ); 179 $slots->modifyContent( 'foo', new WikitextContent( 'Foo' ) ); 180 $this->assertSame( [ 'main', 'foo' ], $slots->getModifiedRoles() ); 181 182 $slots->removeSlot( SlotRecord::MAIN ); 183 $this->assertSame( [ 'foo' ], $slots->getModifiedRoles() ); 184 } 185 186 public function testGetRemovedRoles() { 187 $slotA = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) ); 188 $slots = new RevisionSlotsUpdate( [ $slotA ] ); 189 190 $this->assertSame( [], $slots->getRemovedRoles() ); 191 192 $slots->removeSlot( SlotRecord::MAIN, new WikitextContent( 'A' ) ); 193 $slots->removeSlot( 'foo', new WikitextContent( 'Foo' ) ); 194 195 $this->assertSame( [ 'main', 'foo' ], $slots->getRemovedRoles() ); 196 197 $slots->modifyContent( SlotRecord::MAIN, new WikitextContent( 'A' ) ); 198 $this->assertSame( [ 'foo' ], $slots->getRemovedRoles() ); 199 } 200 201 public function provideHasSameUpdates() { 202 $fooX = SlotRecord::newUnsaved( 'x', new WikitextContent( 'Foo' ) ); 203 $barZ = SlotRecord::newUnsaved( 'z', new WikitextContent( 'Bar' ) ); 204 205 $a = new RevisionSlotsUpdate(); 206 $a->modifySlot( $fooX ); 207 $a->modifySlot( $barZ ); 208 $a->removeSlot( 'Q' ); 209 210 $a2 = new RevisionSlotsUpdate(); 211 $a2->modifySlot( $fooX ); 212 $a2->modifySlot( $barZ ); 213 $a2->removeSlot( 'Q' ); 214 215 $b = new RevisionSlotsUpdate(); 216 $b->modifySlot( $barZ ); 217 $b->removeSlot( 'Q' ); 218 219 $c = new RevisionSlotsUpdate(); 220 $c->modifySlot( $fooX ); 221 $c->modifySlot( $barZ ); 222 223 yield 'same instance' => [ $a, $a, true ]; 224 yield 'same udpates' => [ $a, $a2, true ]; 225 226 yield 'different modified' => [ $a, $b, false ]; 227 yield 'different removed' => [ $a, $c, false ]; 228 } 229 230 /** 231 * @dataProvider provideHasSameUpdates 232 */ 233 public function testHasSameUpdates( RevisionSlotsUpdate $a, RevisionSlotsUpdate $b, $same ) { 234 $this->assertSame( $same, $a->hasSameUpdates( $b ) ); 235 $this->assertSame( $same, $b->hasSameUpdates( $a ) ); 236 } 237 238 /** 239 * @param string $role 240 * @param Content $content 241 * @return SlotRecord 242 */ 243 private function newSavedSlot( $role, Content $content ) { 244 return SlotRecord::newSaved( 7, 7, 'xyz', SlotRecord::newUnsaved( $role, $content ) ); 245 } 246 247 public function testApplyUpdate() { 248 /** @var SlotRecord[] $parentSlots */ 249 $parentSlots = [ 250 'X' => $this->newSavedSlot( 'X', new WikitextContent( 'X' ) ), 251 'Y' => $this->newSavedSlot( 'Y', new WikitextContent( 'Y' ) ), 252 'Z' => $this->newSavedSlot( 'Z', new WikitextContent( 'Z' ) ), 253 ]; 254 $slots = MutableRevisionSlots::newFromParentRevisionSlots( $parentSlots ); 255 $update = RevisionSlotsUpdate::newFromContent( [ 256 'A' => new WikitextContent( 'A' ), 257 'Y' => new WikitextContent( 'yyy' ), 258 ] ); 259 260 $update->removeSlot( 'Z' ); 261 262 $update->apply( $slots ); 263 $this->assertSame( [ 'X', 'Y', 'A' ], $slots->getSlotRoles() ); 264 $this->assertSame( $update->getModifiedSlot( 'A' ), $slots->getSlot( 'A' ) ); 265 $this->assertSame( $update->getModifiedSlot( 'Y' ), $slots->getSlot( 'Y' ) ); 266 } 267 268} 269