functional-php/tests/Collections/LibraryCallablesTest.php
2026-10-27 23:04:09 +01:00

124 lines
3.7 KiB
PHP

<?php
/**
* Copyright 2026 Kacper Donat
*
* @author Kacper "Kadet" Donat <kacper@kadet.net>
*
* Full license available in separate LICENSE file
*/
use Kadet\Functional as f;
use Kadet\Functional\Collections as c;
use Kadet\Functional\Predicates as p;
use const Kadet\Functional\_;
/**
* The library's own callables (predicates, pipes, decorators) used as collection callbacks.
*/
class LibraryCallablesTest extends \PHPUnit\Framework\TestCase
{
// negated predicates
public function testFilterWithNegatedInternalFunction()
{
$this->assertSame([1 => 'x'], c\filter(f\not(is_numeric(...)))(['1', 'x']));
}
public function testFilterWithNegatedClosure()
{
$this->assertSame([1], c\filter(f\not(fn ($x) => $x > 1))([1, 2]));
}
public function testFilterWithNegatedPredicateMethod()
{
$this->assertSame([1], c\filter(p\gt(1)->not())([1, 2]));
}
public function testRejectWithNegatedPredicate()
{
$this->assertSame([1 => 2], c\reject(p\gt(1)->not())([1, 2]));
}
// pipelines get only the value when their first function takes only the value
public function testMapWithPipelineOfInternalFunctions()
{
$slug = f\pipe(trim(...), strtolower(...), f\partial(str_replace(...), ' ', '-', _));
$this->assertSame(['n' => 'main-n'], c\map($slug)(['n' => 'Main n']));
}
public function testMapWithPipelineOfInternalFunctionWithOptionalParameter()
{
$this->assertSame([1.0, 3.0, 3.0], c\map(f\pipe(round(...)))([1.25, 2.5, 3.14159]));
}
public function testMapWithPipelineOfMemoizedOneParameterClosure()
{
$calls = 0;
$pipeline = f\pipe(f\memoize(function ($x) use (&$calls) {
$calls++;
return $x * 2;
}), fn ($x) => $x + 1);
$this->assertSame(['a' => 3, 'b' => 3], c\map($pipeline)(['a' => 1, 'b' => 1]));
$this->assertSame(1, $calls);
}
public function testMapWithPipelineStartingWithTwoParameterClosureGetsKey()
{
$this->assertSame(['a' => 'A=X'], c\map(f\pipe(fn ($v, $k) => "$k=$v", strtoupper(...)))(['a' => 'x']));
}
public function testFilterWithPredicateOfPipeline()
{
$a = (object) ['x' => 1];
$b = (object) ['x' => 2];
$this->assertSame([1 => $b], c\filter(p\property('x', p\gt(1)))([$a, $b]));
}
// predicate combinators forward to each predicate only what it takes
public function testFilterWithAllOfInternalFunctionAndClosure()
{
$this->assertSame([0 => '1'], c\filter(f\all(is_numeric(...), fn ($x) => $x > 0))(['1', 'x', '-1']));
}
public function testFilterWithAnyOfInternalFunctionAndClosure()
{
$this->assertSame([0 => '1', 2 => 'y'], c\filter(f\any(is_numeric(...), fn ($x) => $x === 'y'))(['1', 'x', 'y']));
}
public function testFilterWithAllOfPassesKeyToPredicateTakingIt()
{
$this->assertSame(['b' => 2], c\filter(f\all(is_int(...), fn ($v, $k) => $k === 'b'))(['a' => 1, 'b' => 2]));
}
public function testFilterWithNegatedAllOfInternalFunctions()
{
$this->assertSame([1 => 'x'], c\filter(f\not(f\all(is_numeric(...), is_string(...))))(['1', 'x']));
}
// decorators
public function testMemoizedClosureIsCachedByValueOnly()
{
$calls = 0;
$memoized = f\memoize(function ($x) use (&$calls) {
$calls++;
return $x * 2;
});
$this->assertSame([2, 2, 2, 2], c\map($memoized)([1, 1, 1, 1]));
$this->assertSame(1, $calls);
}
public function testMemoizedInternalFunctionGetsValueOnly()
{
$this->assertSame([1.0, 3.0, 3.0], c\map(f\memoize(round(...)))([1.25, 2.5, 3.14159]));
}
}