scratch – Blame information for rev 87

Subversion Repositories:
Rev:
Rev Author Line No. Line
87 office 1 <?php
2 /** Copyright (C) Fusonic GmbH (http://www.fusonic.net) 2013, All rights reserved. */
3  
4 use Fusonic\Linq\Linq;
5  
6 /**
7 * Interface for test purposes only.
8 */
9 interface StubInterface
10 {
11 }
12  
13 /**
14 * Class for test purposes only.
15 */
16 final class Stub
17 implements
18 StubInterface
19 {
20 }
21  
22 /**
23 * Class for test purposes only.
24 */
25 final class StubWithoutInterface
26 {
27 }
28  
29 class LinqTest extends PHPUnit_Framework_TestCase
30 {
31 const ExceptionName_UnexpectedValue = "UnexpectedValueException";
32 const ExceptionName_InvalidArgument = "InvalidArgumentException";
33 const ExceptionName_OutOfRange = "OutOfRangeException";
34 const ExceptionName_Runtime = "RuntimeException";
35  
36 /** Returns the only element of a sequence that satisfies a specified condition, and throws an exception if more than one such element exists.
37 */
38 public function testSingle_TestBehaviour()
39 {
40 // more than one
41 $items = array(1, 2);
42 $this->assertException(function() use ($items) {
43 Linq::from($items)->single();
44 });
45  
46 // no matching elements
47 $items = array();
48 $this->assertException(function() use ($items) {
49 Linq::from($items)->single();
50 });
51  
52 // OK
53 $items = array(77);
54 $this->assertSame(77, Linq::from($items)->single());
55  
56 // With closure
57  
58 // more than one
59 $items = array(1, 2);
60 $this->assertException(function() use ($items) {
61 Linq::from($items)->single(function($x) { return true; });
62 });
63  
64 // no matching elements
65 // because of false closure
66 $this->assertException(function() use ($items) {
67 Linq::from($items)->single(function($x) { return false; });
68 });
69  
70 // because of empty array
71 $items = array();
72 $this->assertException(function() use ($items) {
73 Linq::from($items)->single(function($x) { return true; });
74 });
75  
76 // OK
77 $items = array(77);
78 $this->assertSame(77, Linq::from($items)->single(function($x) { return true; }));
79 }
80  
81 public function testCount_ReturnsCorrectAmounts()
82 {
83 $items = array(1, 2);
84 $this->assertEquals(2, Linq::from($items)->count());
85  
86 $items = array(1, 2);
87 $this->assertEquals(1, Linq::from($items)->where(function($x) {
88 return $x == 2;
89 })->count());
90  
91 $items = array(1, 2);
92 $this->assertEquals(0, Linq::from($items)->where(function($x) {
93 return false;
94 })->count());
95  
96 $items = array();
97 $this->assertEquals(0, Linq::from($items)->count());
98 }
99  
100 /** Returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists;
101 * this method throws an exception if more than one element satisfies the condition.
102 */
103 public function testSingleOrNull_TestBehaviour()
104 {
105 // more than one
106 $items = array(1, 2);
107 $this->assertException(function() use ($items) {
108 Linq::from($items)->singleOrNull();
109 });
110  
111 // no matching elements
112 $items = array();
113 $this->assertNull(Linq::from($items)->singleOrNull());
114  
115 // OK
116 $items = array(77);
117 $this->assertSame(77, Linq::from($items)->singleOrNull());
118  
119 // With closure
120  
121 // more than one
122 $items = array(1, 2);
123 $this->assertException(function() use ($items) {
124 Linq::from($items)->singleOrNull(function($x) { return true; });
125 });
126  
127 // no matching elements
128 // because of false closure
129 $this->assertNull(Linq::from($items)->singleOrNull(function($x) { return false; }));
130  
131 // because of empty array
132 $items = array();
133 $this->assertNull(Linq::from($items)->singleOrNull());
134  
135 // OK
136 $items = array(77);
137 $this->assertSame(77, Linq::from($items)->singleOrNull(function($x) { return true; }));
138 }
139  
140 /** Returns the first element in a sequence that satisfies a specified condition.
141 *
142 * Exceptions:
143 * No element satisfies the condition in predicate.
144 -or-
145 The source sequence is empty.
146 */
147 public function testFirst_TestBehaviour()
148 {
149 $a = new stdClass();
150 $a->value = "a";
151  
152 $b1 = new stdClass();
153 $b1->value = "b";
154  
155 $b2 = new stdClass();
156 $b2->value = "b";
157  
158 $c = new stdClass();
159 $c->value = "c";
160  
161 // more than one
162 $items = array($a, $b1, $b2, $c);
163 $this->assertSame($a, Linq::from($items)->first());
164  
165 // no matching elements
166 $items = array();
167 $this->assertException(function() use ($items) {
168 Linq::from($items)->first();
169 });
170  
171 $items = array($a);
172 $this->assertSame($a, Linq::from($items)->first());
173  
174 // #### With closures ###
175  
176 // more than one
177 $items = array($a, $b1, $b2, $c);
178 $this->assertSame($b1, Linq::from($items)->first(function($x) { return $x->value == "b"; }));
179  
180 // no matching elements
181 // because of false closure
182 $this->assertException(function() use ($items) {
183 Linq::from($items)->first(function($x) { return false; });
184 });
185  
186 // because of empty array
187 $items = array();
188 $this->assertException(function() use ($items) {
189 Linq::from($items)->first(function($x) { return true; });
190 });
191  
192 // OK
193 $items = array($a);
194 $this->assertSame($a, Linq::from($items)->first(function($x) { return true; }));
195 }
196  
197 /**
198 * Returns the first element of a sequence, or a default value if the sequence contains no elements.
199 */
200 public function testFirstOrNull_DoesReturnTheFirstElement_OrNull_DoesNotThrowExceptions()
201 {
202 $a = new stdClass();
203 $a->value = "a";
204  
205 $b1 = new stdClass();
206 $b1->value = "b";
207  
208 $b2 = new stdClass();
209 $b2->value = "b";
210  
211 $c = new stdClass();
212 $c->value = "c";
213  
214 $items = array($a, $b1, $b2, $c);
215 $this->assertSame($a, Linq::from($items)->firstOrNull());
216  
217 $items = array();
218 $this->assertNull(Linq::from($items)->firstOrNull());
219  
220 // #### With closures ###
221  
222 $items = array($a, $b1, $b2, $c);
223 $this->assertSame($b1, Linq::from($items)->firstOrNull(function($x) { return $x->value == "b"; }));
224  
225 $items = array($a, $b1, $b2, $c);
226 $this->assertSame($c, Linq::from($items)->firstOrNull(function($x) { return $x->value == "c"; }));
227  
228 $items = array();
229 $this->assertNull(Linq::from($items)->firstOrNull(function($x) { return true; }));
230 }
231  
232  
233 public function testLast_DoesReturnTheLastElement_OrThrowsExceptions()
234 {
235 $a = new stdClass();
236 $a->value = "a";
237  
238 $b1 = new stdClass();
239 $b1->value = "b";
240  
241 $b2 = new stdClass();
242 $b2->value = "b";
243  
244 $c = new stdClass();
245 $c->value = "c";
246  
247 // more than one
248 $items = array($a, $b1, $b2, $c);
249 $last = Linq::from($items)->last();
250 $this->assertSame($c, $last);
251  
252 // no matching elements
253 $items = array();
254 $this->assertException(function() use ($items) {
255 Linq::from($items)->last();
256 });
257  
258 $items = array($a);
259 $this->assertSame($a, Linq::from($items)->last());
260  
261 // #### With closures ###
262  
263 // more than one
264 $items = array($a, $b1, $b2, $c);
265 $this->assertSame($b2, Linq::from($items)->last(function($x) { return $x->value == "b"; }));
266  
267 // no matching elements
268 // because of false closure
269 $this->assertException(function() use ($items) {
270 Linq::from($items)->last(function($x) { return false; });
271 });
272  
273 // because of empty array
274 $items = array();
275 $this->assertException(function() use ($items) {
276 Linq::from($items)->last(function($x) { return true; });
277 });
278  
279 // OK
280 $items = array($a);
281 $this->assertSame($a, Linq::from($items)->last(function($x) { return true; }));
282 }
283  
284 public function testLastOrDefault_DoesReturnTheLastElement_OrNull_DoesNotThrowExceptions()
285 {
286 $a = new stdClass();
287 $a->value = "a";
288  
289 $b1 = new stdClass();
290 $b1->value = "b";
291  
292 $b2 = new stdClass();
293 $b2->value = "b";
294  
295 $c = new stdClass();
296 $c->value = "c";
297  
298 $items = array($a, $b1, $b2, $c);
299 $this->assertSame($c, Linq::from($items)->lastOrNull());
300  
301 $items = array();
302 $this->assertNull(Linq::from($items)->lastOrNull());
303  
304 // #### With closures ###
305  
306 $items = array($a, $b1, $b2, $c);
307 $this->assertSame($c, Linq::from($items)->lastOrNull(function($x) { return true; }));
308  
309 $items = array($a, $b1, $b2, $c);
310 $this->assertSame($b2, Linq::from($items)->lastOrNull(function($x) { return $x->value == "b"; }));
311  
312 $items = array();
313 $this->assertNull(Linq::from($items)->lastOrNull(function($x) { return true; }));
314 }
315  
316 public function testWhere_ReturnsOnlyValuesMatching()
317 {
318 $a = new stdClass();
319 $a->value = "a";
320  
321 $b1 = new stdClass();
322 $b1->value = "b";
323  
324 $b2 = new stdClass();
325 $b2->value = "b";
326  
327 $c = new stdClass();
328 $c->value = "c";
329  
330 $items = array($a, $b1, $b2, $c);
331 $matching = Linq::from($items)->where(function ($v) { return false; });
332 $this->assertTrue($matching instanceof Linq);
333  
334 $matching = $matching->toArray();
335  
336 $this->assertEquals(0, count($matching));
337  
338 $matching = Linq::from($items)->where(function ($v) { return true; })->toArray();
339 $this->assertEquals(4, count($matching));
340 $this->assertTrue(in_array($a, (array)$matching));
341 $this->assertTrue(in_array($b1, (array)$matching));
342 $this->assertTrue(in_array($b2, (array)$matching));
343 $this->assertTrue(in_array($c, (array)$matching));
344  
345 $matching = Linq::from($items)->where(function ($v) { return $v->value == "b"; })->toArray();
346 $this->assertEquals(2, count($matching));
347 $this->assertFalse(in_array($a, (array)$matching));
348 $this->assertTrue(in_array($b1, (array)$matching));
349 $this->assertTrue(in_array($b2, (array)$matching));
350 $this->assertFalse(in_array($c, (array)$matching));
351 }
352  
353 public function testWhere_ThrowsExceptionIfPredicateDoesNotReturnABoolean()
354 {
355 $this->assertException(function()
356 {
357 $items = array("1", "2", "3");
358 $matching = Linq::from($items)->where(function ($v) { return "NOT A BOOLEAN"; });
359 $matching->toArray();
360 }, self::ExceptionName_UnexpectedValue);
361 }
362  
363 public function testWhere_DoesLazyEvaluation()
364 {
365 $eval = false;
366 $items = array("1", "2", "3");
367 $matching = Linq::from($items)->where(function ($v) use(&$eval)
368 {
369 $eval = true;
370 return true;
371 });
372  
373 $this->assertFalse($eval, "SelectMany did execute before iterating!");
374 $matching->toArray();
375 $this->assertTrue($eval);
376 }
377  
378 public function testWhere_EmptySequence_ReturnsEmptySequence()
379 {
380 $items = array();
381 $matching = Linq::from($items)->where(function ($v)
382 {
383 return true;
384 });
385  
386 $this->assertEquals(0, $matching->count());
387 $array = $matching->toArray();
388 $this->assertEquals(0, count($array));
389 }
390  
391 public function testCountable_implementedSqlInterface()
392 {
393 $items = array(1,2,3);
394  
395 $matching = Linq::from($items);
396  
397 $this->assertEquals(3, count($matching));
398 $this->assertEquals(3, $matching->count());
399 }
400  
401 public function testSkipWithTake_Combined_SkipAndTakeValuesByAmount()
402 {
403 $items = array("a", "b", "c", "d", "e", "f");
404 $matching = Linq::from($items)->skip(2)->take(0);
405 $this->assertEquals(0, $matching->count());
406  
407 $matching = Linq::from($items)->skip(0)->take(0);
408 $this->assertEquals(0, $matching->count());
409  
410 $matching = Linq::from($items)->skip(0)->take(2);
411 $this->assertEquals(2, $matching->count());
412 $array = $matching->toArray();
413 $this->assertEquals("a", $array[0]);
414 $this->assertEquals("b", $array[1]);
415  
416 $matching = Linq::from($items)->skip(2)->take(2);
417 $this->assertEquals(2, $matching->count());
418 $array = $matching->toArray();
419 $this->assertEquals("c", $array[0]);
420 $this->assertEquals("d", $array[1]);
421  
422 $matching = Linq::from($items)->skip(4)->take(99);
423 $this->assertEquals(2, $matching->count());
424 $array = $matching->toArray();
425 $this->assertEquals("e", $array[0]);
426 $this->assertEquals("f", $array[1]);
427 }
428  
429 public function testTakeSkip_Combined_TakeAndSkipValuesByAmount()
430 {
431 $items = array("a", "b", "c", "d", "e", "f");
432 $matching = Linq::from($items)->take(0)->skip(0);
433 $this->assertEquals(0, $matching->count());
434  
435 $matching = Linq::from($items)->take(2)->skip(2);
436 $this->assertEquals(0, $matching->count());
437  
438 $matching = Linq::from($items)->take(2)->skip(0);
439 $this->assertEquals(2, $matching->count());
440 $array = $matching->toArray();
441 $this->assertEquals("a", $array[0]);
442 $this->assertEquals("b", $array[1]);
443 }
444  
445 public function testSkip_SkipValuesByAmount()
446 {
447 $items = array("a", "b", "c", "d", "e", "f");
448 $matching = Linq::from($items)->skip(2);
449 $this->assertTrue($matching instanceof Linq);
450  
451 $this->assertEquals(4, $matching->count());
452 $matching = $matching->toArray();
453  
454 $this->assertTrue(in_array("c", $matching));
455 $this->assertTrue(in_array("d", $matching));
456 $this->assertTrue(in_array("e", $matching));
457 $this->assertTrue(in_array("f", $matching));
458  
459 $items = array("a", "b", "c", "d", "e", "f");
460  
461 $matching = Linq::from($items)->skip(0);
462 $this->assertEquals(6, $matching->count());
463 $array = $matching->toArray();
464 $this->assertEquals("a", $array[0]);
465 $this->assertEquals("b", $array[1]);
466 $this->assertEquals("c", $array[2]);
467 $this->assertEquals("d", $array[3]);
468 $this->assertEquals("e", $array[4]);
469 $this->assertEquals("f", $array[5]);
470  
471 $matching = Linq::from($items)->skip(5);
472 $this->assertEquals(1, $matching->count());
473 $array = $matching->toArray();
474 $this->assertEquals("f", $array[0]);
475  
476 $matching = Linq::from($items)->skip(6);
477 $this->assertEquals(0, $matching->count());
478  
479 $matching = Linq::from($items)->skip(7);
480 $this->assertEquals(0, $matching->count());
481  
482 // Test against empty sequence:
483  
484 $matching = Linq::from(array())->skip(0);
485 $this->assertEquals(0, $matching->count());
486  
487 $matching = Linq::from(array())->skip(6);
488 $this->assertEquals(0, $matching->count());
489 }
490  
491 public function testTake_TakeValuesByAmount()
492 {
493 $items = array("a", "b", "c", "d", "e", "f");
494 $matching = Linq::from($items)->take(4);
495 $this->assertTrue($matching instanceof Linq);
496  
497 $this->assertEquals(4, $matching->count());
498 $matching = $matching->toArray();
499 $this->assertTrue(in_array("a", $matching));
500 $this->assertTrue(in_array("b", $matching));
501 $this->assertTrue(in_array("c", $matching));
502 $this->assertTrue(in_array("d", $matching));
503  
504 $matching = Linq::from($items)->take(0);
505 $this->assertEquals(0, $matching->count());
506 }
507  
508 public function testAll_WorksCorrectly()
509 {
510 // All must always return true on empty sequences:
511 $items = array();
512 $all = Linq::from($items)->all(function($v) { return true; });
513 $this->assertTrue($all);
514  
515 $all = Linq::from($items)->all(function($v) { return false; });
516 $this->assertTrue($all);
517  
518 // Test with values:
519 $items = array("a", "b");
520 $all = Linq::from($items)->all(function($v) { return $v == "a"; });
521 $this->assertFalse($all);
522  
523 $all = Linq::from($items)->all(function($v) { return $v == "a" || $v == "b"; });
524 $this->assertTrue($all);
525 }
526  
527 public function testAny_WithFunc_CorrectResults()
528 {
529 // Any must always return false on empty sequences:
530 $items = array();
531 $any = Linq::from($items)->any(function($v) { return true; });
532 $this->assertFalse($any);
533  
534 $any = Linq::from($items)->any(function($v) { return false; });
535 $this->assertFalse($any);
536  
537 // Test with values:
538 $items = array("a", "b");
539 $any = Linq::from($items)->any(function($v) { return $v == "not existing"; });
540 $this->assertFalse($any);
541  
542 $any = Linq::from($items)->any(function($v) { return $v == "a"; });
543 $this->assertTrue($any);
544 }
545  
546 public function testAny_WithoutFunc_CorrectResults()
547 {
548 $items = array();
549 $this->assertFalse(Linq::from($items)->any());
550  
551 $items = array("a");
552 $this->assertTrue(Linq::from($items)->any());
553  
554 $items = array("a", "b", "c");
555 $this->assertTrue(Linq::from($items)->any());
556 }
557  
558 public function testAverage_throwsExceptionIfClosureReturnsNotNumericValue()
559 {
560 $this->assertException(function() {
561 $items = array(2, new stdClass());
562 Linq::from($items)->average();
563 }, self::ExceptionName_UnexpectedValue);
564  
565 $this->assertException(function() {
566 $items = array(2, "no numeric value");
567 Linq::from($items)->average();
568 }, self::ExceptionName_UnexpectedValue);
569  
570 $this->assertException(function() {
571 $cls = new stdClass();
572 $cls->value = "no numeric value";
573 $items = array($cls);
574 Linq::from($items)->average(function($x) { return $x->value; });
575 }, self::ExceptionName_UnexpectedValue);
576 }
577  
578 public function testAverage_CalculatesCorrectAverage()
579 {
580 $items = array(2, 4, 6);
581 $avg = Linq::from($items)->average();
582 $this->assertEquals(4, $avg);
583  
584 $items = array(2.5, 2.5);
585 $avg = Linq::from($items)->average();
586 $this->assertEquals(2.5, $avg);
587  
588 $items = array(2, "4", "6");
589 $avg = Linq::from($items)->average();
590 $this->assertEquals(4, $avg);
591  
592 $items = array(2, 4, 6);
593 $avg = Linq::from($items)->average(function($v) { return 1; });
594 $this->assertEquals(1, $avg);
595  
596 $items = array(2.5, 2.5);
597 $avg = Linq::from($items)->average(function($v) { return $v; });
598 $this->assertEquals(2.5, $avg);
599  
600 $a = new stdClass();
601 $a->value = 2;
602  
603 $b = new stdClass();
604 $b->value = "4";
605  
606 $c = new stdClass();
607 $c->value = "6";
608  
609 $items = array($a, $b, $c);
610 $avg = Linq::from($items)->average(function($v) { return $v->value; });
611 $this->assertEquals(4, $avg);
612 }
613  
614 public function testOrderBy_NumericValues_WithSelector_ReturnsOrderedObjects()
615 {
616 $a = new stdClass(); $a->value = 77;
617 $b = new stdClass(); $b->value = 10;
618 $c = new stdClass(); $c->value = 20;
619 $items = array($a, $b, $c);
620  
621 $ascending = Linq::from($items)->orderBy(function($x)
622 {
623 return $x->value;
624 });
625 $this->assertEquals(3, $ascending->count());
626 $items = $ascending->toArray();
627  
628 $this->assertSame($b, $items[0]);
629 $this->assertSame($c, $items[1]);
630 $this->assertSame($a, $items[2]);
631  
632 $a = new stdClass(); $a->value = 77;
633 $b = new stdClass(); $b->value = 10.44;
634 $c = new stdClass(); $c->value = 20;
635 $d = new stdClass(); $d->value = 20;
636 $items = array($a, $b, $c, $d);
637  
638 $ascending = Linq::from($items)->orderBy(function($x) { return $x->value; });
639 $this->assertEquals(4, $ascending->count());
640 $items = $ascending->toArray();
641  
642 $this->assertSame($b, $items[0]);
643  
644 // It is not predictable in which order objects with the same order key are ordered, both positions are valid:
645 $pos1 = $items[1];
646 $pos2 = $items[2];
647 $this->assertTrue($pos1 === $d || $pos1 === $c);
648 $this->assertTrue($pos1 === $d || $pos1 === $c);
649 $this->assertNotsame($pos1, $pos2);
650  
651 $this->assertSame($a, $items[3]);
652 }
653  
654 public function testOrderBy_NumericValues_ReturnsCorrectOrders()
655 {
656 $items = array(77, 10, 20);
657 $ascending = Linq::from($items)->orderBy(function($x) { return $x; });
658 $this->assertTrue($ascending instanceof Linq);
659  
660 $ascending = $ascending->toArray();
661  
662 $this->assertEquals(10, $ascending[0]);
663 $this->assertEquals(20, $ascending[1]);
664 $this->assertEquals(77, $ascending[2]);
665  
666 // Verify that original collection is unmodified:
667 $this->assertEquals(77, $items[0]);
668 $this->assertEquals(10, $items[1]);
669 $this->assertEquals(20, $items[2]);
670  
671 $items = array(12.33, 8.21, 11.3, 8.21, 33);
672 $ascending = Linq::from($items)->orderBy(function($x) { return $x; });
673  
674 $ascending = $ascending->toArray();
675 $this->assertEquals(8.21, $ascending[0]);
676 $this->assertEquals(8.21, $ascending[1]);
677 $this->assertEquals(11.3, $ascending[2]);
678 $this->assertEquals(12.33, $ascending[3]);
679 $this->assertEquals(33, $ascending[4]);
680 }
681  
682 public function testOrderBy_StringValues_ReturnsCorrectOrders()
683 {
684 $items = array("e", "a", "c");
685 $ascending = Linq::from($items)->orderBy(function($x) { return $x; });
686 $this->assertTrue($ascending instanceof Linq);
687  
688 $ascending = $ascending->toArray();
689 $this->assertEquals("a", $ascending[0]);
690 $this->assertEquals("c", $ascending[1]);
691 $this->assertEquals("e", $ascending[2]);
692  
693 // Verify that original collection is unmodified:
694 $this->assertEquals("e", $items[0]);
695 $this->assertEquals("a", $items[1]);
696 $this->assertEquals("c", $items[2]);
697 }
698  
699 public function testOrderBy_DateTimeValues_ReturnsCorrectOrders()
700 {
701 $items = array(new DateTime("27.10.2011"), new DateTime("03.04.2012"), new DateTime("01.01.2005"));
702 $ascending = Linq::from($items)->orderBy(function($x) { return $x; });
703 $this->assertTrue($ascending instanceof Linq);
704 $ascending = $ascending->toArray();
705  
706 $this->assertEquals(new DateTime("01.01.2005"), $ascending[0]);
707 $this->assertEquals(new DateTime("27.10.2011"), $ascending[1]);
708 $this->assertEquals(new DateTime("03.04.2012"), $ascending[2]);
709  
710 // Verify that original collection is unmodified:
711 $this->assertEquals(new DateTime("27.10.2011"), $items[0]);
712 $this->assertEquals(new DateTime("03.04.2012"), $items[1]);
713 $this->assertEquals(new DateTime("01.01.2005"), $items[2]);
714 }
715  
716 public function testOrderByDescending_NumericValues_ReturnsCorrectOrders()
717 {
718 $items = array(77, 10, 20);
719 $desc = Linq::from($items)->orderByDescending(function($x) { return $x; });
720 $this->assertTrue($desc instanceof Linq);
721 $desc = $desc->toArray();
722  
723 $this->assertEquals(77, $desc[0]);
724 $this->assertEquals(20, $desc[1]);
725 $this->assertEquals(10, $desc[2]);
726  
727 // Verify that original collection is unmodified:
728 $this->assertEquals(77, $items[0]);
729 $this->assertEquals(10, $items[1]);
730 $this->assertEquals(20, $items[2]);
731  
732 $items = array(12.33, 8.21, 11.3, 8.21, 33);
733 $desc = Linq::from($items)->orderByDescending(function($x) { return $x; });
734 $desc = $desc->toArray();
735 $this->assertEquals(33, $desc[0]);
736 $this->assertEquals(12.33, $desc[1]);
737 $this->assertEquals(11.3, $desc[2]);
738 $this->assertEquals(8.21, $desc[3]);
739 $this->assertEquals(8.21, $desc[4]);
740 }
741  
742 public function testOrderByDescending_NumericValues_WithSelector_ReturnsOrderedObjects()
743 {
744 $a = new stdClass(); $a->value = 77;
745 $b = new stdClass(); $b->value = 10;
746 $c = new stdClass(); $c->value = 20;
747 $items = array($a, $b, $c);
748  
749 $ascending = Linq::from($items)->orderByDescending(function($x) { return $x->value; });
750 $this->assertEquals(3, $ascending->count());
751 $items = $ascending->toArray();
752  
753 $this->assertSame($a, $items[0]);
754 $this->assertSame($c, $items[1]);
755 $this->assertSame($b, $items[2]);
756  
757 $a = new stdClass(); $a->value = 77;
758 $b = new stdClass(); $b->value = 10.44;
759 $c = new stdClass(); $c->value = 20;
760 $d = new stdClass(); $d->value = 20;
761 $items = array($a, $b, $c, $d);
762  
763 $ascending = Linq::from($items)->orderByDescending(function($x) { return $x->value; });
764 $this->assertEquals(4, $ascending->count());
765 $items = $ascending->toArray();
766  
767 $this->assertSame($a, $items[0]);
768 $this->assertSame($c, $items[1]);
769  
770 // It is not predictable in which order objects with the same order key are ordered, both positions are valid:
771 $pos1 = $items[2];
772 $pos2 = $items[3];
773 $this->assertTrue($pos1 === $d || $pos1 === $c);
774 $this->assertTrue($pos1 === $d || $pos1 === $c);
775 $this->assertNotsame($pos1, $pos2);
776 }
777  
778 public function testOrderByDescending_DateTimeValues_ReturnsCorrectOrders()
779 {
780 $items = array(new DateTime("27.10.2011"), new DateTime("03.04.2012"), new DateTime("01.01.2005"));
781 $desc = Linq::from($items)->orderByDescending(function($x) { return $x; });
782 $this->assertTrue($desc instanceof Linq);
783 $desc = $desc->toArray();
784  
785 $this->assertEquals(new DateTime("03.04.2012"), $desc[0]);
786 $this->assertEquals(new DateTime("27.10.2011"), $desc[1]);
787 $this->assertEquals(new DateTime("01.01.2005"), $desc[2]);
788  
789 // Verify that original collection is unmodified:
790 $this->assertEquals(new DateTime("27.10.2011"), $items[0]);
791 $this->assertEquals(new DateTime("03.04.2012"), $items[1]);
792 $this->assertEquals(new DateTime("01.01.2005"), $items[2]);
793 }
794  
795 public function testOrderBy_Descending_StringValues_ReturnsCorrectOrders()
796 {
797 $items = array("e", "a", "c");
798 $desc = Linq::from($items)->orderByDescending(function($x) { return $x; });
799 $this->assertTrue($desc instanceof Linq);
800 $desc = $desc->toArray();
801  
802 $this->assertEquals("e", $desc[0]);
803 $this->assertEquals("c", $desc[1]);
804 $this->assertEquals("a", $desc[2]);
805  
806 // Verify that original collection is unmodified:
807 $this->assertEquals("e", $items[0]);
808 $this->assertEquals("a", $items[1]);
809 $this->assertEquals("c", $items[2]);
810 }
811  
812 public function testOrderBy_DoesMakeLazyEvalution()
813 {
814 $items = array("e", "a", "c");
815 $eval = false;
816 $linq = Linq::from($items)->orderByDescending(function($x) use(&$eval)
817 {
818 $eval = true;
819 return $x;
820 });
821  
822 $this->assertFalse($eval, "OrderBy did execute before iterating!");
823 $result = $linq->toArray();
824 $this->assertTrue($eval);
825 }
826  
827 public function testGroupBy()
828 {
829 $a1 = new stdClass(); $a1->id = 1; $a1->value = "a";
830 $a2 = new stdClass(); $a2->id = 2; $a2->value = "a";
831 $b1 = new stdClass(); $b1->id = 3; $b1->value = "b";
832  
833 $items = array ($a1, $a2, $b1);
834 $grouped = Linq::from($items)->groupBy(function($x) {
835 return $x->value;
836 });
837  
838 $this->assertTrue($grouped instanceof Linq);
839  
840 $this->assertEquals(2, $grouped->count());
841 $aGroup = $grouped->elementAt(0);
842 $this->assertTrue($aGroup instanceof Fusonic\Linq\GroupedLinq);
843  
844 $this->assertEquals("a", $aGroup->key());
845 $this->assertEquals(2, $aGroup->count());
846 $this->assertSame($a1, $aGroup->elementAt(0));
847 $this->assertSame($a2, $aGroup->elementAt(1));
848  
849 $bGroup = $grouped->elementAt(1);
850 $this->assertEquals("b", $bGroup->key());
851 $this->assertEquals(1, $bGroup->count());
852 $this->assertSame($b1, $bGroup->elementAt(0));
853 }
854  
855 public function testElementAt_ReturnsElementAtPositionOrThrowsException()
856 {
857 $items = array ("a", "b", "c");
858 $this->assertEquals("a", Linq::from($items)->elementAt(0));
859 $this->assertEquals("b", Linq::from($items)->elementAt(1));
860 $this->assertEquals("c", Linq::from($items)->elementAt(2));
861  
862 $items = array("a" => "aValue", "b" => "bValue");
863 $this->assertEquals("aValue", Linq::from($items)->elementAt(0));
864 $this->assertEquals("bValue", Linq::from($items)->elementAt(1));
865  
866 $this->assertException(function() {
867 $items = array();
868 Linq::from($items)->elementAt(0);
869 }, self::ExceptionName_OutOfRange);
870  
871 $this->assertException(function() {
872 $items = array();
873 Linq::from($items)->elementAt(1);
874 }, self::ExceptionName_OutOfRange);
875  
876 $this->assertException(function() {
877 $items = array();
878 Linq::from($items)->elementAt(-1);
879 }, self::ExceptionName_OutOfRange);
880  
881 $this->assertException(function() {
882 $items = array("a");
883 Linq::from($items)->elementAt(1);
884 }, self::ExceptionName_OutOfRange);
885  
886 $this->assertException(function() {
887 $items = array("a", "b");
888 Linq::from($items)->elementAt(2);
889 }, self::ExceptionName_OutOfRange);
890  
891 $this->assertException(function() {
892 $items = array("a", "b");
893 Linq::from($items)->elementAt(-1);
894 }, self::ExceptionName_OutOfRange);
895  
896 $this->assertException(function() {
897 $items = array("a" => "value", "b" => "bValue");
898 Linq::from($items)->elementAt(2);
899 }, self::ExceptionName_OutOfRange);
900 }
901  
902 public function testElementAtOrNull_ReturnsElementAtPositionOrNull()
903 {
904 $items = array ("a", "b", "c");
905 $this->assertEquals("a", Linq::from($items)->elementAtOrNull(0));
906 $this->assertEquals("b", Linq::from($items)->elementAtOrNull(1));
907 $this->assertEquals("c", Linq::from($items)->elementAtOrNull(2));
908  
909 $this->assertNull(Linq::from($items)->elementAtOrNull(3));
910 $this->assertNull(Linq::from($items)->elementAtOrNull(4));
911 $this->assertNull(Linq::from($items)->elementAtOrNull(-1));
912  
913 $items = array();
914 $this->assertNull(Linq::from($items)->elementAtOrNull(3));
915 $this->assertNull(Linq::from($items)->elementAtOrNull(4));
916 $this->assertNull(Linq::from($items)->elementAtOrNull(-1));
917  
918 $items = array("a" => "value", "b" => "bValue");
919 $this->assertEquals("value", Linq::from($items)->elementAtOrNull(0));
920 $this->assertNull(Linq::from($items)->elementAtOrNull(2));
921 }
922  
923 public function testSelect_ReturnsProjectedSequence()
924 {
925 $a1 = new stdClass(); $a1->value = "a1";
926 $a2 = new stdClass(); $a2->value = "a2";
927 $a3 = new stdClass(); $a3->value = "a3";
928 $a4 = new stdClass(); $a4->value = "a4";
929  
930 // more than one
931 $items = array($a1, $a2, $a3, $a4);
932  
933 $projected = Linq::from($items)->select(function($v){
934 return $v->value;
935 });
936  
937 $this->assertTrue($projected instanceof Linq);
938 $this->assertEquals(4, $projected->count());
939  
940 $projected = $projected->toArray();
941 $this->assertEquals("a1", $projected[0]);
942 $this->assertEquals("a2", $projected[1]);
943 $this->assertEquals("a3", $projected[2]);
944 $this->assertEquals("a4", $projected[3]);
945  
946 $items = array();
947  
948 $projected = Linq::from($items)->select(function($v){
949 return $v->value;
950 });
951  
952 $this->assertEquals(0, $projected->count());
953 }
954  
955 public function testSelectMany_throwsExceptionIfElementIsNotIterable()
956 {
957 $a1 = new stdClass(); $a1->value = "a1";
958 $items = array($a1);
959  
960 $this->assertException(function() use($items) {
961 Linq::from($items)->selectMany(function($v) {
962 return $v->value;
963 })->toArray();
964  
965 }, self::ExceptionName_UnexpectedValue);
966  
967 $this->assertException(function() use($items) {
968 Linq::from($items)->selectMany(function($v) {
969 return null;
970 })->toArray();
971 }, self::ExceptionName_UnexpectedValue);
972 }
973  
974 public function testSelectMany_ReturnsFlattenedSequence()
975 {
976 $a1 = new stdClass(); $a1->value = array("a", "b");
977 $a2 = new stdClass(); $a2->value = array("c", "d");
978 $items = array($a1, $a2);
979  
980 $linq = Linq::from($items)->selectMany(function($x)
981 {
982 return $x->value;
983 });
984  
985 $this->assertTrue($linq instanceof Linq);
986  
987 $this->assertEquals(4, $linq->count());
988  
989 $array = $linq->toArray();
990 $this->assertEquals("a", $array[0]);
991 $this->assertEquals("b", $array[1]);
992 $this->assertEquals("c", $array[2]);
993 $this->assertEquals("d", $array[3]);
994  
995 // Try once again to see if rewinding the iterator works:
996 $this->assertEquals(4, $linq->count());
997  
998 $array = $linq->toArray();
999 $this->assertEquals("a", $array[0]);
1000 $this->assertEquals("b", $array[1]);
1001 $this->assertEquals("c", $array[2]);
1002 $this->assertEquals("d", $array[3]);
1003 }
1004  
1005 public function testSelectMany_EmptySequence_ReturnsEmptySequence()
1006 {
1007 $linq = Linq::from(array())->selectMany(function($x)
1008 {
1009 return $x->value;
1010 });
1011  
1012 $this->assertEquals(0, $linq->count());
1013 $array = $linq->toArray();
1014 $this->assertEquals(0, count($array));
1015  
1016 // Try once again to see if rewinding the iterator works:
1017 $this->assertEquals(0, $linq->count());
1018 $array = $linq->toArray();
1019 $this->assertEquals(0, count($array));
1020 }
1021  
1022 public function testSelectMany_DoesLazyEvaluation()
1023 {
1024 $a1 = new stdClass(); $a1->value = array("a", "b");
1025 $a2 = new stdClass(); $a2->value = array("c", "d");
1026 $items = array($a1, $a2);
1027  
1028 $eval = false;
1029 $flattened = Linq::from($items)->selectMany(function($x) use(&$eval)
1030 {
1031 $eval = true;
1032 return $x->value;
1033 });
1034  
1035 $this->assertFalse($eval, "SelectMany did execute before iterating!");
1036 $result = $flattened->toArray();
1037 $this->assertTrue($eval);
1038 }
1039  
1040 public function testConcat_ReturnsConcatenatedElements()
1041 {
1042 $first = array("a", "b");
1043 $second = array("c", "d");
1044  
1045 $all = Linq::from($first)->concat($second);
1046 $this->assertTrue($all instanceof Linq);
1047  
1048 $this->assertEquals(4, $all->count());
1049  
1050 $all = $all->toArray();
1051 $this->assertEquals("a", $all[0]);
1052 $this->assertEquals("b", $all[1]);
1053 $this->assertEquals("c", $all[2]);
1054 $this->assertEquals("d", $all[3]);
1055 }
1056  
1057 public function testConcat_ThrowsArgumentExceptionIfNoTraversableArgument()
1058 {
1059 $this->assertException(function() {
1060 $input = array();
1061 $linq = Linq::from($input);
1062 $linq->concat(null);
1063 },self::ExceptionName_InvalidArgument);
1064  
1065 $this->assertException(function() {
1066 $input = array();
1067 $second = new stdClass();
1068 Linq::from($input)->concat($second);
1069 },self::ExceptionName_InvalidArgument);
1070 }
1071  
1072 public function testLinqFrom_WorksWith_Arrays_Iterators_And_IteratorAggregates()
1073 {
1074 $linq = Linq::from(array(1, 2));
1075 $linq = Linq::from($linq);
1076 $linq = Linq::from($linq->getIterator());
1077 }
1078  
1079 public function testMethodsWithSequencesAsArguments_WorkWith_Arrays_Iterators_And_IteratorAggregates()
1080 {
1081 $first = Linq::from(array("a", "b"));
1082 $secondArray = array("c", "d");
1083 $secondLinq = Linq::from(array("c", "d"));
1084 $secondIterator = $secondLinq->getIterator();
1085  
1086 $res = $first->concat($secondLinq)->toArray();
1087 $res = $first->intersect($secondLinq)->toArray();
1088 $res = $first->except($secondLinq)->toArray();
1089  
1090 $res = $first->concat($secondArray)->toArray();
1091 $res = $first->intersect($secondArray)->toArray();
1092 $res = $first->except($secondArray)->toArray();
1093  
1094 $res = $first->concat($secondIterator)->toArray();
1095 $res = $first->intersect($secondIterator)->toArray();
1096 $res = $first->except($secondIterator)->toArray();
1097 }
1098  
1099 public function testIntersect_ReturnsIntersectedElements()
1100 {
1101 $first = array("a", "b", "c", "d");
1102 $second = array("b", "c");
1103  
1104 $linq = Linq::from($first)->intersect($second);
1105 $this->assertEquals(2, $linq->count());
1106  
1107 $array = $linq->toArray();
1108 $this->assertEquals("b", $array[0]);
1109 $this->assertEquals("c", $array[1]);
1110 }
1111  
1112 public function testIntersect_ThrowsArgumentExceptionIfSecondSequenceIsNotTraversable()
1113 {
1114 $this->assertException(function() {
1115 $input = array();
1116 $linq = Linq::from($input);
1117 $linq->intersect(null);
1118 },self::ExceptionName_InvalidArgument);
1119  
1120 $this->assertException(function() {
1121 $input = array();
1122 $linq = Linq::from($input);
1123 $linq->intersect("Not a sequence");
1124 },self::ExceptionName_InvalidArgument);
1125 }
1126  
1127 public function testIntersect_EmptySequence_ReturnsEmptySequence()
1128 {
1129 $first = array("a", "b", "c", "d");
1130 $second = array();
1131  
1132 $linq = Linq::from($first)->intersect($second);
1133 $this->assertEquals(0, $linq->count());
1134 $array = $linq->toArray();
1135 $this->assertEquals(0, count($array));
1136 }
1137  
1138 public function testExcept_ReturnsAllElementsExceptTheGivenOnes()
1139 {
1140 $first = array("a", "b", "c", "d");
1141 $second = array("b", "c");
1142  
1143 $linq = Linq::from($first)->except($second);
1144 $this->assertEquals(2, $linq->count());
1145  
1146 $array = $linq->toArray();
1147 $this->assertEquals("a", $array[0]);
1148 $this->assertEquals("d", $array[1]);
1149 }
1150  
1151 public function testExcept_ThrowsArgumentExceptionIfSecondSequenceIsNotTraversable()
1152 {
1153 $this->assertException(function() {
1154 $input = array();
1155 $linq = Linq::from($input);
1156 $linq->except(null);
1157 },self::ExceptionName_InvalidArgument);
1158  
1159 $this->assertException(function() {
1160 $input = array();
1161 $linq = Linq::from($input);
1162 $linq->except("Not a sequence");
1163 },self::ExceptionName_InvalidArgument);
1164 }
1165  
1166 public function testExcept_EmptySequence_ReturnsAllElementsFromFirst()
1167 {
1168 $first = array("a", "b", "c", "d");
1169 $second = array();
1170  
1171 $linq = Linq::from($first)->except($second);
1172 $this->assertEquals(4, $linq->count());
1173  
1174 $array = $linq->toArray();
1175 $this->assertEquals("a", $array[0]);
1176 $this->assertEquals("b", $array[1]);
1177 $this->assertEquals("c", $array[2]);
1178 $this->assertEquals("d", $array[3]);
1179 }
1180  
1181 public function testDistinct_ReturnsDistinctElements()
1182 {
1183 $items = array("a", "b", "a", "b");
1184  
1185 $distinct = Linq::from($items)->distinct();
1186 $this->assertTrue($distinct instanceof Linq);
1187  
1188 $this->assertEquals(2, $distinct->count());
1189 $distinct = $distinct->toArray();
1190 $this->assertEquals("a", $distinct[0]);
1191 $this->assertEquals("b", $distinct[1]);
1192  
1193 $a1 = new stdClass(); $a1->id = 1; $a1->value = "a";
1194 $a2 = new stdClass(); $a2->id = 2; $a2->value = "a";
1195 $b1 = new stdClass(); $b1->id = 3; $b1->value = "b";
1196  
1197 $items = array($a1, $a2, $b1);
1198 $distinct = Linq::from($items)->distinct(function($v) { return $v->value; });
1199 $this->assertEquals(2, $distinct->count());
1200 }
1201  
1202 public function testDistinct_DoesLazyEvaluation()
1203 {
1204 $eval = false;
1205 $a1 = new stdClass(); $a1->id = 1; $a1->value = "a";
1206 $a2 = new stdClass(); $a2->id = 2; $a2->value = "a";
1207  
1208 $items = array($a1, $a2);
1209 $distinct = Linq::from($items)->distinct(function($v) use(&$eval)
1210 {
1211 $eval = true;
1212 return $v->value;
1213 });
1214  
1215 $this->assertFalse($eval, "SelectMany did execute before iterating!");
1216 $distinct->toArray();
1217 $this->assertTrue($eval);
1218 }
1219  
1220 public function testMin_ReturnsMinValueFromNumerics()
1221 {
1222 $items = array(88, 77, 12, 112);
1223 $min = Linq::from($items)->min();
1224 $this->assertEquals(12, $min);
1225  
1226 $items = array(13);
1227 $min = Linq::from($items)->min();
1228 $this->assertEquals(13, $min);
1229  
1230 $items = array(0);
1231 $min = Linq::from($items)->min();
1232 $this->assertEquals(0, $min);
1233  
1234 $items = array(-12);
1235 $min = Linq::from($items)->min();
1236 $this->assertEquals(-12, $min);
1237  
1238 $items = array(-12, 0, 100, -33);
1239 $min = Linq::from($items)->min();
1240 $this->assertEquals(-33, $min);
1241 }
1242  
1243 public function testMin_ReturnsMinValueFromStrings()
1244 {
1245 $items = array("c", "a", "b", "d");
1246 $min = Linq::from($items)->min();
1247 $this->assertEquals("a", $min);
1248  
1249 $items = array("a");
1250 $min = Linq::from($items)->min();
1251 $this->assertEquals("a", $min);
1252 }
1253  
1254 public function testMin_ReturnsMinValueFromDateTimes()
1255 {
1256 $items = array(
1257 new DateTime("2015-01-01 10:00:00"),
1258 new DateTime("2015-02-01 10:00:00"),
1259 new DateTime("2015-01-01 09:00:00"),
1260 );
1261 $min = Linq::from($items)->min();
1262 $this->assertEquals($items[2], $min);
1263 }
1264  
1265 public function testMin_ThrowsExceptionIfSequenceIsEmpty()
1266 {
1267 $this->assertException(function()
1268 {
1269 $data = array();
1270 $min = Linq::from($data)->min();
1271 });
1272 }
1273  
1274 public function testMin_ThrowsExceptionIfSequenceContainsNoneNumericValuesOrStrings()
1275 {
1276 $this->assertException(function()
1277 {
1278 $data = array(null);
1279 $max = Linq::from($data)->min();
1280 }, self::ExceptionName_UnexpectedValue);
1281  
1282 $this->assertException(function()
1283 {
1284 $data = array(new stdClass());
1285 $min = Linq::from($data)->min();
1286 }, self::ExceptionName_UnexpectedValue);
1287  
1288 $this->assertException(function()
1289 {
1290 $data = array("string value", 1, new stdClass());
1291 $min = Linq::from($data)->min();
1292 }, self::ExceptionName_UnexpectedValue);
1293  
1294 $this->assertException(function()
1295 {
1296 $a = new stdClass(); $a->nonNumeric = new stdClass();
1297 $data = array($a);
1298 $min = Linq::from($data)->min(function($x)
1299 {
1300 return $x->nonNumeric;
1301 });
1302 }, self::ExceptionName_UnexpectedValue);
1303 }
1304  
1305 public function testMax_ReturnsMaxValueFromNumerics()
1306 {
1307 $items = array(88, 77, 12, 112);
1308 $max = Linq::from($items)->max();
1309 $this->assertEquals(112, $max);
1310  
1311 $items = array(13);
1312 $max = Linq::from($items)->max();
1313 $this->assertEquals(13, $max);
1314  
1315 $items = array(0);
1316 $max = Linq::from($items)->max();
1317 $this->assertEquals(0, $max);
1318  
1319 $items = array(-12);
1320 $max = Linq::from($items)->max();
1321 $this->assertEquals(-12, $max);
1322  
1323 $items = array(-12, 0, 100, -33);
1324 $max = Linq::from($items)->max();
1325 $this->assertEquals(100, $max);
1326 }
1327  
1328 public function testMax_ReturnsMaxValueFromDateTimes()
1329 {
1330 $items = array(
1331 new DateTime("2015-01-01 10:00:00"),
1332 new DateTime("2015-02-01 10:00:00"),
1333 new DateTime("2015-01-01 09:00:00"),
1334 );
1335 $max = Linq::from($items)->max();
1336 $this->assertEquals($items[1], $max);
1337 }
1338  
1339 public function testSum_ThrowsExceptionIfSequenceContainsNoneNumericValues()
1340 {
1341 $this->assertException(function()
1342 {
1343 $data = array(null);
1344 $max = Linq::from($data)->sum();
1345 }, self::ExceptionName_UnexpectedValue);
1346  
1347 $this->assertException(function()
1348 {
1349 $data = array(new stdClass());
1350 $min = Linq::from($data)->sum();
1351 }, self::ExceptionName_UnexpectedValue);
1352  
1353 $this->assertException(function()
1354 {
1355 $data = array("string value", 1, new stdClass());
1356 $min = Linq::from($data)->sum();
1357 }, self::ExceptionName_UnexpectedValue);
1358  
1359 $this->assertException(function()
1360 {
1361 $a = new stdClass(); $a->value = 100; $a->nonNumeric = "asdf";
1362 $b = new stdClass(); $b-> value = 133; $a->nonNumeric = "asdf";
1363  
1364 $data = array($a, $b);
1365 $sum = Linq::from($data)->sum(function($x) {
1366 return $x->nonNumeric;
1367 });
1368 }, self::ExceptionName_UnexpectedValue);
1369 }
1370  
1371 public function testSum_GetSumOfValues()
1372 {
1373 $data = array();
1374 $sum = Linq::from($data)->sum();
1375 $this->assertEquals(0, $sum);
1376  
1377 $data = array(4, 9, 100.77);
1378 $sum = Linq::from($data)->sum();
1379 $this->assertEquals(113.77, $sum);
1380  
1381 $data = array(12, -12);
1382 $sum = Linq::from($data)->sum();
1383 $this->assertEquals(0, $sum);
1384  
1385 $data = array(12, -24);
1386 $sum = Linq::from($data)->sum();
1387 $this->assertEquals(-12, $sum);
1388  
1389 $a = new stdClass(); $a->value = 100;
1390 $b = new stdClass(); $b-> value = 133;
1391  
1392 $data = array($a, $b);
1393 $sum = Linq::from($data)->sum(function($x) {
1394 return $x->value;
1395 });
1396  
1397 $this->assertEquals(233, $sum);
1398 }
1399  
1400 public function testMax_ReturnsMaxValueFromStrings()
1401 {
1402 $items = array("c", "a", "b", "d");
1403 $max = Linq::from($items)->max();
1404 $this->assertEquals("d", $max);
1405  
1406 $items = array("a");
1407 $max = Linq::from($items)->max();
1408 $this->assertEquals("a", $max);
1409 }
1410  
1411 public function testMax_ThrowsExceptionIfSequenceIsEmpty()
1412 {
1413 $this->assertException(function()
1414 {
1415 $data = array();
1416 $max = Linq::from($data)->max();
1417 });
1418 }
1419  
1420 public function testMax_ThrowsExceptionIfSequenceContainsNoneNumericValuesOrStrings()
1421 {
1422 $this->assertException(function()
1423 {
1424 $data = array(new stdClass());
1425 $max = Linq::from($data)->max();
1426 }, self::ExceptionName_UnexpectedValue);
1427  
1428 $this->assertException(function()
1429 {
1430 $data = array(null);
1431 $max = Linq::from($data)->max();
1432 }, self::ExceptionName_UnexpectedValue);
1433  
1434 $this->assertException(function()
1435 {
1436 $data = array("string value", 1, new stdClass());
1437 $max = Linq::from($data)->max();
1438 }, self::ExceptionName_UnexpectedValue);
1439  
1440 $this->assertException(function()
1441 {
1442 $a = new stdClass(); $a->nonNumeric = new stdClass();
1443 $data = array($a);
1444 $min = Linq::from($data)->max(function($x)
1445 {
1446 return $x->nonNumeric;
1447 });
1448 }, self::ExceptionName_UnexpectedValue);
1449 }
1450  
1451 public function testEach_PerformsActionOnEachElement()
1452 {
1453 $items = array("a", "b", "c");
1454 $looped = array();
1455 Linq::from($items)
1456 ->each(function($x) use(&$looped)
1457 {
1458 $looped[] = $x;
1459 });
1460  
1461 $this->assertEquals(3, count($looped));
1462 $this->assertEquals("a", $looped[0]);
1463 $this->assertEquals("b", $looped[1]);
1464 $this->assertEquals("c", $looped[2]);
1465 }
1466  
1467 public function testEach_ReturnsOriginalLinqSequence()
1468 {
1469 $linq = Linq::from(array(1,2,3,4))
1470 ->skip(2)->take(1);
1471  
1472 $linqAfterEach = $linq->each(function($x) {});
1473 $this->assertSame($linq, $linqAfterEach);
1474 }
1475  
1476 public function testToArray_WithoutKeySelector_ReturnsIteratorValuesAsArray_UsesDefaultNumericArrayKeys()
1477 {
1478 $linq = Linq::from(array("a", "b", "c"))
1479 ->skip(1)->take(3);
1480  
1481 $array = $linq->toArray();
1482 $this->assertTrue(is_array($array));
1483 $this->assertEquals(2, count($array));
1484  
1485 $keys = array_keys($array);
1486 $this->assertEquals(0, $keys[0]);
1487 $this->assertEquals(1, $keys[1]);
1488  
1489 $this->assertEquals("b", $array[0]);
1490 $this->assertEquals("c", $array[1]);
1491 }
1492  
1493 public function testToArray_WithKeySelector_ReturnsIteratorValuesAsArray_UsesKeySelectorValueAsKey()
1494 {
1495 $linq = Linq::from(array("a", "b", "c"))
1496 ->skip(1)->take(3);
1497  
1498 $array = $linq->toArray(function($x) {
1499 return "keyprefix_" . $x;
1500 });
1501  
1502 $this->assertTrue(is_array($array));
1503 $this->assertEquals(2, count($array));
1504  
1505 $keys = array_keys($array);
1506 $this->assertEquals("keyprefix_b", $keys[0]);
1507 $this->assertEquals("keyprefix_c", $keys[1]);
1508  
1509 $this->assertEquals("b", $array["keyprefix_b"]);
1510 $this->assertEquals("c", $array["keyprefix_c"]);
1511 }
1512  
1513 public function testToArray_WithKeyAndValueSelector_ReturnsArrayWithKeyValueSetsFromClosures()
1514 {
1515 $source = array(
1516 array("catId" => 11, "name" => "Category11", "additionalcolumn" => "foo"),
1517 array("catId" => 12, "name" => "Category12", "additionalcolumn" => "bar"),
1518 );
1519 $linq = Linq::from($source);
1520  
1521 $array = $linq->toArray(function($x) {
1522 return $x["catId"];
1523 }, function($y) {
1524 return $y["name"];
1525 });
1526  
1527 $this->assertTrue(is_array($array));
1528 $this->assertEquals(2, count($array));
1529  
1530 $keys = array_keys($array);
1531 $this->assertEquals(11, $keys[0]);
1532 $this->assertEquals(12, $keys[1]);
1533  
1534 $this->assertEquals("Category11", $array[11]);
1535 $this->assertEquals("Category12", $array[12]);
1536 }
1537  
1538 public function testToArray_WithValueSelector_ReturnsArrayWithDefaultNumericKey_AndValueFromClosure()
1539 {
1540 $source = array(
1541 array("catId" => 11, "name" => "Category11", "additionalcolumn" => "foo"),
1542 array("catId" => 12, "name" => "Category12", "additionalcolumn" => "bar"),
1543 );
1544  
1545 $linq = Linq::from($source);
1546  
1547 $array = $linq->toArray(null, function($y) {
1548 return $y["additionalcolumn"];
1549 });
1550  
1551 $this->assertTrue(is_array($array));
1552 $this->assertEquals(2, count($array));
1553  
1554 $keys = array_keys($array);
1555 $this->assertEquals(0, $keys[0]);
1556 $this->assertEquals(1, $keys[1]);
1557  
1558 $this->assertEquals("foo", $array[0]);
1559 $this->assertEquals("bar", $array[1]);
1560 }
1561  
1562 public function testAggregate_novalues_throwsException()
1563 {
1564 $this->assertException(function() {
1565  
1566 Linq::from(array())->aggregate(function() {});
1567 }, self::ExceptionName_Runtime);
1568  
1569  
1570 $this->assertException(function() {
1571  
1572 Linq::from(array())->aggregate(function() {}, null);
1573 }, self::ExceptionName_Runtime);
1574 }
1575  
1576 public function testAggregate_returnsCorrectResult()
1577 {
1578 $this->assertEquals("value", Linq::from(array("value"))->aggregate(function($a, $b) { throw new Exception("Must not becalled"); }));
1579 $this->assertEquals(2, Linq::from(array(2))->aggregate(function($a, $b) { throw new Exception("Must not becalled"); }));
1580 $this->assertEquals(5, Linq::from(array(2, 3))->aggregate(function($a, $b) { return $a + $b; }));
1581 $this->assertEquals(17, Linq::from(array(2, 3, 3, 4, 5))->aggregate(function($a, $b) { return $a + $b; }));
1582 $this->assertEquals("abcde", Linq::from(array("a","b","c","d","e"))->aggregate(function($a, $b) { return $a . $b; }));
1583 }
1584  
1585 public function testAggregate_withSeedValue_returnsCorrectResult()
1586 {
1587 $this->assertEquals(9999, Linq::from(array())->aggregate(function() {}, 9999));
1588 $this->assertEquals(104, Linq::from(array(2))->aggregate(function($a, $b) { return $a + $b; }, 102));
1589 $this->assertEquals(137, Linq::from(array(2, 2, 20, 11))->aggregate(function($a, $b) { return $a + $b; }, 102));
1590 $this->assertEquals("begin_abcde", Linq::from(array("a","b","c","d","e"))->aggregate(function($a, $b) { return $a . $b; }, "begin_"));
1591 }
1592  
1593 public function testRange_throwsExceptionIfCountIsNegative()
1594 {
1595 $this->assertException(function() {
1596  
1597 Linq::range(0, -1);
1598 }, self::ExceptionName_OutOfRange);
1599 }
1600  
1601 public function testRange_returnsRangeOfIntegers()
1602 {
1603 $range = Linq::range(0, 3)->toArray();
1604 $this->assertEquals(3, count($range));
1605 $this->assertEquals(0, $range[0]);
1606 $this->assertEquals(1, $range[1]);
1607 $this->assertEquals(2, $range[2]);
1608  
1609 $range = Linq::range(6, 3)->toArray();
1610 $this->assertEquals(3, count($range));
1611 $this->assertEquals(6, $range[0]);
1612 $this->assertEquals(7, $range[1]);
1613 $this->assertEquals(8, $range[2]);
1614  
1615 $range = Linq::range(-3, 5)->toArray();
1616 $this->assertEquals(5, count($range));
1617 $this->assertEquals(-3, $range[0]);
1618 $this->assertEquals(-2, $range[1]);
1619 $this->assertEquals(-1, $range[2]);
1620 $this->assertEquals(0, $range[3]);
1621 $this->assertEquals(1, $range[4]);
1622 }
1623  
1624 public function testContains_defaultComparison()
1625 {
1626 $items = array("2", 2);
1627 $linq = Linq::from($items);
1628 $this->assertTrue($linq->contains(2));
1629 $this->assertTrue($linq->contains("2"));
1630 $this->assertFalse($linq->contains(true));
1631  
1632 $this->assertFalse($linq->contains(3));
1633 $this->assertFalse($linq->contains("3"));
1634  
1635 $this->assertFalse($linq->contains(3));
1636 $this->assertFalse($linq->contains(null));
1637  
1638 $a = new stdClass();
1639 $b = new stdClass();
1640 $c = new stdClass();
1641 $linq = Linq::from(array($a, $b));
1642 $this->assertTrue($linq->contains($a));
1643 $this->assertTrue($linq->contains($b));
1644 $this->assertFalse($linq->contains($c));
1645 }
1646  
1647 public function testChunk_throwsException_IfchunksizeIsInvalid()
1648 {
1649 $this->assertException(function() {
1650 Linq::from(array())->chunk(0);
1651 }, self::ExceptionName_InvalidArgument);
1652  
1653 $this->assertException(function() {
1654 Linq::from(array())->chunk(-1);
1655 }, self::ExceptionName_InvalidArgument);
1656 }
1657  
1658 public function testChunk_ReturnsChunkedElementsAccordingToChunksize()
1659 {
1660 $groups = Linq::from(array())->chunk(2);
1661 $this->assertEquals(0, $groups->count());
1662  
1663 $groups = Linq::from(array("a"))->chunk(2);
1664 $this->assertEquals(1, $groups->count());
1665 $this->assertEquals(1, $groups->ElementAt(0)->count());
1666 $this->assertEquals("a", $groups->ElementAt(0)->ElementAt(0));
1667  
1668 $groups = Linq::from(array("a","b","c","d","e"))->chunk(2);
1669 $this->assertEquals(3, $groups->count());
1670 $this->assertEquals(2, $groups->ElementAt(0)->count());
1671 $this->assertEquals("a", $groups->ElementAt(0)->ElementAt(0));
1672 $this->assertEquals("b", $groups->ElementAt(0)->ElementAt(1));
1673  
1674 $this->assertEquals(2, $groups->ElementAt(1)->count());
1675 $this->assertEquals("c", $groups->ElementAt(1)->ElementAt(0));
1676 $this->assertEquals("d", $groups->ElementAt(1)->ElementAt(1));
1677  
1678 $this->assertEquals(1, $groups->ElementAt(2)->count());
1679 $this->assertEquals("e", $groups->ElementAt(2)->ElementAt(0));
1680  
1681 $groups = Linq::from(array("a","b","c","d","e"))->chunk(3);
1682 $this->assertEquals(2, $groups->count());
1683  
1684 $groups = Linq::from(array("a","b","c","d","e"))->chunk(4);
1685 $this->assertEquals(2, $groups->count());
1686  
1687 $groups = Linq::from(array("a","b","c","d","e"))->chunk(5);
1688 $this->assertEquals(1, $groups->count());
1689  
1690 $groups = Linq::from(array("a","b","c","d","e"))->chunk(117);
1691 $this->assertEquals(1, $groups->count());
1692 }
1693  
1694 public function testIssue3_emtpyCollectionOrdering()
1695 {
1696 Linq::from(array())
1697 ->orderBy(function(array $x) { return $x["name"]; })
1698 ->toArray();
1699 }
1700  
1701 /**
1702 * @test
1703 */
1704 public function when_ofType_is_called_with_empty_array()
1705 {
1706 /** @var array $result */
1707 $result = Linq::from(array())
1708 ->ofType('StubInterface')
1709 ->toArray();
1710  
1711 $this->assertNotNull($result);
1712 $this->assertCount(0, $result);
1713 }
1714  
1715 /**
1716 * @test
1717 */
1718 public function when_ofType_is_called_with_array_containing_expected_interface()
1719 {
1720 /** @var Stub $expectedResult */
1721 $expectedResult = new Stub();
1722  
1723 /** @var array $result */
1724 $result = Linq::from(array($expectedResult,
1725 new StubWithoutInterface()))
1726 ->ofType('StubInterface')
1727 ->toArray();
1728  
1729 $this->assertNotNull($result);
1730 $this->assertCount(1, $result);
1731 $this->assertSame($expectedResult, $result[0]);
1732 }
1733  
1734 /**
1735 * @test
1736 */
1737 public function when_ofType_is_called_with_array_containing_expected_object_type()
1738 {
1739 /** @var StubWithoutInterface $expectedResult1 */
1740 $expectedResult1 = new StubWithoutInterface();
1741  
1742 /** @var array $result */
1743 $result = Linq::from(array(new Stub(),
1744 $expectedResult1))
1745 ->ofType('StubWithoutInterface')
1746 ->toArray();
1747  
1748 $this->assertNotNull($result);
1749 $this->assertCount(1, $result);
1750 $this->assertSame($expectedResult1, $result[0]);
1751  
1752 /** @var StubWithoutInterface $expectedResult2 */
1753 $expectedResult2 = new Stub();
1754  
1755 $result = Linq::from(array($expectedResult2,
1756 new StubWithoutInterface()))
1757 ->ofType('Stub')
1758 ->toArray();
1759  
1760 $this->assertNotNull($result);
1761 $this->assertCount(1, $result);
1762 $this->assertSame($expectedResult2, $result[0]);
1763 }
1764  
1765 /**
1766 * @test
1767 */
1768 public function when_ofType_is_called_with_array_not_containing_expected_interface()
1769 {
1770 /** @var array $result */
1771 $result = Linq::from(array(new StubWithoutInterface(),
1772 new StubWithoutInterface()))
1773 ->ofType('StubInterface')
1774 ->toArray();
1775  
1776 $this->assertNotNull($result);
1777 $this->assertCount(0, $result);
1778 }
1779  
1780 /**
1781 * @test
1782 */
1783 public function when_ofType_is_called_with_array_not_containing_expected_object_type()
1784 {
1785 /** @var array $result */
1786 $result = Linq::from(array(new Stub(),
1787 new Stub()))
1788 ->ofType('StubWithoutInterface')
1789 ->toArray();
1790  
1791 $this->assertNotNull($result);
1792 $this->assertCount(0, $result);
1793 }
1794  
1795 /**
1796 * @test
1797 */
1798 public function when_ofType_is_called_with_unknown_interface()
1799 {
1800 /** @var array $result */
1801 $result = Linq::from(array(new Stub(),
1802 new Stub()))
1803 ->ofType('UnknownInterface')
1804 ->toArray();
1805  
1806 $this->assertNotNull($result);
1807 $this->assertCount(0, $result);
1808 }
1809  
1810 /**
1811 * @test
1812 */
1813 public function when_ofType_is_called_with_unknown_object_type()
1814 {
1815 /** @var array $result */
1816 $result = Linq::from(array(new Stub(),
1817 new Stub()))
1818 ->ofType('UnknownObject')
1819 ->toArray();
1820  
1821 $this->assertNotNull($result);
1822 $this->assertCount(0, $result);
1823 }
1824  
1825 /**
1826 * @test
1827 */
1828 public function when_ofType_is_called_with_int_as_type()
1829 {
1830 /** @var int[] $expectedResult */
1831 $expectedResult = array(1,
1832 2,
1833 10,
1834 20);
1835  
1836 $result = Linq::from(array(1,
1837 2,
1838 new Stub(),
1839 10,
1840 NULL,
1841 20))
1842 ->ofType('int')
1843 ->toArray();
1844  
1845 $this->assertNotNull($result);
1846 $this->assertEquals($expectedResult, $result);
1847 }
1848  
1849 /**
1850 * @test
1851 */
1852 public function when_ofType_is_called_with_bool_as_type()
1853 {
1854 /** @var int[] $expectedResult */
1855 $expectedResult = array(TRUE,
1856 FALSE);
1857  
1858 $result = Linq::from(array(0,
1859 'string',
1860 'true',
1861 TRUE,
1862 'false',
1863 FALSE))
1864 ->ofType('bool')
1865 ->toArray();
1866  
1867 $this->assertNotNull($result);
1868 $this->assertEquals($expectedResult, $result);
1869 }
1870  
1871 /**
1872 * @test
1873 */
1874 public function when_ofType_is_called_with_string_as_type()
1875 {
1876 /** @var int[] $expectedResult */
1877 $expectedResult = array('string',
1878 'true',
1879 'false');
1880  
1881 $result = Linq::from(array(0,
1882 'string',
1883 'true',
1884 TRUE,
1885 'false',
1886 FALSE))
1887 ->ofType('string')
1888 ->toArray();
1889  
1890 $this->assertNotNull($result);
1891 $this->assertEquals($expectedResult, $result);
1892 }
1893  
1894 /**
1895 * @test
1896 */
1897 public function when_ofType_is_called_with_float_as_type()
1898 {
1899 /** @var int[] $expectedResult */
1900 $expectedResult = array(2.5,
1901 10.0,
1902 0.3);
1903  
1904 $result = Linq::from(array(0,
1905 'string',
1906 2.5,
1907 10.0,
1908 11,
1909 'false',
1910 0.3))
1911 ->ofType('float')
1912 ->toArray();
1913  
1914 $this->assertNotNull($result);
1915 $this->assertEquals($expectedResult, $result);
1916 }
1917  
1918 /**
1919 * @test
1920 */
1921 public function when_ofType_is_called_with_double_as_type()
1922 {
1923 /** @var int[] $expectedResult */
1924 $expectedResult = array(2.5,
1925 10.0,
1926 0.3);
1927  
1928 $result = Linq::from(array(0,
1929 'string',
1930 2.5,
1931 10.0,
1932 NULL,
1933 11,
1934 'false',
1935 0.3))
1936 ->ofType('double')
1937 ->toArray();
1938  
1939 $this->assertNotNull($result);
1940 $this->assertEquals($expectedResult, $result);
1941 }
1942  
1943 private function assertException($closure, $expected = self::ExceptionName_Runtime)
1944 {
1945 try
1946 {
1947 $closure();
1948 }
1949 catch(Exception $ex)
1950 {
1951 $exName = get_class($ex);
1952  
1953 if($exName != $expected)
1954 {
1955 $this->fail("Wrong exception raised. Expected: '" . $expected . "' Actual: '" . get_class($ex) . "'. Message: " . $ex->getMessage());
1956 }
1957 return;
1958 }
1959  
1960 $this->fail($expected .' has not been raised.');
1961 }
1962 }