Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
265 / 265
100.00% covered (success)
100.00%
1 / 1
CRAP
n/a
0 / 0
createTestCollection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10use Phexium\Domain\Collection\AbstractCollection;
11
12function createTestCollection(array $items = []): object
13{
14    return new class($items) extends AbstractCollection {};
15}
16
17test('fromArray() creates a new collection', function (): void {
18    $collection = createTestCollection()::fromArray([]);
19    expect($collection->items())->toBeEmpty();
20
21    $collection = createTestCollection()::fromArray(['foo']);
22    expect($collection->items())->toHaveCount(1);
23
24    $collection = createTestCollection()::fromArray(['key1' => 'a', 'key2' => 'b']);
25    expect($collection->items())->toHaveCount(2);
26});
27
28test('fromMap() creates a new collection', function (): void {
29    $array = ['foo', 'bar', 'baz'];
30    $callable = fn ($item) => strtoupper((string) $item);
31
32    $collection = createTestCollection()::fromMap($array, $callable);
33
34    expect($collection->items())->toHaveCount(3);
35    expect($collection->values())->toBe(['FOO', 'BAR', 'BAZ']);
36});
37
38test('Items() returns empty array when collection is empty', function (): void {
39    $collection = createTestCollection();
40
41    $items = $collection->items();
42    $values = $collection->values();
43
44    expect($items)->toBeArray();
45    expect($items)->toBeEmpty();
46    expect($values)->toBeEmpty();
47});
48
49test('Items() returns all items', function (): void {
50    $collection = createTestCollection(['key1' => 'a', 'key2' => 'b']);
51
52    $items = $collection->items();
53    $values = $collection->values();
54
55    expect($items)->toBeArray();
56    expect($items)->toHaveCount(2);
57    expect($values)->toHaveCount(2);
58});
59
60test('Add() adds an item to the collection', function (): void {
61    $collection = createTestCollection();
62    expect($collection)->toHaveCount(0);
63
64    $collection->add('foo');
65    expect($collection)->toHaveCount(1);
66
67    $collection->add('bar');
68    expect($collection)->toHaveCount(2);
69});
70
71test('Map() returns a new collection with the items mapped', function (): void {
72    $collection = createTestCollection([1, 2, 3, 5, 8]);
73    expect($collection->map(fn ($item): float|int => $item * 2)->values())->toBe([2, 4, 6, 10, 16]);
74});
75
76test('Filter() returns a new collection with the items filtered', function (): void {
77    $collection = createTestCollection([1, 2, 3, 4, 5]);
78    expect($collection->filter(fn ($item): bool => $item % 2 === 0)->values())->toBe([2, 4]);
79});
80
81test('Reduce() returns a new collection with the items reduced', function (): void {
82    $collection = createTestCollection([1, 2, 3, 5, 8]);
83    expect($collection->reduce(fn ($carry, $item): array|float|int => $carry + $item, 0))->toBe(19);
84});
85
86test('Each() calls a callback for each item', function (): void {
87    $collection = createTestCollection([1, 2, 3, 5, 8]);
88    $count = 0;
89    $collection->each(function ($item) use (&$count): void {
90        $count += $item;
91    });
92    expect($count)->toBe(19);
93});
94
95test('Some() returns true if at least one item satisfies the condition', function (): void {
96    $collection = createTestCollection([1, 2, 3, 5, 8]);
97    expect($collection->some(fn ($item): bool => $item < 5))->toBeTrue();
98    expect($collection->some(fn ($item): bool => $item > 10))->toBeFalse();
99});
100
101test('First() and Last() returns the first and last item', function (): void {
102    $collection = createTestCollection([]);
103    expect($collection->first())->toBeFalse();
104    expect($collection->last())->toBeFalse();
105
106    $collection = createTestCollection(['qix']);
107    expect($collection->first())->toBe('qix');
108    expect($collection->last())->toBe('qix');
109
110    $collection = createTestCollection(['foo', 'bar', 'baz']);
111    expect($collection->first())->toBe('foo');
112    expect($collection->last())->toBe('baz');
113});
114
115test('Count() returns the number of items', function (): void {
116    $collection = createTestCollection();
117    expect($collection->count())->toBe(0);
118    expect($collection)->toHaveCount(0);
119
120    $collection = createTestCollection(['foo']);
121    expect($collection->count())->toBe(1);
122    expect($collection)->toHaveCount(1);
123
124    $collection = createTestCollection(['key1' => 'a', 'key2' => 'b']);
125    expect($collection->count())->toBe(2);
126    expect($collection)->toHaveCount(2);
127});
128
129test('IsEmpty() returns true when collection is empty', function (): void {
130    $collection = createTestCollection();
131
132    expect($collection->isEmpty())->toBeTrue();
133});
134
135test('IsEmpty() returns false when collection has entities', function (): void {
136    $collection = createTestCollection(['foo']);
137
138    expect($collection->isEmpty())->toBeFalse();
139});
140
141test('IteratorAggregate interface is implemented', function (): void {
142    $collection = createTestCollection(['foo', 'bar']);
143
144    $count = 0;
145
146    foreach ($collection as $item) {
147        ++$count;
148    }
149
150    expect($count)->toBe(2);
151});
152
153test('Contains() returns true when element exists', function (): void {
154    $collection = createTestCollection(['foo', 'bar']);
155
156    expect($collection->contains('foo'))->toBeTrue();
157    expect($collection->contains('bar'))->toBeTrue();
158});
159
160test('Contains() returns false when element does not exist', function (): void {
161    $collection = createTestCollection(['foo', 'bar']);
162
163    expect($collection->contains('qix'))->toBeFalse();
164});
165
166test('Every() returns true when all elements satisfy condition', function (): void {
167    $collection = createTestCollection([2, 4, 6, 8]);
168
169    expect($collection->every(fn ($item): bool => $item % 2 === 0))->toBeTrue();
170});
171
172test('Every() returns false when at least one element does not satisfy condition', function (): void {
173    $collection = createTestCollection([2, 3, 4, 5]);
174
175    expect($collection->every(fn ($item): bool => $item % 2 === 0))->toBeFalse();
176});
177
178test('Every() returns true for empty collection', function (): void {
179    $collection = createTestCollection([]);
180
181    expect($collection->every(fn ($item): false => false))->toBeTrue();
182});
183
184test('Find() returns first element that satisfies condition', function (): void {
185    $collection = createTestCollection([1, 2, 3, 4, 5]);
186
187    $result = $collection->find(fn ($item): bool => $item > 2);
188
189    expect($result)->toBe(3);
190});
191
192test('Find() returns false when no element satisfies condition', function (): void {
193    $collection = createTestCollection([1, 2, 3]);
194
195    $result = $collection->find(fn ($item): bool => $item > 10);
196
197    expect($result)->toBeFalse();
198});
199
200test('Remove() removes first occurrence of element and returns true', function (): void {
201    $collection = createTestCollection(['foo', 'bar', 'baz']);
202    expect($collection)->toHaveCount(3);
203
204    $result = $collection->remove('bar');
205
206    expect($result)->toBeTrue();
207    expect($collection)->toHaveCount(2);
208    expect($collection->values())->toBe(['foo', 'baz']);
209});
210
211test('Remove() returns false when element does not exist', function (): void {
212    $collection = createTestCollection(['foo', 'bar', 'baz']);
213    expect($collection)->toHaveCount(3);
214
215    $result = $collection->remove('qix');
216
217    expect($result)->toBeFalse();
218    expect($collection)->toHaveCount(3);
219    expect($collection->values())->toBe(['foo', 'bar', 'baz']);
220});
221
222test('Clear() empties the collection', function (): void {
223    $collection = createTestCollection(['foo', 'bar', 'baz']);
224    expect($collection)->toHaveCount(3);
225    expect($collection->isEmpty())->toBeFalse();
226    expect($collection->items())->not->toBeEmpty();
227
228    $collection->clear();
229
230    expect($collection)->toHaveCount(0);
231    expect($collection->isEmpty())->toBeTrue();
232    expect($collection->items())->toBeEmpty();
233});
234
235test('ArrayAccess (access to $collection as array)', function (): void {
236    $collection = createTestCollection([1, 2]);
237    expect($collection)->toHaveCount(2);
238
239    $collection[] = 9; // [1,2,9]
240    expect($collection)->toHaveCount(3);
241    expect($collection[2])->toBe(9);
242
243    $collection[2] = 3; // [1,2,3]
244    expect($collection)->toHaveCount(3);
245    expect($collection[2])->toBe(3);
246
247    unset($collection[1]); // [1,-,3]
248    expect($collection)->toHaveCount(2);
249
250    expect(isset($collection[0]))->toBeTrue();
251    expect(isset($collection[1]))->toBeFalse();
252    expect(isset($collection[2]))->toBeTrue();
253
254    expect($collection[0])->toBe(1);
255    expect($collection[1])->toBeNull();
256    expect($collection[2])->toBe(3);
257});
258
259test('Get() returns value at key or default', function (): void {
260    $collection = createTestCollection(['a' => 'foo', 'b' => 'bar']);
261
262    expect($collection->get('a'))->toBe('foo');
263    expect($collection->get('b'))->toBe('bar');
264    expect($collection->get('c'))->toBeNull();
265    expect($collection->get('c', 'default'))->toBe('default');
266});
267
268test('Keys() returns all keys', function (): void {
269    $collection = createTestCollection(['a' => 'foo', 'b' => 'bar', 'c' => 'baz']);
270
271    expect($collection->keys())->toBe(['a', 'b', 'c']);
272});
273
274test('Keys() returns numeric keys for indexed array', function (): void {
275    $collection = createTestCollection(['foo', 'bar', 'baz']);
276
277    expect($collection->keys())->toBe([0, 1, 2]);
278});
279
280test('ContainsKey() returns true when key exists', function (): void {
281    $collection = createTestCollection(['a' => 'foo', 'b' => 'bar']);
282
283    expect($collection->containsKey('a'))->toBeTrue();
284    expect($collection->containsKey('b'))->toBeTrue();
285});
286
287test('ContainsKey() returns false when key does not exist', function (): void {
288    $collection = createTestCollection(['a' => 'foo']);
289
290    expect($collection->containsKey('b'))->toBeFalse();
291    expect($collection->containsKey('c'))->toBeFalse();
292});
293
294test('Slice() returns a portion of the collection', function (): void {
295    $collection = createTestCollection(['a', 'b', 'c', 'd', 'e']);
296
297    $sliced = $collection->slice(1, 3);
298
299    expect($sliced)->toHaveCount(3);
300    expect($sliced->values())->toBe(['b', 'c', 'd']);
301});
302
303test('Slice() preserves keys', function (): void {
304    $collection = createTestCollection(['x' => 'a', 'y' => 'b', 'z' => 'c']);
305
306    $sliced = $collection->slice(1, 2);
307
308    expect($sliced->keys())->toBe(['y', 'z']);
309    expect($sliced->values())->toBe(['b', 'c']);
310});
311
312test('Slice() preserves numeric keys', function (): void {
313    $collection = createTestCollection(['a', 'b', 'c', 'd', 'e']);
314
315    $sliced = $collection->slice(1, 3);
316
317    expect($sliced->keys())->toBe([1, 2, 3]);
318    expect($sliced->values())->toBe(['b', 'c', 'd']);
319});
320
321test('Take() returns first N elements', function (): void {
322    $collection = createTestCollection([1, 2, 3, 4, 5]);
323
324    $taken = $collection->take(3);
325
326    expect($taken)->toHaveCount(3);
327    expect($taken->values())->toBe([1, 2, 3]);
328});
329
330test('Reverse() reverses the collection', function (): void {
331    $collection = createTestCollection(['a', 'b', 'c', 'd']);
332
333    $reversed = $collection->reverse();
334
335    expect($reversed->values())->toBe(['d', 'c', 'b', 'a']);
336});
337
338test('Reverse() preserves keys', function (): void {
339    $collection = createTestCollection(['x' => 'a', 'y' => 'b', 'z' => 'c']);
340
341    $reversed = $collection->reverse();
342
343    expect($reversed->keys())->toBe(['z', 'y', 'x']);
344    expect($reversed->values())->toBe(['c', 'b', 'a']);
345});
346
347test('Reverse() preserves numeric keys', function (): void {
348    $collection = createTestCollection(['a', 'b', 'c', 'd']);
349
350    $reversed = $collection->reverse();
351
352    expect($reversed->keys())->toBe([3, 2, 1, 0]);
353    expect($reversed->values())->toBe(['d', 'c', 'b', 'a']);
354});
355
356test('Sort() sorts collection using comparator', function (): void {
357    $collection = createTestCollection([5, 2, 8, 1, 9]);
358
359    $sorted = $collection->sort(fn ($a, $b): int => $a <=> $b);
360
361    expect($sorted->values())->toBe([1, 2, 5, 8, 9]);
362});
363
364test('Sort() can sort in descending order', function (): void {
365    $collection = createTestCollection([5, 2, 8, 1, 9]);
366
367    $sorted = $collection->sort(fn ($a, $b): int => $b <=> $a);
368
369    expect($sorted->values())->toBe([9, 8, 5, 2, 1]);
370});
371
372test('Unique() removes duplicate values', function (): void {
373    $collection = createTestCollection(['a', 'b', 'a', 'c', 'b', 'd']);
374
375    $unique = $collection->unique();
376
377    expect($unique)->toHaveCount(4);
378    expect($unique->values())->toBe(['a', 'b', 'c', 'd']);
379});
380
381test('Unique() works with numeric values', function (): void {
382    $collection = createTestCollection([1, 2, 3, 2, 4, 1, 5]);
383
384    $unique = $collection->unique();
385
386    expect($unique)->toHaveCount(5);
387    expect($unique->values())->toBe([1, 2, 3, 4, 5]);
388});