90 lines
2.7 KiB
PHP
90 lines
2.7 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2026 Kacper Donat
|
|
*
|
|
* @author Kacper "Kadet" Donat <kacper@kadet.net>
|
|
*
|
|
* Full license available in separate LICENSE file
|
|
*/
|
|
|
|
/*
|
|
* The only test written with the pipe operator, which needs PHP 8.5 to parse. phpunit.xml loads this file only on
|
|
* PHP 8.5 and newer, so the rest of the suite still runs on PHP 8.2-8.4.
|
|
*/
|
|
|
|
use Ds\Map;
|
|
use Kadet\Functional\Collections as c;
|
|
use Kadet\Functional\Collections\Lazy as lazy;
|
|
use Kadet\Functional\Exception\EmptyCollectionException;
|
|
use Kadet\Functional\Predicates as p;
|
|
use Kadet\Functional\Transforms as t;
|
|
use PHPUnit\Framework\Attributes\RequiresPhp;
|
|
|
|
final class PipeDeparture
|
|
{
|
|
public function __construct(
|
|
public readonly string $id,
|
|
public readonly string $line,
|
|
public readonly string $destination,
|
|
public readonly int $delay,
|
|
) {
|
|
}
|
|
}
|
|
|
|
#[RequiresPhp('>= 8.5')]
|
|
class PipeSyntaxTest extends \PHPUnit\Framework\TestCase
|
|
{
|
|
/** @return list<PipeDeparture> */
|
|
private static function departures(): array
|
|
{
|
|
return [
|
|
new PipeDeparture('d1', '8', 'Stogi', 0),
|
|
new PipeDeparture('d2', '8', 'Stogi', 120),
|
|
new PipeDeparture('d3', '12', 'Oliwa', 30),
|
|
new PipeDeparture('d4', '12', 'Oliwa', 300),
|
|
new PipeDeparture('d5', '6', 'Łostowice', 0),
|
|
];
|
|
}
|
|
|
|
public function testRealisticPipeline()
|
|
{
|
|
$late = self::departures()
|
|
|> lazy\filter(p\property('delay', p\gt(60)))
|
|
|> c\keyBy(t\property('id'))
|
|
|> c\map(fn (PipeDeparture $departure) => "{$departure->line} → {$departure->destination}");
|
|
|
|
$this->assertInstanceOf(Map::class, $late);
|
|
$this->assertSame(['d2' => '8 → Stogi', 'd4' => '12 → Oliwa'], $late->toArray());
|
|
}
|
|
|
|
public function testFirstRequired()
|
|
{
|
|
$mostDelayed = self::departures()
|
|
|> c\sortBy(t\property('delay'), descending: true)
|
|
|> c\first(required: true);
|
|
|
|
$this->assertSame('d4', $mostDelayed->id);
|
|
}
|
|
|
|
public function testFirstRequiredThrowsWhenNothingMatches()
|
|
{
|
|
$this->expectException(EmptyCollectionException::class);
|
|
$this->expectExceptionMessage('first(): no element matched');
|
|
|
|
self::departures() |> c\first(p\property('line', p\same('2')), required: true);
|
|
}
|
|
|
|
public function testLazyChainReadsOnlyWhatIsNeeded()
|
|
{
|
|
$read = [];
|
|
|
|
$line = self::departures()
|
|
|> lazy\tap(function (PipeDeparture $departure) use (&$read) { $read[] = $departure->id; })
|
|
|> lazy\map(t\property('line'))
|
|
|> c\find(fn (string $line) => $line === '12');
|
|
|
|
$this->assertSame('12', $line);
|
|
$this->assertSame(['d1', 'd2', 'd3'], $read);
|
|
}
|
|
}
|