testGivenInvalidConstructorArguments_constructorThrowsException()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Wikibase\DataModel\Tests;
4
5
use InvalidArgumentException;
6
use Traversable;
7
use Wikibase\DataModel\Entity\PropertyId;
8
use Wikibase\DataModel\Reference;
9
use Wikibase\DataModel\ReferenceList;
10
use Wikibase\DataModel\Snak\PropertyNoValueSnak;
11
use Wikibase\DataModel\Snak\SnakList;
12
13
/**
14
 * @covers \Wikibase\DataModel\ReferenceList
15
 *
16
 * @group Wikibase
17
 * @group WikibaseDataModel
18
 * @group WikibaseReference
19
 *
20
 * @license GPL-2.0-or-later
21
 * @author Jeroen De Dauw < [email protected] >
22
 * @author Thiemo Kreuz
23
 */
24
class ReferenceListTest extends \PHPUnit\Framework\TestCase {
25
26
	public function instanceProvider() {
27
		return [
28
			[ new ReferenceList( [] ) ],
29
			[ new ReferenceList( [
30
				new Reference(),
31
				new Reference( [ new PropertyNoValueSnak( 2 ) ] ),
32
				new Reference( [ new PropertyNoValueSnak( 3 ) ] ),
33
			] ) ],
34
		];
35
	}
36
37
	public function testCanConstructWithReferenceListObject() {
38
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
39
		$original = new ReferenceList( [ $reference ] );
40
		$copy = new ReferenceList( $original );
41
42
		$this->assertSame( 1, $copy->count() );
43
		$this->assertNotNull( $copy->getReference( $reference->getHash() ) );
44
	}
45
46
	public function testConstructorIgnoresIdenticalObjects() {
47
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
48
		$list = new ReferenceList( [ $reference, $reference ] );
49
		$this->assertCount( 1, $list );
50
	}
51
52
	public function testConstructorDoesNotIgnoreCopies() {
53
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
54
		$list = new ReferenceList( [ $reference, clone $reference ] );
55
		$this->assertCount( 2, $list );
56
	}
57
58
	/**
59
	 * @dataProvider invalidConstructorArgumentsProvider
60
	 */
61
	public function testGivenInvalidConstructorArguments_constructorThrowsException( $input ) {
62
		$this->expectException( InvalidArgumentException::class );
63
		new ReferenceList( $input );
64
	}
65
66
	public function invalidConstructorArgumentsProvider() {
67
		$id1 = new PropertyId( 'P1' );
68
69
		return [
70
			[ null ],
71
			[ false ],
72
			[ 1 ],
73
			[ 0.1 ],
74
			[ 'string' ],
75
			[ $id1 ],
76
			[ new PropertyNoValueSnak( $id1 ) ],
77
			[ new Reference() ],
78
			[ new SnakList( [ new PropertyNoValueSnak( $id1 ) ] ) ],
79
			[ [ new PropertyNoValueSnak( $id1 ) ] ],
80
			[ [ new ReferenceList() ] ],
81
			[ [ new SnakList() ] ],
82
		];
83
	}
84
85
	public function testGetIterator_isTraversable() {
86
		$references = new ReferenceList();
87
		$references->addNewReference( new PropertyNoValueSnak( 1 ) );
88
		$iterator = $references->getIterator();
89
90
		$this->assertInstanceOf( Traversable::class, $iterator );
91
		$this->assertCount( 1, $iterator );
92
		foreach ( $references as $reference ) {
93
			$this->assertInstanceOf( Reference::class, $reference );
94
		}
95
	}
96
97
	/**
98
	 * @dataProvider instanceProvider
99
	 */
100
	public function testHasReferenceBeforeRemoveButNotAfter( ReferenceList $array ) {
101
		if ( $array->count() === 0 ) {
102
			$this->assertTrue( true );
103
			return;
104
		}
105
106
		/**
107
		 * @var Reference $hashable
108
		 */
109
		foreach ( iterator_to_array( $array ) as $hashable ) {
110
			$this->assertTrue( $array->hasReference( $hashable ) );
111
			$array->removeReference( $hashable );
112
			$this->assertFalse( $array->hasReference( $hashable ) );
113
		}
114
	}
115
116
	public function testGivenCloneOfReferenceInList_hasReferenceReturnsTrue() {
117
		$list = new ReferenceList();
118
119
		$reference = new Reference( [ new PropertyNoValueSnak( 42 ) ] );
120
		$sameReference = unserialize( serialize( $reference ) );
121
122
		$list->addReference( $reference );
123
124
		$this->assertTrue(
125
			$list->hasReference( $sameReference ),
126
			'hasReference should return true when a reference with the same value is present, even '
127
				. 'when its another instance'
128
		);
129
	}
130
131
	/**
132
	 * @dataProvider instanceProvider
133
	 */
134
	public function testRemoveReference( ReferenceList $array ) {
135
		$elementCount = count( $array );
136
137
		/**
138
		 * @var Reference $element
139
		 */
140
		foreach ( iterator_to_array( $array ) as $element ) {
141
			$this->assertTrue( $array->hasReference( $element ) );
142
143
			$array->removeReference( $element );
144
145
			$this->assertFalse( $array->hasReference( $element ) );
146
			$this->assertSame( --$elementCount, count( $array ) );
147
		}
148
		if ( $elementCount === 0 ) {
149
			$this->assertTrue( true );
150
		}
151
	}
152
153
	public function testRemoveReferenceRemovesIdenticalObjects() {
154
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
155
		$references = new ReferenceList( [ $reference, $reference ] );
156
157
		$references->removeReference( $reference );
158
159
		$this->assertTrue( $references->isEmpty() );
160
	}
161
162
	public function testRemoveReferenceDoesNotRemoveCopies() {
163
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
164
		$references = new ReferenceList( [ $reference, clone $reference ] );
165
166
		$references->removeReference( $reference );
167
168
		$this->assertFalse( $references->isEmpty() );
169
		$this->assertTrue( $references->hasReference( $reference ) );
170
		$this->assertNotSame( $reference, $references->getReference( $reference->getHash() ) );
171
	}
172
173
	public function testAddReferenceOnEmptyList() {
174
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
175
176
		$references = new ReferenceList();
177
		$references->addReference( $reference );
178
179
		$this->assertCount( 1, $references );
180
181
		$expectedList = new ReferenceList( [ $reference ] );
182
		$this->assertSameReferenceOrder( $expectedList, $references );
183
	}
184
185
	private function assertSameReferenceOrder( ReferenceList $expectedList, ReferenceList $references ) {
186
		$this->assertSame(
187
			iterator_to_array( $expectedList ),
188
			iterator_to_array( $references )
189
		);
190
	}
191
192
	public function testAddReferenceAtTheEnd() {
193
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
194
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
195
		$reference3 = new Reference( [ new PropertyNoValueSnak( 3 ) ] );
196
197
		$references = new ReferenceList( [ $reference1, $reference2 ] );
198
		$references->addReference( $reference3 );
199
200
		$this->assertCount( 3, $references );
201
202
		$expectedList = new ReferenceList( [ $reference1, $reference2, $reference3 ] );
203
		$this->assertSameReferenceOrder( $expectedList, $references );
204
	}
205
206
	public function testAddReferenceBetweenExistingReferences() {
207
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
208
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
209
		$list = new ReferenceList( [ $reference1, $reference2 ] );
210
211
		$reference3 = new Reference( [ new PropertyNoValueSnak( 3 ) ] );
212
		$list->addReference( $reference3, 1 );
213
214
		$this->assertCount( 3, $list );
215
		$this->assertSame( 1, $list->indexOf( $reference3 ) );
216
	}
217
218
	public function testAddReferenceIgnoresIdenticalObjects() {
219
		$list = new ReferenceList();
220
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
221
		$list->addReference( $reference );
222
		$list->addReference( $reference );
223
		$this->assertCount( 1, $list );
224
	}
225
226
	public function testAddReferenceDoesNotIgnoreCopies() {
227
		$list = new ReferenceList();
228
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
229
		$list->addReference( $reference );
230
		$list->addReference( clone $reference );
231
		$this->assertCount( 2, $list );
232
	}
233
234
	public function testAddReferenceAtIndexIgnoresIdenticalObjects() {
235
		$list = new ReferenceList();
236
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
237
		$list->addReference( $reference, 0 );
238
		$list->addReference( $reference, 0 );
239
		$this->assertCount( 1, $list );
240
	}
241
242
	public function testAddReferenceAtIndexMovesIdenticalObjects() {
243
		$list = new ReferenceList();
244
		$list->addNewReference( new PropertyNoValueSnak( 1 ) );
245
		$reference = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
246
		$list->addReference( $reference );
247
		$list->addNewReference( new PropertyNoValueSnak( 3 ) );
248
249
		$this->assertSame(
250
			1,
251
			$list->indexOf( $reference ),
252
			'pre-condition is that the element is at index 1'
253
		);
254
255
		$list->addReference( $reference, 0 );
256
257
		$this->assertCount( 3, $list, 'not added' );
258
		$this->assertSame(
259
			1,
260
			$list->indexOf( $reference ),
261
			'make sure calling addReference with a lower index did not changed it'
262
		);
263
264
		$list->addReference( $reference, 2 );
265
266
		$this->assertCount( 3, $list, 'not added' );
267
		$this->assertSame(
268
			1,
269
			$list->indexOf( $reference ),
270
			'make sure calling addReference with a higher index did not changed it'
271
		);
272
	}
273
274
	public function testAddReferenceAtIndexZero() {
275
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
276
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
277
		$reference3 = new Reference( [ new PropertyNoValueSnak( 3 ) ] );
278
279
		$references = new ReferenceList( [ $reference1, $reference2 ] );
280
		$references->addReference( $reference3, 0 );
281
282
		$expectedList = new ReferenceList( [ $reference3, $reference1, $reference2 ] );
283
		$this->assertSameReferenceOrder( $expectedList, $references );
284
	}
285
286
	public function testAddReferenceAtNegativeIndex() {
287
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
288
		$referenceList = new ReferenceList();
289
290
		$this->expectException( InvalidArgumentException::class );
291
		$referenceList->addReference( $reference, -1 );
292
	}
293
294
	public function testGivenEmptyReference_addReferenceDoesNotAdd() {
295
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
296
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
297
		$emptyReference = new Reference( [] );
298
299
		$references = new ReferenceList( [ $reference1, $reference2 ] );
300
		$references->addReference( $emptyReference );
301
302
		$expectedList = new ReferenceList( [ $reference1, $reference2 ] );
303
		$this->assertSameReferenceOrder( $expectedList, $references );
304
	}
305
306
	public function testGivenEmptyReferenceAndIndex_addReferenceDoesNotAdd() {
307
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
308
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
309
		$emptyReference = new Reference( [] );
310
311
		$references = new ReferenceList( [ $reference1, $reference2 ] );
312
		$references->addReference( $emptyReference, 0 );
313
314
		$expectedList = new ReferenceList( [ $reference1, $reference2 ] );
315
		$this->assertSameReferenceOrder( $expectedList, $references );
316
	}
317
318
	/**
319
	 * @dataProvider instanceProvider
320
	 */
321
	public function testIndexOf( ReferenceList $array ) {
322
		$this->assertFalse( $array->indexOf( new Reference() ) );
323
324
		$i = 0;
325
		foreach ( $array as $reference ) {
326
			$this->assertSame( $i++, $array->indexOf( $reference ) );
327
		}
328
	}
329
330
	public function testIndexOf_checksForIdentity() {
331
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
332
		$reference2 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
333
		$referenceList = new ReferenceList( [ $reference1 ] );
334
335
		$this->assertNotSame( $reference1, $reference2, 'post condition' );
336
		$this->assertTrue( $reference1->equals( $reference2 ), 'post condition' );
337
		$this->assertSame( 0, $referenceList->indexOf( $reference1 ), 'identity' );
338
		$this->assertFalse( $referenceList->indexOf( $reference2 ), 'not equality' );
339
	}
340
341
	/**
342
	 * @dataProvider instanceProvider
343
	 */
344
	public function testEquals( ReferenceList $array ) {
345
		$this->assertTrue( $array->equals( $array ) );
346
		$this->assertFalse( $array->equals( 42 ) );
347
	}
348
349
	/**
350
	 * @dataProvider instanceProvider
351
	 */
352
	public function testGetValueHashReturnsString( ReferenceList $array ) {
353
		$this->assertIsString( $array->getValueHash() );
354
	}
355
356
	/**
357
	 * @dataProvider instanceProvider
358
	 */
359
	public function testGetValueHashIsTheSameForClone( ReferenceList $array ) {
360
		$copy = unserialize( serialize( $array ) );
361
		$this->assertSame( $array->getValueHash(), $copy->getValueHash() );
362
	}
363
364
	/**
365
	 * @dataProvider instanceProvider
366
	 */
367
	public function testHasReferenceHash( ReferenceList $references ) {
368
		$this->assertFalse( $references->hasReferenceHash( '~=[,,_,,]:3' ) );
369
370
		/**
371
		 * @var Reference $reference
372
		 */
373
		foreach ( $references as $reference ) {
374
			$this->assertTrue( $references->hasReferenceHash( $reference->getHash() ) );
375
		}
376
	}
377
378
	/**
379
	 * @dataProvider instanceProvider
380
	 */
381
	public function testGetReference( ReferenceList $references ) {
382
		$this->assertNull( $references->getReference( '~=[,,_,,]:3' ) );
383
384
		/**
385
		 * @var Reference $reference
386
		 */
387
		foreach ( $references as $reference ) {
388
			$this->assertTrue( $reference->equals( $references->getReference( $reference->getHash() ) ) );
389
		}
390
	}
391
392
	/**
393
	 * @dataProvider instanceProvider
394
	 */
395
	public function testRemoveReferenceHash( ReferenceList $references ) {
396
		$references->removeReferenceHash( '~=[,,_,,]:3' );
397
398
		$hashes = [];
399
400
		/**
401
		 * @var Reference $reference
402
		 */
403
		foreach ( $references as $reference ) {
404
			$hashes[] = $reference->getHash();
405
		}
406
407
		foreach ( $hashes as $hash ) {
408
			$references->removeReferenceHash( $hash );
409
		}
410
411
		$this->assertTrue( $references->isEmpty() );
412
	}
413
414
	/**
415
	 * This integration test (relies on ReferenceList::getValueHash) is supposed to break whenever the hash
416
	 * calculation changes.
417
	 */
418
	public function testGetValueHashStability() {
419
		$array = new ReferenceList();
420
		$snak1 = new PropertyNoValueSnak( 1 );
421
		$snak2 = new PropertyNoValueSnak( 3 );
422
		$snak3 = new PropertyNoValueSnak( 2 );
423
424
		$array->addNewReference( $snak1, $snak2, $snak3 );
425
		$expectedHash = '92244e1a91f60b7fa922d42441995442bf50adb5';
426
		$this->assertSame( $expectedHash, $array->getValueHash() );
427
	}
428
429
	public function testRemoveReferenceHashRemovesIdenticalObjects() {
430
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
431
		$references = new ReferenceList( [ $reference, $reference ] );
432
433
		$references->removeReferenceHash( $reference->getHash() );
434
435
		$this->assertTrue( $references->isEmpty() );
436
	}
437
438
	public function testRemoveReferenceHashDoesNotRemoveCopies() {
439
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
440
		$references = new ReferenceList( [ $reference, clone $reference ] );
441
442
		$references->removeReferenceHash( $reference->getHash() );
443
444
		$this->assertFalse( $references->isEmpty() );
445
		$this->assertTrue( $references->hasReference( $reference ) );
446
		$this->assertNotSame( $reference, $references->getReference( $reference->getHash() ) );
447
	}
448
449
	public function testRemoveReferenceHashUpdatesIndexes() {
450
		$reference1 = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
451
		$reference2 = new Reference( [ new PropertyNoValueSnak( 2 ) ] );
452
		$references = new ReferenceList( [ $reference1, $reference2 ] );
453
454
		$references->removeReferenceHash( $reference1->getHash() );
455
456
		$this->assertSame( 0, $references->indexOf( $reference2 ) );
457
	}
458
459
	public function testGivenOneSnak_addNewReferenceAddsSnak() {
460
		$references = new ReferenceList();
461
		$snak = new PropertyNoValueSnak( 1 );
462
463
		$references->addNewReference( $snak );
464
		$this->assertTrue( $references->hasReference( new Reference( [ $snak ] ) ) );
465
	}
466
467
	public function testGivenMultipleSnaks_addNewReferenceAddsThem() {
468
		$references = new ReferenceList();
469
		$snak1 = new PropertyNoValueSnak( 1 );
470
		$snak2 = new PropertyNoValueSnak( 3 );
471
		$snak3 = new PropertyNoValueSnak( 2 );
472
473
		$references->addNewReference( $snak1, $snak2, $snak3 );
474
475
		$expectedSnaks = [ $snak1, $snak2, $snak3 ];
476
		$this->assertTrue( $references->hasReference( new Reference( $expectedSnaks ) ) );
477
	}
478
479
	public function testGivenAnArrayOfSnaks_addNewReferenceAddsThem() {
480
		$references = new ReferenceList();
481
		$snaks = [
482
			new PropertyNoValueSnak( 1 ),
483
			new PropertyNoValueSnak( 3 ),
484
			new PropertyNoValueSnak( 2 )
485
		];
486
487
		$references->addNewReference( $snaks );
488
		$this->assertTrue( $references->hasReference( new Reference( $snaks ) ) );
489
	}
490
491
	public function testAddNewReferenceDoesNotIgnoreIdenticalObjects() {
492
		$list = new ReferenceList();
493
		$snak = new PropertyNoValueSnak( 1 );
494
		$list->addNewReference( $snak );
495
		$list->addNewReference( $snak );
496
		$this->assertCount( 2, $list );
497
	}
498
499
	public function testAddNewReferenceDoesNotIgnoreCopies() {
500
		$list = new ReferenceList();
501
		$snak = new PropertyNoValueSnak( 1 );
502
		$list->addNewReference( $snak );
503
		$list->addNewReference( clone $snak );
504
		$this->assertCount( 2, $list );
505
	}
506
507
	public function testGivenNoneSnak_addNewReferenceThrowsException() {
508
		$references = new ReferenceList();
509
510
		$this->expectException( InvalidArgumentException::class );
511
		$references->addNewReference( new PropertyNoValueSnak( 1 ), null );
512
	}
513
514
	public function testEmptySerializationStability() {
515
		$list = new ReferenceList();
516
		$this->assertSame( 'a:0:{}', $list->serialize() );
517
	}
518
519
	/**
520
	 * This test will change when the serialization format changes.
521
	 * If it is being changed intentionally, the test should be updated.
522
	 * It is just here to catch unintentional changes.
523
	 */
524
	public function testSerializationStability() {
525
		$list = new ReferenceList();
526
		$list->addNewReference( new PropertyNoValueSnak( 1 ) );
527
528
		/*
529
		 * https://wiki.php.net/rfc/custom_object_serialization
530
		 */
531
		if ( version_compare( phpversion(), '7.4', '>=' ) ) {
532
			$testString = "a:1:{i:0;O:28:\"Wikibase\\DataModel\\Reference\":1:{s:35:\"\x00Wikibase\\DataModel\\"
533
				. "Reference\x00snaks\";O:32:\"Wikibase\\DataModel\\Snak\\SnakList\":2:{s:4:\""
534
				. 'data";a:1:{i:0;C:43:"Wikibase\\DataModel\\Snak\\PropertyNoValueSnak":2:{P1}}s:5'
535
				. ':"index";i:0;}}}';
536
		} else {
537
			$testString = "a:1:{i:0;O:28:\"Wikibase\\DataModel\\Reference\":1:{s:35:\"\x00Wikibase\\DataModel\\"
538
				. "Reference\x00snaks\";C:32:\"Wikibase\\DataModel\\Snak\\SnakList\":100:{a:2:{s:4:\""
539
				. 'data";a:1:{i:0;C:43:"Wikibase\\DataModel\\Snak\\PropertyNoValueSnak":2:{P1}}s:5'
540
				. ':"index";i:0;}}}}';
541
		}
542
543
		$this->assertSame(
544
			$testString,
545
			$list->serialize()
546
		);
547
	}
548
549
	public function testSerializeUnserializeRoundtrip() {
550
		$original = new ReferenceList();
551
		$original->addNewReference( new PropertyNoValueSnak( 1 ) );
552
553
		/** @var ReferenceList $clone */
554
		$clone = unserialize( serialize( $original ) );
555
556
		$this->assertTrue( $original->equals( $clone ) );
557
		$this->assertSame( $original->getValueHash(), $clone->getValueHash() );
558
		$this->assertSame( $original->serialize(), $clone->serialize() );
559
	}
560
561
	public function testUnserializeCreatesNonIdenticalClones() {
562
		$original = new ReferenceList();
563
		$reference = new Reference( [ new PropertyNoValueSnak( 1 ) ] );
564
		$original->addReference( $reference );
565
566
		/** @var ReferenceList $clone */
567
		$clone = unserialize( serialize( $original ) );
568
		$clone->addReference( $reference );
569
570
		$this->assertCount( 2, $clone );
571
	}
572
573
	public function testGivenEmptyList_isEmpty() {
574
		$references = new ReferenceList();
575
		$this->assertTrue( $references->isEmpty() );
576
	}
577
578
	public function testGivenNonEmptyList_isNotEmpty() {
579
		$references = new ReferenceList();
580
		$references->addNewReference( new PropertyNoValueSnak( 1 ) );
581
582
		$this->assertFalse( $references->isEmpty() );
583
	}
584
585
}
586