124 lines
3.7 KiB
PHP
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]));
|
|
}
|
|
}
|