Passed
Push — html ( 1dd7b6 )
by Peter
08:14
created

NodeTest::testArrayAccessWithoutOffset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Framework\Html;
6
7
use AbterPhp\Framework\I18n\ITranslator;
8
use AbterPhp\Framework\TestDouble\Html\CollectionStub;
9
use AbterPhp\Framework\TestDouble\I18n\MockTranslatorFactory;
10
use Closure;
11
use PHPUnit\Framework\TestCase;
12
13
class NodeTest extends TestCase
14
{
15
16
    /** @var Node - System Under Test */
17
    protected Node $sut;
18
19
    public function setUp(): void
20
    {
21
        $this->sut = new Node();
22
    }
23
24
    /**
25
     * @return array[]
26
     */
27
    public function setContentProvider(): array
28
    {
29
        $fooNode     = new Node('foo');
30
        $fooBarNode  = new Node(['foo', 'bar']);
31
        $fooNodeNode = new Node($fooNode, 'baz');
32
33
        return [
34
            'null'            => [null, '', []],
35
            'false'           => [false, '', []],
36
            'true'            => [true, '1', []],
37
            '12.43'           => [12.43, '12.43', []],
38
            'string'          => ['foo', 'foo', []],
39
            'array'           => [['foo'], 'foo', []],
40
            'strings'         => [['foo', 'bar'], 'foobar', []],
41
            'node'            => [$fooNode, 'foo', [$fooNode]],
42
            'node-with-array' => [$fooBarNode, 'foobar', [$fooBarNode]],
43
            'node-node'       => [$fooNodeNode, 'foo', [$fooNodeNode]],
44
            'node-nodes'      => [[$fooNode, $fooNodeNode], 'foofoo', [$fooNode, $fooNodeNode]],
45
            'mixed'           => [[$fooNode, 'bar', $fooNodeNode], 'foobarfoo', [$fooNode, $fooNodeNode]],
46
        ];
47
    }
48
49
    /**
50
     * @dataProvider setContentProvider
51
     *
52
     * @param        $content
53
     * @param string $expectedAsString
54
     * @param array  $expectedNodes
55
     */
56
    public function testSetContentNode($content, string $expectedAsString, array $expectedNodes)
57
    {
58
        $node = new Node($content);
59
60
        $actualString = (string)$node;
61
        $this->assertEquals($expectedAsString, $actualString);
62
63
        $actualNodes = $node->getExtendedNodes();
64
        $this->assertEquals($expectedNodes, $actualNodes);
65
    }
66
67
    public function testSetContentWillThrowExceptionOnNonStringLikeContent()
68
    {
69
        $this->expectException(\Error::class);
70
71
        new Node(new \stdClass());
72
    }
73
74
    public function testConstructAddsIntents()
75
    {
76
        $expectedIntents = ['foo', 'bar'];
77
78
        $node = new Node(null, ...$expectedIntents);
79
        $this->assertEquals($expectedIntents, $node->getIntents());
80
    }
81
82
    public function testSetIntent()
83
    {
84
        $node = new Node(null, 'foo', 'bar');
85
        $this->assertTrue($node->hasIntent('foo'));
86
        $this->assertTrue($node->hasIntent('bar'));
87
88
        $node->setIntent('Foo', 'Bar');
89
        $this->assertFalse($node->hasIntent('foo'));
90
        $this->assertFalse($node->hasIntent('bar'));
91
        $this->assertTrue($node->hasIntent('Foo'));
92
        $this->assertTrue($node->hasIntent('Bar'));
93
    }
94
95
    public function testAddIntent()
96
    {
97
        $node = new Node(null, 'foo', 'bar');
98
        $this->assertTrue($node->hasIntent('foo'));
99
        $this->assertTrue($node->hasIntent('bar'));
100
101
        $node->addIntent('baz');
102
        $this->assertTrue($node->hasIntent('foo'));
103
        $this->assertTrue($node->hasIntent('bar'));
104
        $this->assertTrue($node->hasIntent('baz'));
105
    }
106
107
    public function testSetTranslatorSetTranslatorOnNodes()
108
    {
109
        $embedded = new Node();
110
        $wrapper  = new Node($embedded);
111
112
        $translatorMock = $this->getMockBuilder(ITranslator::class)->getMock();
113
114
        $wrapper->setTranslator($translatorMock);
115
116
        $this->assertSame($translatorMock, $embedded->getTranslator());
117
    }
118
119
    /**
120
     * @return array[]
121
     */
122
    public function isMatchProvider(): array
123
    {
124
        $f1 = fn(INode $node) => $node->hasIntent('foo');
125
        $f2 = fn(INode $node) => false;
0 ignored issues
show
Unused Code introduced by
The parameter $node is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

125
        $f2 = fn(/** @scrutinizer ignore-unused */ INode $node) => false;

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
126
127
        return [
128
            'empty'          => [null, null, [], true],
129
            'inode'          => [INode::class, null, [], true],
130
            'node'           => [Node::class, null, [], true],
131
            'nodetest'       => [NodeTest::class, null, [], false],
132
            'foo'            => [null, null, ['foo'], true],
133
            'bar'            => [null, null, ['bar'], true],
134
            'baz'            => [null, null, ['baz'], false],
135
            'foobar'         => [null, null, ['foo', 'bar'], true],
136
            'node-foobar'    => [Node::class, null, ['foo', 'bar'], true],
137
            'node-foobarbaz' => [Node::class, null, ['foo', 'bar', 'baz'], false],
138
            'f1'             => [null, $f1, [], true],
139
            'f2'             => [null, $f2, [], false],
140
        ];
141
    }
142
143
    /**
144
     * @dataProvider isMatchProvider
145
     *
146
     * @param string|null   $className
147
     * @param \Closure|null $matcher
148
     * @param string[]      $intents
149
     * @param bool          $expectedResult
150
     */
151
    public function testIsMatch(?string $className, ?Closure $matcher, array $intents, bool $expectedResult)
152
    {
153
        $sut = new Node('foo', 'foo', 'bar');
154
155
        $actualResult = $sut->isMatch($className, $matcher, ...$intents);
156
157
        $this->assertSame($expectedResult, $actualResult);
158
    }
159
160
    /**
161
     * @dataProvider isMatchProvider
162
     *
163
     * @param string|null   $className
164
     * @param \Closure|null $matcher
165
     * @param string[]      $intents
166
     * @param bool          $expectedResult
167
     */
168
    public function testFindFindsItself(?string $className, ?Closure $matcher, array $intents, bool $expectedResult)
169
    {
170
        $sut = new Node('foo', 'foo', 'bar');
171
172
        $actualResult = $sut->find($className, $matcher, ...$intents);
173
174
        if ($expectedResult) {
175
            $this->assertSame($sut, $actualResult);
176
        } else {
177
            $this->assertNull($actualResult);
178
        }
179
    }
180
181
    /**
182
     * @return array[]
183
     */
184
    public function findTraversesNodesProvider(): array
185
    {
186
        $f1 = fn(INode $node) => $node->hasIntent('foo');
187
        $f2 = fn($a) => false;
0 ignored issues
show
Unused Code introduced by
The parameter $a is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

187
        $f2 = fn(/** @scrutinizer ignore-unused */ $a) => false;

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
188
189
        return [
190
            'foo'            => [null, null, ['foo'], true],
191
            'bar'            => [null, null, ['bar'], true],
192
            'baz'            => [null, null, ['baz'], false],
193
            'foobar'         => [null, null, ['foo', 'bar'], true],
194
            'node-foobar'    => [Node::class, null, ['foo', 'bar'], true],
195
            'node-foobarbaz' => [Node::class, null, ['foo', 'bar', 'baz'], false],
196
            'f1'             => [null, $f1, [], true],
197
            'f2'             => [null, $f2, [], false],
198
        ];
199
    }
200
201
    /**
202
     * @dataProvider findTraversesNodesProvider
203
     *
204
     * @param string|null   $className
205
     * @param \Closure|null $matcher
206
     * @param string[]      $intents
207
     * @param bool          $expectedResult
208
     */
209
    public function testFindTraversesNodes(?string $className, ?Closure $matcher, array $intents, bool $expectedResult)
210
    {
211
        $grandChild = new Node('foo', 'foo', 'bar');
212
        $child      = new Node([new Node(), '', $grandChild]);
213
        $sut        = new Node($child);
214
215
        $actualResult = $sut->find($className, $matcher, ...$intents);
216
217
        if ($expectedResult) {
218
            $this->assertSame($grandChild, $actualResult);
219
        } else {
220
            $this->assertNull($actualResult);
221
        }
222
    }
223
224
    /**
225
     * @return array[]
226
     */
227
    public function findAllProvider(): array
228
    {
229
        $f1 = fn(INode $node) => $node->hasIntent('foo');
230
        $f2 = fn($a) => false;
0 ignored issues
show
Unused Code introduced by
The parameter $a is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

230
        $f2 = fn(/** @scrutinizer ignore-unused */ $a) => false;

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
231
232
        return [
233
            'empty'          => [null, null, [], 4],
234
            'inode'          => [INode::class, null, [], 4],
235
            'node'           => [Node::class, null, [], 4],
236
            'nodetest'       => [NodeTest::class, null, [], 0],
237
            'foo'            => [null, null, ['foo'], 1],
238
            'bar'            => [null, null, ['bar'], 1],
239
            'baz'            => [null, null, ['baz'], 0],
240
            'foobar'         => [null, null, ['foo', 'bar'], 1],
241
            'node-foobar'    => [Node::class, null, ['foo', 'bar'], 1],
242
            'node-foobarbaz' => [Node::class, null, ['foo', 'bar', 'baz'], 0],
243
            'f1'             => [null, $f1, [], 1],
244
            'f2'             => [null, $f2, [], 0],
245
        ];
246
    }
247
248
    /**
249
     * @dataProvider findAllProvider
250
     *
251
     * @param string|null   $className
252
     * @param \Closure|null $matcher
253
     * @param string[]      $intents
254
     * @param int           $expectedLength
255
     */
256
    public function testFindAll(?string $className, ?Closure $matcher, array $intents, int $expectedLength)
257
    {
258
        $grandChild = new Node('foo', 'foo', 'bar');
259
        $child      = new Node([new Node(), '', $grandChild]);
260
        $sut        = new Node($child);
261
262
        $actualResult = $sut->findAll($className, $matcher, ...$intents);
263
264
        $this->assertSame($expectedLength, count($actualResult));
265
    }
266
267
    /**
268
     * @return array[]
269
     */
270
    public function findAllShallowProvider(): array
271
    {
272
        return [
273
            'empty-3'  => [3, null, null, [], 4],
274
            'empty-2'  => [2, null, null, [], 4],
275
            'empty-1'  => [1, null, null, [], 2],
276
            'empty-0'  => [0, null, null, [], 1],
277
            'empty--1' => [-1, null, null, [], 4],
278
            'foo-2'    => [2, null, null, ['foo'], 1],
279
            'foo-1'    => [1, null, null, ['foo'], 0],
280
        ];
281
    }
282
283
    /**
284
     * @dataProvider findAllShallowProvider
285
     *
286
     * @param int           $maxDepth
287
     * @param string|null   $className
288
     * @param \Closure|null $matcher
289
     * @param string[]      $intents
290
     * @param int           $expectedLength
291
     */
292
    public function testFindAllShallow(
293
        int $maxDepth,
294
        ?string $className,
295
        ?Closure $matcher,
296
        array $intents,
297
        int $expectedLength
298
    ) {
299
        $grandChild = new Node('foo', 'foo', 'bar');
300
        $child      = new Node([new Node(), '', $grandChild]);
301
        $sut        = new Node($child);
302
303
        $actualResult = $sut->findAllShallow($maxDepth, $className, $matcher, ...$intents);
304
305
        $this->assertSame($expectedLength, count($actualResult));
306
    }
307
308
    public function testToString()
309
    {
310
        $grandChild = new Node('foo', 'foo', 'bar');
311
        $child      = new Node([new Node(), '', $grandChild]);
312
        $sut        = new Node($child);
313
314
        $expectedResult = 'bar';
315
316
        $translatorMock = MockTranslatorFactory::createSimpleTranslator($this, ['foo' => 'bar']);
317
318
        $sut->setTranslator($translatorMock);
319
320
        $actualResult = (string)$sut;
321
322
        $this->assertSame($expectedResult, $actualResult);
323
    }
324
    public function toStringReturnsRawContentByDefaultProvider(): array
325
    {
326
        return [
327
            'INode[]' => [[new Node('foo')], 'foo'],
328
        ];
329
    }
330
331
    /**
332
     * @dataProvider toStringReturnsRawContentByDefaultProvider
333
     *
334
     * @param mixed  $rawContent
335
     * @param string $expectedResult
336
     */
337
    public function testToStringReturnsRawContentByDefault($rawContent, string $expectedResult): void
338
    {
339
        $sut = $this->createCollectionStub($rawContent);
340
341
        $this->assertStringContainsString($expectedResult, (string)$sut);
342
    }
343
344
    /**
345
     * @return array
346
     */
347
    public function toStringCanReturnTranslatedContentProvider(): array
348
    {
349
        $translations = ['foo' => 'bar'];
350
351
        return [
352
            'INode[]' => [[new Node('foo')], $translations, 'bar'],
353
        ];
354
    }
355
356
    /**
357
     * @dataProvider toStringCanReturnTranslatedContentProvider
358
     *
359
     * @param mixed  $rawContent
360
     * @param array  $translations
361
     * @param string $expectedResult
362
     */
363
    public function testToStringCanReturnTranslatedContent(
364
        $rawContent,
365
        array $translations,
366
        string $expectedResult
367
    ): void {
368
        $translatorMock = MockTranslatorFactory::createSimpleTranslator($this, $translations);
369
370
        $sut = $this->createCollectionStub($rawContent);
371
372
        $sut->setTranslator($translatorMock);
373
374
        $this->assertStringContainsString($expectedResult, (string)$sut);
375
    }
376
377
    public function testCountWithoutOffset(): void
378
    {
379
        $expectedResult = 2;
380
381
        $node1 = new Node('1');
382
        $node2 = new Node('2');
383
384
        $sut = $this->createCollectionStub();
385
386
        $sut[] = $node1;
387
        $sut[] = $node2;
388
389
        $this->assertSame($expectedResult, count($sut));
390
    }
391
392
    public function testCountWithExplicitOffset(): void
393
    {
394
        $expectedResult = 2;
395
396
        $node1 = new Node('1');
397
        $node2 = new Node('2');
398
399
        $sut = $this->createCollectionStub();
400
401
        $sut[0] = $node1;
402
        $sut[1] = $node2;
403
404
        $this->assertSame($expectedResult, count($sut));
405
    }
406
407
    public function testCountWithMixedOffset(): void
408
    {
409
        $node1 = new Node('1');
410
        $node2 = new Node('2');
411
412
        $expectedCount = 5;
413
414
        $sut = $this->createCollectionStub();
415
416
        $sut[]  = $node1;
417
        $sut[]  = $node1;
418
        $sut[1] = $node2;
419
        $sut[2] = $node1;
420
        $sut[3] = $node1;
421
        $sut[]  = $node1;
422
423
        $this->assertSame($expectedCount, count($sut));
424
    }
425
426
    public function testArrayAccessWithoutOffset(): void
427
    {
428
        $node1 = new Node('1');
429
        $node2 = new Node('2');
430
431
        $sut = $this->createCollectionStub();
432
433
        $sut[] = $node1;
434
        $sut[] = $node2;
435
436
        $this->assertSame($node1, $sut[0]);
437
        $this->assertSame($node2, $sut[1]);
438
    }
439
440
    public function testArrayAccessWithExplicitOffset(): void
441
    {
442
        $node1 = new Node('1');
443
        $node2 = new Node('2');
444
445
        $sut = $this->createCollectionStub();
446
447
        $sut[0] = $node1;
448
        $sut[1] = $node2;
449
450
        $this->assertSame($node1, $sut[0]);
451
        $this->assertSame($node2, $sut[1]);
452
    }
453
454
    public function testArrayAccessThrowExceptionWhenMadeDirty(): void
455
    {
456
        $this->expectException(\InvalidArgumentException::class);
457
458
        $node1 = new Node('1');
459
460
        $sut = $this->createCollectionStub();
461
462
        $sut[1] = $node1;
463
    }
464
465
    public function testArrayAccessWithMixedOffset(): void
466
    {
467
        $node1 = new Node('1');
468
        $node2 = new Node('2');
469
470
        $expectedNodes = [0 => $node1, 1 => $node2, 2 => $node1, 3 => $node1, 4 => $node1];
471
472
        $sut = $this->createCollectionStub();
473
474
        $sut[]  = $node1;
475
        $sut[]  = $node1;
476
        $sut[1] = $node2;
477
        $sut[2] = $node1;
478
        $sut[3] = $node1;
479
        $sut[]  = $node1;
480
481
        $this->assertEquals($expectedNodes, $sut->getExtendedNodes());
482
    }
483
484
    /**
485
     * @return array[]
486
     */
487
    public function contentFailureProvider(): array
488
    {
489
        return [
490
            'string wrapped'          => [['']],
491
            'non-node object wrapped' => [[new \StdClass()]],
492
            'node double wrapped'     => [[[new Node()]]],
493
        ];
494
    }
495
496
    /**
497
     * @dataProvider contentFailureProvider
498
     */
499
    public function testConstructFailure($item): void
500
    {
501
        $this->expectException(\AssertionError::class);
502
503
        $this->createCollectionStub($item);
504
    }
505
506
    /**
507
     * @dataProvider contentFailureProvider
508
     */
509
    public function testSetContentFailure($item): void
510
    {
511
        $this->expectException(\AssertionError::class);
512
513
        $sut = $this->createCollectionStub();
514
515
        $sut->setContent($item);
516
    }
517
518
    /**
519
     * @return array
520
     */
521
    public function offsetSetFailureProvider(): array
522
    {
523
        $contentFailure = $this->contentFailureProvider();
524
525
        $offsetFailure = [
526
            'string'       => ['foo'],
527
            'node wrapped' => [[new Node()]],
528
        ];
529
530
        return array_merge($contentFailure, $offsetFailure);
531
    }
532
533
    /**
534
     * @dataProvider offsetSetFailureProvider
535
     */
536
    public function testArrayAccessFailureWithoutOffset($item): void
537
    {
538
        $this->expectException(\AssertionError::class);
539
540
        $sut = $this->createCollectionStub();
541
542
        $sut[] = $item;
543
    }
544
545
    /**
546
     * @dataProvider offsetSetFailureProvider
547
     */
548
    public function testArrayAccessFailureWithExplicitOffset($item): void
549
    {
550
        $this->expectException(\AssertionError::class);
551
552
        $sut = $this->createCollectionStub();
553
554
        $sut[] = $item;
555
    }
556
557
    public function testArrayAccessUnset(): void
558
    {
559
        $node1 = new Node('1');
560
561
        $sut = $this->createCollectionStub();
562
563
        $sut[] = $node1;
564
565
        $this->assertTrue($sut->offsetExists(0));
566
567
        unset($sut[0]);
568
569
        $this->assertfalse($sut->offsetExists(0));
570
    }
571
572
    public function testSetContentCollection(): void
573
    {
574
        $node1 = new Node('1');
575
        $node2 = new Node('2');
576
577
        $expectedNodes = [$node1];
578
579
        $sut = $this->createCollectionStub();
580
581
        $sut[]  = $node1;
582
        $sut[]  = $node1;
583
        $sut[1] = $node2;
584
        $sut[2] = $node1;
585
        $sut[3] = $node1;
586
        $sut[]  = $node1;
587
588
        $sut->setContent($node1);
589
590
        $actualResult = $sut->getNodes();
591
592
        $this->assertEquals($expectedNodes, $actualResult);
593
    }
594
595
    public function testIterator(): void
596
    {
597
        $node1 = new Node('1');
598
        $node2 = new Node('2');
599
600
        $expectedKeys  = [0, 1, 2, 3, 4];
601
        $expectedNodes = [$node1, $node2, $node1, $node1, $node1];
602
603
        $sut = $this->createCollectionStub();
604
605
        $sut[]  = $node1;
606
        $sut[]  = $node1;
607
        $sut[1] = $node2;
608
        $sut[2] = $node1;
609
        $sut[3] = $node1;
610
        $sut[]  = $node1;
611
612
        $pos = 0;
613
        foreach ($sut as $key => $node) {
614
            $this->assertSame($expectedKeys[$pos], $key);
615
            $this->assertSame($expectedNodes[$pos], $node);
616
            $pos++;
617
        }
618
    }
619
620
    public function testGetRawContentReturnsNonTranslatedContent(): void
621
    {
622
        $this->assertTrue(true, 'No need to test getRawContent');
623
    }
624
625
    /**
626
     * @return array
627
     */
628
    public function replaceProvider(): array
629
    {
630
        $needle = new Node('1');
631
        $node2  = new Node('2');
632
        $node3  = new Node('3');
633
634
        return [
635
            'empty-content'                  => [
636
                [],
637
                $needle,
638
                [$node2],
639
                [],
640
            ],
641
            'only-non-matching-content'      => [
642
                [$node2, $node3],
643
                $needle,
644
                [$needle, $node3],
645
                [$node2, $node3],
646
            ],
647
            'only-non-matching-content-deep' => [
648
                [$node2, $node3, new CollectionStub([$node2, $node3])],
649
                $needle,
650
                [$needle, $node3],
651
                [$node2, $node3, new CollectionStub([$node2, $node3])],
652
            ],
653
            'only-matching-content'          => [
654
                [$needle],
655
                $needle,
656
                [$node2, $node3],
657
                [$node2, $node3],
658
            ],
659
            'non-first-matching-content'     => [
660
                [$node2, $needle],
661
                $needle,
662
                [$node3, $node3],
663
                [$node2, $node3, $node3],
664
            ],
665
            'non-last-matching-content'      => [
666
                [$needle, $node2],
667
                $needle,
668
                [$node3, $node3],
669
                [$node3, $node3, $node2],
670
            ],
671
            'deep-first-matching-content'    => [
672
                [$node2, new CollectionStub([$node2, $needle])],
673
                $needle,
674
                [$node3, $node3],
675
                [$node2, new CollectionStub([$node2, $node3, $node3])],
676
            ],
677
        ];
678
    }
679
680
    /**
681
     * @dataProvider replaceProvider
682
     *
683
     * @param INode[] $content
684
     * @param INode   $nodeToFind
685
     * @param INode[] $nodesToInsert
686
     * @param INode[] $expectedNodes
687
     */
688
    public function testReplace(
689
        array $content,
690
        INode $nodeToFind,
691
        array $nodesToInsert,
692
        array $expectedNodes
693
    ): void {
694
        $sut = $this->createCollectionStub($content);
695
696
        $sut->replace($nodeToFind, ...$nodesToInsert);
697
698
        $this->assertEquals($expectedNodes, $sut->getNodes());
699
    }
700
701
    /**
702
     * @return array
703
     */
704
    public function hasIntentProvider(): array
705
    {
706
        return [
707
            [[], 'foo', false],
708
            [['foo'], 'foo', true],
709
            [['bar'], 'foo', false],
710
            [['foo', 'bar', 'baz'], 'bar', true],
711
        ];
712
    }
713
714
    /**
715
     * @dataProvider hasIntentProvider
716
     *
717
     * @param array  $intents
718
     * @param string $intentToCheck
719
     * @param bool   $expectedResult
720
     */
721
    public function testHasIntentChecksIfAGivenIntentHasBeenSet(
722
        array $intents,
723
        string $intentToCheck,
724
        bool $expectedResult
725
    ): void {
726
        $sut = $this->createCollectionStub();
727
728
        $sut->setIntent(...$intents);
729
730
        $actualResult = $sut->hasIntent($intentToCheck);
731
732
        $this->assertSame($expectedResult, $actualResult);
733
    }
734
735
    /**
736
     * @param INode[]|INode|null $content
737
     *
738
     * @return CollectionStub
739
     */
740
    private function createCollectionStub($content = null): CollectionStub
741
    {
742
        return new CollectionStub($content);
743
    }
744
}
745