1+ <?php
2+
3+ declare (strict_types=1 );
4+
5+ use TypePHP \Tests \Fixtures \Collections \AnimalElementCollection ;
6+ use TypePHP \Tests \Fixtures \Collections \UnparameterizedElementCollection ;
7+ use TypePHP \Tests \Fixtures \Domain \Animal ;
8+ use TypePHP \Tests \Fixtures \Domain \Car ;
9+ use TypePHP \Tests \Fixtures \Domain \Dog ;
10+
11+ describe ('Class-Level Template Resolution in Callable & Iterator Parameters ' , function () {
12+ describe ('Generic Subclasses (@extends Collection<Animal>) ' , function () {
13+ test ('resolves class-level template TElement when filtering populated collection ' , function () {
14+ $ collection = new AnimalElementCollection ();
15+ $ collection ->setElementsDirectly ([new Dog ()]);
16+
17+ $ result = $ collection ->filter (function (Animal $ animal ) {
18+ return true ;
19+ });
20+
21+ expect ($ result ->count ())->toBe (1 );
22+ });
23+
24+ test ('resolves class-level template TElement when iterating through getIterator() ' , function () {
25+ $ collection = new AnimalElementCollection ();
26+ $ collection ->setElementsDirectly ([new Dog (), new Dog ()]);
27+
28+ $ collected = [];
29+ foreach ($ collection as $ item ) {
30+ $ collected [] = $ item ;
31+ }
32+
33+ expect (\count ($ collected ))->toBe (2 )
34+ ->and ($ collected [0 ])->toBeInstanceOf (Dog::class)
35+ ;
36+ });
37+
38+ test ('validates iterable parameter against class-level template TElement ' , function () {
39+ $ collection = new AnimalElementCollection ();
40+ $ validIterator = new ArrayIterator ([new Dog ()]);
41+
42+ $ collection ->mergeItems ($ validIterator );
43+ expect ($ collection ->count ())->toBe (1 );
44+
45+ $ badIterator = new ArrayIterator ([new Car ()]);
46+ expect (fn () => $ collection ->mergeItems ($ badIterator ))
47+ ->toThrow (TypeError::class, 'must be of type TypePHP\Tests\Fixtures\Domain\Animal ' )
48+ ;
49+ });
50+ });
51+
52+ describe ('Unparameterized Collections (Unbound TElement -> mixed) ' , function () {
53+ test ('falls back TElement to mixed on filter() closures without throwing literal TElement errors ' , function () {
54+ $ collection = new UnparameterizedElementCollection ();
55+ $ collection ->setElementsDirectly ([new Dog (), 'string_val ' , new stdClass ()]);
56+
57+ $ result = $ collection ->filter (function ($ item ) {
58+ return true ;
59+ });
60+
61+ expect ($ result ->count ())->toBe (3 );
62+ });
63+
64+ test ('falls back TElement to mixed on getIterator() yields without throwing literal TElement errors ' , function () {
65+ $ collection = new UnparameterizedElementCollection ();
66+ $ collection ->setElementsDirectly ([new Dog (), 42 , ['array_val ' ]]);
67+
68+ $ yielded = [];
69+ foreach ($ collection as $ item ) {
70+ $ yielded [] = $ item ;
71+ }
72+
73+ expect (\count ($ yielded ))->toBe (3 );
74+ });
75+
76+ test ('accepts mixed items on mergeItems() iterable parameter when collection is unparameterized ' , function () {
77+ $ collection = new UnparameterizedElementCollection ();
78+ $ mixedIterator = new ArrayIterator ([new Dog (), new Car (), 100 ]);
79+
80+ $ collection ->mergeItems ($ mixedIterator );
81+ expect ($ collection ->count ())->toBe (3 );
82+ });
83+ });
84+ });
0 commit comments