226 lines
6.7 KiB
PHP
226 lines
6.7 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2026 Kacper Donat
|
|
*
|
|
* @author Kacper "Kadet" Donat <kacper@kadet.net>
|
|
*
|
|
* Full license available in separate LICENSE file
|
|
*/
|
|
|
|
use Ds\Map;
|
|
use Ds\Seq;
|
|
use Kadet\Functional\Collections as c;
|
|
use Kadet\Functional\Exception\EmptyCollectionException;
|
|
|
|
class QueriesTest extends \PHPUnit\Framework\TestCase
|
|
{
|
|
public function testFirstReturnsNullOnEmpty()
|
|
{
|
|
$this->assertNull(c\first()([]));
|
|
$this->assertNull(c\first(fn ($v) => $v > 5)([1, 2]));
|
|
}
|
|
|
|
public function testFirstRequiredThrowsWithMessage()
|
|
{
|
|
$this->expectException(EmptyCollectionException::class);
|
|
$this->expectExceptionMessage('first(): no element matched');
|
|
c\first(fn ($v) => $v > 5, required: true)([1, 2]);
|
|
}
|
|
|
|
public function testFirstRequiredWithoutPredicateSaysEmpty()
|
|
{
|
|
$this->expectException(EmptyCollectionException::class);
|
|
$this->expectExceptionMessage('first(): collection is empty');
|
|
c\first(required: true)([]);
|
|
}
|
|
|
|
public function testFirstRequiredAcceptsNullMatch()
|
|
{
|
|
$this->assertNull(c\first(fn ($v) => $v === null, required: true)([1, null]));
|
|
}
|
|
|
|
public function testFirstFalsyValueFound()
|
|
{
|
|
$this->assertSame(0, c\first()([0]));
|
|
}
|
|
|
|
public function testFirstStopsEarly()
|
|
{
|
|
$gen = (static function () {
|
|
yield 1;
|
|
throw new \LogicException('consumed too far');
|
|
})();
|
|
|
|
$this->assertSame(1, c\first()($gen));
|
|
}
|
|
|
|
public function testFirstPassesKeyToPredicate()
|
|
{
|
|
$this->assertSame(2, c\first(fn ($v, $k) => $k === 'b')(['a' => 1, 'b' => 2]));
|
|
$this->assertSame(2, c\first(fn ($v, $k) => $k === 'b')(new Map(['a' => 1, 'b' => 2])));
|
|
}
|
|
|
|
public function testFindIsFirstWithPredicate()
|
|
{
|
|
$this->assertSame(2, c\find(fn ($v) => $v > 1)([1, 2, 3]));
|
|
|
|
$this->expectException(EmptyCollectionException::class);
|
|
$this->expectExceptionMessage('find(): no element matched');
|
|
c\find(fn ($v) => $v > 5, required: true)([1, 2, 3]);
|
|
}
|
|
|
|
public function testLastOnArrayAndPredicate()
|
|
{
|
|
$this->assertSame(3, c\last()([1, 2, 3]));
|
|
$this->assertSame(2, c\last(fn ($v) => $v < 3)([1, 2, 3]));
|
|
$this->assertNull(c\last()([]));
|
|
}
|
|
|
|
public function testLastOnGenerator()
|
|
{
|
|
$gen = (static function () {
|
|
yield 'a' => 1;
|
|
yield 'b' => 2;
|
|
})();
|
|
|
|
$this->assertSame(2, c\last()($gen));
|
|
}
|
|
|
|
public function testLastOnSeqAndSet()
|
|
{
|
|
$this->assertSame(3, c\last()(new Seq([1, 2, 3])));
|
|
$this->assertSame(2, c\last(fn ($v, $k) => $k < 2)(new Seq([1, 2, 3])));
|
|
$this->assertSame(3, c\last()(new \Ds\Set([1, 2, 3])));
|
|
$this->assertNull(c\last()(new Seq()));
|
|
}
|
|
|
|
public function testLastOnMapReturnsValue()
|
|
{
|
|
$this->assertSame('y', c\last()(new Map(['a' => 'x', 'b' => 'y'])));
|
|
$this->assertSame('x', c\last(fn ($v, $k) => $k === 'a')(new Map(['a' => 'x', 'b' => 'y'])));
|
|
}
|
|
|
|
public function testLastRequired()
|
|
{
|
|
$this->assertNull(c\last(fn ($v) => $v === null, required: true)([null, 1]));
|
|
|
|
$this->expectException(EmptyCollectionException::class);
|
|
$this->expectExceptionMessage('last(): collection is empty');
|
|
c\last(required: true)([]);
|
|
}
|
|
|
|
public function testLastRequiredNoMatchMessage()
|
|
{
|
|
$this->expectException(EmptyCollectionException::class);
|
|
$this->expectExceptionMessage('last(): no element matched');
|
|
c\last(fn ($v) => false, required: true)([1]);
|
|
}
|
|
|
|
public function testContainsIsStrict()
|
|
{
|
|
$this->assertFalse(c\contains('1')([1]));
|
|
$this->assertTrue(c\contains(1)([3, 1]));
|
|
$this->assertTrue(c\contains(null)([1, null]));
|
|
}
|
|
|
|
public function testContainsUsesDsKey()
|
|
{
|
|
$key = new class (1) implements \Ds\Key {
|
|
public function __construct(public int $id) {}
|
|
|
|
public function equals(mixed $obj): bool
|
|
{
|
|
return $obj->id === $this->id;
|
|
}
|
|
|
|
public function hash(): mixed
|
|
{
|
|
return $this->id;
|
|
}
|
|
};
|
|
|
|
$this->assertTrue(c\contains($key)([new $key(1)]));
|
|
$this->assertFalse(c\contains($key)([new $key(2)]));
|
|
}
|
|
|
|
public function testAnySomeEvery()
|
|
{
|
|
$this->assertTrue(c\any(fn ($v) => $v > 2)([1, 3]));
|
|
$this->assertFalse(c\any(fn ($v) => $v > 5)([1, 3]));
|
|
$this->assertFalse(c\any(fn ($v) => true)([]));
|
|
$this->assertTrue(c\some(fn ($v) => $v > 2)([1, 3]));
|
|
$this->assertTrue(c\every(fn ($v) => $v > 0)([1, 3]));
|
|
$this->assertFalse(c\every(fn ($v) => $v > 1)([1, 3]));
|
|
}
|
|
|
|
public function testEveryOnEmptyIsTrue()
|
|
{
|
|
$this->assertTrue(c\every(fn ($v) => false)([]));
|
|
}
|
|
|
|
public function testAnyStopsEarly()
|
|
{
|
|
$gen = (static function () {
|
|
yield 1;
|
|
throw new \LogicException('consumed too far');
|
|
})();
|
|
|
|
$this->assertTrue(c\any(fn ($v) => $v === 1)($gen));
|
|
}
|
|
|
|
public function testCountAndIsEmpty()
|
|
{
|
|
$gen = (static function () {
|
|
yield 1;
|
|
yield 2;
|
|
})();
|
|
|
|
$this->assertSame(2, c\count()($gen));
|
|
$this->assertSame(3, c\count()(new Seq([1, 2, 3])));
|
|
$this->assertSame(0, c\count()([]));
|
|
$this->assertTrue(c\isEmpty()([]));
|
|
$this->assertFalse(c\isEmpty()(new Seq([0])));
|
|
$this->assertTrue(c\isEmpty()(new Map()));
|
|
}
|
|
|
|
public function testMinMax()
|
|
{
|
|
$this->assertSame(1, c\min()([3, 1, 2]));
|
|
$this->assertSame(3, c\max()([3, 1, 2]));
|
|
$this->assertNull(c\min()([]));
|
|
$this->assertNull(c\max()([]));
|
|
}
|
|
|
|
public function testMinWithSelector()
|
|
{
|
|
$this->assertSame(1, c\min(fn ($v) => strlen($v))(['ccc', 'a', 'bb']));
|
|
$this->assertSame(3, c\max(fn ($v) => strlen($v))(['ccc', 'a', 'bb']));
|
|
}
|
|
|
|
public function testReduce()
|
|
{
|
|
$this->assertSame(6, c\reduce(fn ($carry, $v) => $carry + $v, 0)([1, 2, 3]));
|
|
$this->assertSame('a:1,b:2,', c\reduce(fn ($carry, $v, $k) => "$carry$k:$v,", '')(['a' => 1, 'b' => 2]));
|
|
$this->assertNull(c\reduce(fn ($carry, $v) => $v)([]));
|
|
}
|
|
|
|
public function testSum()
|
|
{
|
|
$this->assertSame(6, c\sum()([1, 2, 3]));
|
|
$this->assertSame(3.5, c\sum()([1, 2.5]));
|
|
$this->assertSame(4, c\sum(fn ($v) => strlen($v))(['ab', 'cd']));
|
|
}
|
|
|
|
public function testSumEmptyIsZero()
|
|
{
|
|
$this->assertSame(0, c\sum()([]));
|
|
}
|
|
|
|
public function testJoin()
|
|
{
|
|
$this->assertSame('a, b', c\join(', ')(['a', 'b']));
|
|
$this->assertSame('12', c\join()(new Seq([1, 2])));
|
|
$this->assertSame('', c\join(',')([]));
|
|
}
|
|
}
|