Add toPhpOption; convert both to mostly non-static

This commit is contained in:
2024-07-28 14:15:24 -04:00
parent 7d25b9ea28
commit 5a8a41a660
5 changed files with 581 additions and 567 deletions

View File

@@ -23,7 +23,7 @@ class OptionTest extends TestCase
public function testGetSucceedsForSome(): void
{
$it = Option::Some(9);
$this->assertTrue(Option::isSome($it), 'The option should have been "Some"');
$this->assertTrue($it->isSome(), 'The option should have been "Some"');
$this->assertEquals(9, $it->get(), 'The value was incorrect');
}
@@ -34,10 +34,223 @@ class OptionTest extends TestCase
Option::None()->get();
}
#[TestDox('IsNone succeeds with None')]
public function testIsNoneSucceedsWithNone(): void
{
$this->assertTrue(Option::None()->isNone(), '"None" should return true');
}
#[TestDox('IsNone succeeds with Some')]
public function testIsNoneSucceedsWithSome(): void
{
$this->assertFalse(Option::Some(8)->isNone(), '"Some" should return false');
}
#[TestDox('IsSome succeeds with None')]
public function testIsSomeSucceedsWithNone(): void
{
$this->assertFalse(Option::None()->isSome(), '"None" should return false');
}
#[TestDox('IsSome succeeds with Some')]
public function testIsSomeSucceedsWithSome(): void
{
$this->assertTrue(Option::Some('boo')->isSome(), '"Some" should return true');
}
#[TestDox('GetOrDefault succeeds with None')]
public function testGetOrDefaultSucceedsWithNone(): void
{
$this->assertEquals('yes', Option::None()->getOrDefault('yes'), 'Value should have been default');
}
#[TestDox('GetOrDefault succeeds with Some')]
public function testGetOrDefaultSucceedsWithSome(): void
{
$this->assertEquals('no', Option::Some('no')->getOrDefault('yes'), 'Value should have been from option');
}
#[TestDox('GetOrCall succeeds with None')]
public function testGetOrCallSucceedsWithNone(): void
{
$value = Option::None()->getOrCall(new class { public function __invoke(): string { return 'called'; } });
$this->assertEquals('called', $value, 'The value should have been obtained from the callable');
}
#[TestDox('GetOrCall succeeds with Some')]
public function testGetOrCallSucceedsWithSome(): void
{
$value = Option::Some('passed')->getOrCall(
new class { public function __invoke(): string { return 'called'; } });
$this->assertEquals('passed', $value, 'The value should have been obtained from the option');
}
#[TestDox('Map succeeds with None')]
public function testMapSucceedsWithNone(): void
{
$tattle = new class { public bool $called = false; };
$none = Option::None();
$mapped = $none->map(function () use ($tattle)
{
$tattle->called = true;
return 'hello';
});
$this->assertTrue($mapped->isNone(), 'The mapped option should be "None"');
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
$this->assertSame($none, $mapped, 'The same "None" instance should have been returned');
}
#[TestDox('Map succeeds with Some')]
public function testMapSucceedsWithSome(): void
{
$mapped = Option::Some('abc ')->map(fn($it) => str_repeat($it, 2));
$this->assertTrue($mapped->isSome(), 'The mapped option should be "Some"');
$this->assertEquals('abc abc ', $mapped->get(), 'The mapping function was not called correctly');
}
#[TestDox('Map fails with Some when mapping is null')]
public function testMapFailsWithSomeWhenMappingIsNull(): void
{
$this->expectException(InvalidArgumentException::class);
Option::Some('oof')->map(fn($it) => null);
}
#[TestDox('Iter succeeds with None')]
public function testIterSucceedsWithNone(): void
{
$target = new class { public mixed $called = null; };
Option::None()->iter(function () use ($target) { $target->called = 'uh oh'; });
$this->assertNull($target->called, 'The function should not have been called');
}
#[TestDox('Iter succeeds with Some')]
public function testIterSucceedsWithSome(): void
{
$target = new class { public mixed $called = null; };
Option::Some(33)->iter(function ($it) use ($target) { $target->called = $it; });
$this->assertEquals(33, $target->called, 'The function should have been called with the "Some" value');
}
#[TestDox('Filter succeeds with None')]
public function testFilterSucceedsWithNone(): void
{
$tattle = new class { public bool $called = false; };
$none = Option::None();
$filtered = $none->filter(function () use ($tattle)
{
$tattle->called = true;
return true;
});
$this->assertTrue($filtered->isNone(), 'The filtered option should have been "None"');
$this->assertFalse($tattle->called, 'The callable should not have been called');
$this->assertSame($none, $filtered, 'The "None" instance returned should have been the one passed');
}
#[TestDox('Filter succeeds with Some when true')]
public function testFilterSucceedsWithSomeWhenTrue(): void
{
$some = Option::Some(12);
$filtered = $some->filter(fn($it) => $it % 2 === 0);
$this->assertTrue($filtered->isSome(), 'The filtered option should have been "Some"');
$this->assertEquals(12, $filtered->get(), 'The filtered option value is incorrect');
$this->assertSame($some, $filtered, 'The same "Some" instance should have been returned');
}
#[TestDox('Filter succeeds with Some when false')]
public function testFilterSucceedsWithSomeWhenFalse(): void
{
$some = Option::Some(23);
$filtered = $some->filter(fn($it) => $it % 2 === 0);
$this->assertTrue($filtered->isNone(), 'The filtered option should have been "None"');
}
#[TestDox('Is succeeds with None')]
public function testIsSucceedsWithNone(): void
{
$this->assertFalse(Option::None()->is(null), '"None" should always return false');
}
#[TestDox('Is succeeds with Some when strictly equal')]
public function testIsSucceedsWithSomeWhenStrictlyEqual(): void
{
$this->assertTrue(Option::Some(3)->is(3), '"Some" with strict equality should be true');
}
#[TestDox('Is succeeds with Some when strictly unequal')]
public function testIsSucceedsWithSomeWhenStrictlyUnequal(): void
{
$this->assertFalse(Option::Some('3')->is(3), '"Some" with strict equality should be false');
}
#[TestDox('Is succeeds with Some when loosely equal')]
public function testIsSucceedsWithSomeWhenLooselyEqual(): void
{
$this->assertTrue(Option::Some('3')->is(3, strict: false), '"Some" with loose equality should be true');
}
#[TestDox('Is succeeds with Some when loosely unequal')]
public function testIsSucceedsWithSomeWhenLooselyUnequal(): void
{
$this->assertFalse(Option::Some('3')->is(4, strict: false), '"Some" with loose equality should be false');
}
#[TestDox('Unwrap succeeds with None')]
public function testUnwrapSucceedsWithNone(): void
{
$this->assertNull(Option::None()->unwrap(), '"None" should return null');
}
#[TestDox('Unwrap succeeds with Some')]
public function testUnwrapSucceedsWithSome(): void
{
$this->assertEquals('boy howdy', Option::Some('boy howdy')->unwrap(), '"Some" should return its value');
}
#[TestDox('Tap succeeds with Some')]
public function testTapSucceedsWithSome(): void
{
$value = '';
$original = Option::Some('testing');
$tapped = $original->tap(
function (Option $it) use (&$value) { $value = $it->isSome() ? $it->get() : 'none'; });
$this->assertEquals('testing', $value, 'The tapped function was not called');
$this->assertSame($original, $tapped, 'The same option should have been returned');
}
#[TestDox('Tap succeeds with None')]
public function testTapSucceedsWithNone(): void
{
$value = '';
$original = Option::None();
$tapped = $original->tap(
function (Option $it) use (&$value) { $value = $it->isSome() ? $it->get() : 'none'; });
$this->assertEquals('none', $value, 'The tapped function was not called');
$this->assertSame($original, $tapped, 'The same option should have been returned');
}
#[TestDox('ToPhpOption succeeds for Some')]
public function testToPhpOptionSucceedsForSome(): void
{
$opt = Option::Some('php')->toPhpOption();
$this->assertNotNull($opt, 'The PhpOption should not have been null');
$this->assertInstanceOf('PhpOption\Some', $opt, 'The PhpOption should have been "Some"');
$this->assertTrue($opt->isDefined(), 'There should have been a value for the PhpOption');
$this->assertEquals('php', $opt->get(), 'The value was not correct');
}
#[TestDox('ToPhpOption succeeds for None')]
public function testToPhpOptionSucceedsForNone(): void
{
$opt = Option::None()->toPhpOption();
$this->assertNotNull($opt, 'The PhpOption should not have been null');
$this->assertInstanceOf('PhpOption\None', $opt, 'The PhpOption should have been "None"');
$this->assertFalse($opt->isDefined(), 'There should not have been a value for the PhpOption');
}
public function testSomeSucceedsWithValue(): void
{
$it = Option::Some('hello');
$this->assertTrue(Option::isSome($it), 'The option should have been "Some"');
$this->assertTrue($it->isSome(), 'The option should have been "Some"');
}
public function testSomeFailsWithNull(): void
@@ -49,19 +262,19 @@ class OptionTest extends TestCase
public function testNoneSucceeds(): void
{
$it = Option::None();
$this->assertTrue(Option::isNone($it), 'The option should have been "None"');
$this->assertTrue($it->isNone(), 'The option should have been "None"');
}
public function testOfSucceedsWithNull(): void
{
$it = Option::of(null);
$this->assertTrue(Option::isNone($it), '"null" should have created a "None" option');
$this->assertTrue($it->isNone(), '"null" should have created a "None" option');
}
public function testOfSucceedsWithNonNull(): void
{
$it = Option::of('test');
$this->assertTrue(Option::isSome($it), 'A non-null value should have created a "Some" option');
$this->assertTrue($it->isSome(), 'A non-null value should have created a "Some" option');
$this->assertEquals('test', $it->get(), 'The value was not assigned correctly');
}
@@ -69,7 +282,7 @@ class OptionTest extends TestCase
public function testOfSucceedsWithPhpOptionSome(): void
{
$it = Option::of(Some::create('something'));
$this->assertTrue(Option::isSome($it), 'A "Some" PhpOption should have created a "Some" option');
$this->assertTrue($it->isSome(), 'A "Some" PhpOption should have created a "Some" option');
$this->assertEquals('something', $it->get(), 'The value was not assigned correctly');
}
@@ -77,202 +290,6 @@ class OptionTest extends TestCase
public function testOfSucceedsWithPhpOptionNone(): void
{
$it = Option::of(None::create());
$this->assertTrue(Option::isNone($it), 'A "None" PhpOption should have created a "None" option');
}
#[TestDox('IsNone succeeds with None')]
public function testIsNoneSucceedsWithNone(): void
{
$this->assertTrue(Option::isNone(Option::None()), '"None" should return true');
}
#[TestDox('IsNone succeeds with Some')]
public function testIsNoneSucceedsWithSome(): void
{
$this->assertFalse(Option::isNone(Option::Some(8)), '"Some" should return false');
}
#[TestDox('IsSome succeeds with None')]
public function testIsSomeSucceedsWithNone(): void
{
$this->assertFalse(Option::isSome(Option::None()), '"None" should return false');
}
#[TestDox('IsSome succeeds with Some')]
public function testIsSomeSucceedsWithSome(): void
{
$this->assertTrue(Option::isSome(Option::Some('boo')), '"Some" should return true');
}
#[TestDox('DefaultValue succeeds with None')]
public function testDefaultValueSucceedsWithNone(): void
{
$this->assertEquals('yes', Option::defaultValue('yes', Option::None()), 'Value should have been default');
}
#[TestDox('DefaultValue succeeds with Some')]
public function testDefaultValueSucceedsWithSome(): void
{
$this->assertEquals('no', Option::defaultValue('yes', Option::Some('no')),
'Value should have been from option');
}
#[TestDox('GetOrCall succeeds with None')]
public function testGetOrCallSucceedsWithNone(): void
{
$value = Option::getOrCall(new class { public function __invoke(): string { return 'called'; } },
Option::None());
$this->assertEquals('called', $value, 'The value should have been obtained from the callable');
}
#[TestDox('GetOrCall succeeds with Some')]
public function testGetOrCallSucceedsWithSome(): void
{
$value = Option::getOrCall(new class { public function __invoke(): string { return 'called'; } },
Option::Some('passed'));
$this->assertEquals('passed', $value, 'The value should have been obtained from the option');
}
#[TestDox('Map succeeds with None')]
public function testMapSucceedsWithNone(): void
{
$tattle = new class { public bool $called = false; };
$none = Option::None();
$mapped = Option::map(function ($ignored) use ($tattle)
{
$tattle->called = true;
return 'hello';
}, $none);
$this->assertTrue(Option::isNone($mapped), 'The mapped option should be "None"');
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
$this->assertNotSame($none, $mapped, 'There should have been a new "None" instance returned');
}
#[TestDox('Map succeeds with Some')]
public function testMapSucceedsWithSome(): void
{
$mapped = Option::map(fn($it) => str_repeat($it, 2), Option::Some('abc '));
$this->assertTrue(Option::isSome($mapped), 'The mapped option should be "Some"');
$this->assertEquals('abc abc ', $mapped->get(), 'The mapping function was not called correctly');
}
#[TestDox('Map fails with Some when mapping is null')]
public function testMapFailsWithSomeWhenMappingIsNull(): void
{
$this->expectException(InvalidArgumentException::class);
Option::map(fn($it) => null, Option::Some('oof'));
}
#[TestDox('Iter succeeds with None')]
public function testIterSucceedsWithNone(): void
{
$target = new class { public mixed $called = null; };
Option::iter(function ($ignored) use ($target) { $target->called = 'uh oh'; }, Option::None());
$this->assertNull($target->called, 'The function should not have been called');
}
#[TestDox('Iter succeeds with Some')]
public function testIterSucceedsWithSome(): void
{
$target = new class { public mixed $called = null; };
Option::iter(function ($it) use ($target) { $target->called = $it; }, Option::Some(33));
$this->assertEquals(33, $target->called, 'The function should have been called with the "Some" value');
}
#[TestDox('Filter succeeds with None')]
public function testFilterSucceedsWithNone(): void
{
$tattle = new class { public bool $called = false; };
$none = Option::None();
$filtered = Option::filter(function ($ignored) use ($tattle)
{
$tattle->called = true;
return true;
}, $none);
$this->assertTrue(Option::isNone($filtered), 'The filtered option should have been "None"');
$this->assertFalse($tattle->called, 'The callable should not have been called');
}
#[TestDox('Filter succeeds with Some when true')]
public function testFilterSucceedsWithSomeWhenTrue(): void
{
$some = Option::Some(12);
$filtered = Option::filter(fn($it) => $it % 2 === 0, $some);
$this->assertTrue(Option::isSome($filtered), 'The filtered option should have been "Some"');
$this->assertEquals(12, $filtered->get(), 'The filtered option value is incorrect');
$this->assertNotSame($some, $filtered, 'There should have been a new option instance returned');
}
#[TestDox('Filter succeeds with Some when false')]
public function testFilterSucceedsWithSomeWhenFalse(): void
{
$some = Option::Some(23);
$filtered = Option::filter(fn($it) => $it % 2 === 0, $some);
$this->assertTrue(Option::isNone($filtered), 'The filtered option should have been "None"');
}
#[TestDox('Is succeeds with None')]
public function testIsSucceedsWithNone(): void
{
$this->assertFalse(Option::is(null, Option::None()), '"None" should always return false');
}
#[TestDox('Is succeeds with Some when strictly equal')]
public function testIsSucceedsWithSomeWhenStrictlyEqual(): void
{
$this->assertTrue(Option::is(3, Option::Some(3)), '"Some" with strict equality should be true');
}
#[TestDox('Is succeeds with Some when strictly unequal')]
public function testIsSucceedsWithSomeWhenStrictlyUnequal(): void
{
$this->assertFalse(Option::is(3, Option::Some('3')), '"Some" with strict equality should be false');
}
#[TestDox('Is succeeds with Some when loosely equal')]
public function testIsSucceedsWithSomeWhenLooselyEqual(): void
{
$this->assertTrue(Option::is(3, Option::Some('3'), strict: false), '"Some" with loose equality should be true');
}
#[TestDox('Is succeeds with Some when loosely unequal')]
public function testIsSucceedsWithSomeWhenLooselyUnequal(): void
{
$this->assertFalse(Option::is(4, Option::Some('3'), strict: false),
'"Some" with loose equality should be false');
}
#[TestDox('Unwrap succeeds with None')]
public function testUnwrapSucceedsWithNone(): void
{
$this->assertNull(Option::unwrap(Option::None()), '"None" should return null');
}
#[TestDox('Unwrap succeeds with Some')]
public function testUnwrapSucceedsWithSome(): void
{
$this->assertEquals('boy howdy', Option::unwrap(Option::Some('boy howdy')), '"Some" should return its value');
}
#[TestDox('Tap succeeds with Some')]
public function testTapSucceedsWithSome(): void
{
$value = '';
$original = Option::Some('testing');
$tapped = Option::tap(
function (Option $it) use (&$value) { $value = Option::isSome($it) ? $it->get() : 'none'; }, $original);
$this->assertEquals('testing', $value, 'The tapped function was not called');
$this->assertSame($original, $tapped, 'The same option should have been returned');
}
#[TestDox('Tap succeeds with None')]
public function testTapSucceedsWithNone(): void
{
$value = '';
$original = Option::None();
$tapped = Option::tap(
function (Option $it) use (&$value) { $value = Option::isSome($it) ? $it->get() : 'none'; }, $original);
$this->assertEquals('none', $value, 'The tapped function was not called');
$this->assertSame($original, $tapped, 'The same option should have been returned');
$this->assertTrue($it->isNone(), 'A "None" PhpOption should have created a "None" option');
}
}

View File

@@ -8,7 +8,6 @@ declare(strict_types=1);
namespace Test;
use BitBadger\InspiredByFSharp\Option;
use BitBadger\InspiredByFSharp\Result;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\TestDox;
@@ -47,11 +46,156 @@ class ResultTest extends TestCase
Result::OK('yeah')->getError();
}
#[TestDox('IsOK succeeds for OK result')]
public function testIsOKSucceedsForOKResult(): void
{
$result = Result::OK('ok');
$this->assertTrue($result->isOK(), 'The check for "OK" should have returned true');
}
#[TestDox('IsOK succeeds for Error result')]
public function testIsOKSucceedsForErrorResult(): void
{
$result = Result::Error('error');
$this->assertFalse($result->isOK(), 'The check for "OK" should have returned false');
}
#[TestDox('IsError succeeds for Error result')]
public function testIsErrorSucceedsForErrorResult(): void
{
$result = Result::Error('not ok');
$this->assertTrue($result->isError(), 'The check for "Error" should have returned true');
}
#[TestDox('IsError succeeds for OK result')]
public function testIsErrorSucceedsForOKResult(): void
{
$result = Result::OK('fine');
$this->assertFalse($result->isError(), 'The check for "Error" should have returned false');
}
#[TestDox('Map succeeds for OK result')]
public function testMapSucceedsForOKResult(): void
{
$ok = Result::OK('yard');
$mapped = $ok->map(fn($it) => strrev($it));
$this->assertTrue($mapped->isOK(), 'The mapped result should be "OK"');
$this->assertEquals('dray', $mapped->getOK(), 'The mapping function was not called correctly');
}
#[TestDox('Map fails for OK result when mapping is null')]
public function testMapFailsForOKResultWhenMappingIsNull(): void
{
$this->expectException(InvalidArgumentException::class);
Result::OK('not null')->map(fn($it) => null);
}
#[TestDox('Map succeeds for Error result')]
public function testMapSucceedsForErrorResult(): void
{
$tattle = new class { public bool $called = false; };
$error = Result::Error('nope');
$mapped = $error->map(function () use ($tattle)
{
$tattle->called = true;
return 'hello';
});
$this->assertTrue($mapped->isError(), 'The mapped result should be "Error"');
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
$this->assertSame($error, $mapped, 'The same "Error" instance should have been returned');
}
#[TestDox('MapError succeeds for OK result')]
public function testMapErrorSucceedsForOKResult(): void
{
$tattle = new class { public bool $called = false; };
$ok = Result::OK('sure');
$mapped = $ok->mapError(function () use ($tattle)
{
$tattle->called = true;
return 'hello';
});
$this->assertTrue($mapped->isOK(), 'The mapped result should be "OK"');
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
$this->assertSame($ok, $mapped, 'The same "OK" instance should have been returned');
}
#[TestDox('MapError succeeds for Error result')]
public function testMapErrorSucceedsForErrorResult(): void
{
$error = Result::Error('taco');
$mapped = $error->mapError(fn($it) => str_repeat('*', strlen($it)));
$this->assertTrue($mapped->isError(), 'The mapped result should be "Error"');
$this->assertEquals('****', $mapped->getError(), 'The mapping function was not called correctly');
}
#[TestDox('MapError fails for Error result when mapping is null')]
public function testMapErrorFailsForErrorResultWhenMappingIsNull(): void
{
$this->expectException(InvalidArgumentException::class);
Result::Error('pizza')->mapError(fn($it) => null);
}
#[TestDox('Iter succeeds for OK result')]
public function testIterSucceedsForOKResult(): void
{
$target = new class { public mixed $called = null; };
Result::OK(77)->iter(function ($it) use ($target) { $target->called = $it; });
$this->assertEquals(77, $target->called, 'The function should have been called with the "OK" value');
}
#[TestDox('Iter succeeds for Error result')]
public function testIterSucceedsForErrorResult(): void
{
$target = new class { public mixed $called = null; };
Result::Error('')->iter(function () use ($target) { $target->called = 'uh oh'; });
$this->assertNull($target->called, 'The function should not have been called');
}
#[TestDox('ToOption succeeds for OK result')]
public function testToOptionSucceedsForOKResult()
{
$value = Result::OK(99)->toOption();
$this->assertTrue($value->isSome(), 'An "OK" result should map to a "Some" option');
$this->assertEquals(99, $value->get(), 'The value is not correct');
}
#[TestDox('ToOption succeeds for Error result')]
public function testToOptionSucceedsForErrorResult()
{
$value = Result::Error('file not found')->toOption();
$this->assertTrue($value->isNone(), 'An "Error" result should map to a "None" option');
}
#[TestDox('Tap succeeds for OK result')]
public function testTapSucceedsForOKResult(): void
{
$value = '';
$original = Result::OK('working');
$tapped = $original->tap(function (Result $it) use (&$value) {
$value = $it->isOK() ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError();
});
$this->assertEquals('OK: working', $value, 'The tapped function was not called');
$this->assertSame($original, $tapped, 'The same result should have been returned');
}
#[TestDox('Tap succeeds for Error result')]
public function testTapSucceedsForErrorResult(): void
{
$value = '';
$original = Result::Error('failed');
$tapped = $original->tap(function (Result $it) use (&$value) {
$value = $it->isOK() ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError();
});
$this->assertEquals('Error: failed', $value, 'The tapped function was not called');
$this->assertSame($original, $tapped, 'The same result should have been returned');
}
#[TestDox('OK succeeds for non null result')]
public function testOKSucceedsForNonNullResult(): void
{
$result = Result::OK('something');
$this->assertTrue(Result::isOK($result), 'The result should have been "OK"');
$this->assertTrue($result->isOK(), 'The result should have been "OK"');
$this->assertEquals('something', $result->getOK(), 'The "OK" value was incorrect');
}
@@ -66,7 +210,7 @@ class ResultTest extends TestCase
public function testErrorSucceedsForNonNullResult(): void
{
$result = Result::Error('sad trombone');
$this->assertTrue(Result::isError($result), 'The result should have been "Error"');
$this->assertTrue($result->isError(), 'The result should have been "Error"');
$this->assertEquals('sad trombone', $result->getError(), 'The "Error" value was incorrect');
}
@@ -76,149 +220,4 @@ class ResultTest extends TestCase
$this->expectException(InvalidArgumentException::class);
Result::Error(null);
}
#[TestDox('IsOK succeeds for OK result')]
public function testIsOKSucceedsForOKResult(): void
{
$result = Result::OK('ok');
$this->assertTrue(Result::isOK($result), 'The check for "OK" should have returned true');
}
#[TestDox('IsOK succeeds for Error result')]
public function testIsOKSucceedsForErrorResult(): void
{
$result = Result::Error('error');
$this->assertFalse(Result::isOK($result), 'The check for "OK" should have returned false');
}
#[TestDox('IsError succeeds for Error result')]
public function testIsErrorSucceedsForErrorResult(): void
{
$result = Result::Error('not ok');
$this->assertTrue(Result::isError($result), 'The check for "Error" should have returned true');
}
#[TestDox('IsError succeeds for OK result')]
public function testIsErrorSucceedsForOKResult(): void
{
$result = Result::OK('fine');
$this->assertFalse(Result::isError($result), 'The check for "Error" should have returned false');
}
#[TestDox('Map succeeds for OK result')]
public function testMapSucceedsForOKResult(): void
{
$ok = Result::OK('yard');
$mapped = Result::map(fn($it) => strrev($it), $ok);
$this->assertTrue(Result::isOK($mapped), 'The mapped result should be "OK"');
$this->assertEquals('dray', $mapped->getOK(), 'The mapping function was not called correctly');
}
#[TestDox('Map fails for OK result when mapping is null')]
public function testMapFailsForOKResultWhenMappingIsNull(): void
{
$this->expectException(InvalidArgumentException::class);
Result::map(fn($it) => null, Result::OK('not null'));
}
#[TestDox('Map succeeds for Error result')]
public function testMapSucceedsForErrorResult(): void
{
$tattle = new class { public bool $called = false; };
$error = Result::Error('nope');
$mapped = Result::map(function ($ignored) use ($tattle)
{
$tattle->called = true;
return 'hello';
}, $error);
$this->assertTrue(Result::isError($mapped), 'The mapped result should be "Error"');
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
$this->assertNotSame($error, $mapped, 'There should have been a new "Error" instance returned');
}
#[TestDox('MapError succeeds for OK result')]
public function testMapErrorSucceedsForOKResult(): void
{
$tattle = new class { public bool $called = false; };
$ok = Result::OK('sure');
$mapped = Result::mapError(function ($ignored) use ($tattle)
{
$tattle->called = true;
return 'hello';
}, $ok);
$this->assertTrue(Result::isOK($mapped), 'The mapped result should be "OK"');
$this->assertFalse($tattle->called, 'The mapping function should not have been called');
$this->assertNotSame($ok, $mapped, 'There should have been a new "OK" instance returned');
}
#[TestDox('MapError succeeds for Error result')]
public function testMapErrorSucceedsForErrorResult(): void
{
$error = Result::Error('taco');
$mapped = Result::mapError(fn($it) => str_repeat('*', strlen($it)), $error);
$this->assertTrue(Result::isError($mapped), 'The mapped result should be "Error"');
$this->assertEquals('****', $mapped->getError(), 'The mapping function was not called correctly');
}
#[TestDox('MapError fails for Error result when mapping is null')]
public function testMapErrorFailsForErrorResultWhenMappingIsNull(): void
{
$this->expectException(InvalidArgumentException::class);
Result::mapError(fn($it) => null, Result::Error('pizza'));
}
#[TestDox('Iter succeeds for OK result')]
public function testIterSucceedsForOKResult(): void
{
$target = new class { public mixed $called = null; };
Result::iter(function ($it) use ($target) { $target->called = $it; }, Result::OK(77));
$this->assertEquals(77, $target->called, 'The function should have been called with the "OK" value');
}
#[TestDox('Iter succeeds for Error result')]
public function testIterSucceedsForErrorResult(): void
{
$target = new class { public mixed $called = null; };
Result::iter(function ($ignored) use ($target) { $target->called = 'uh oh'; }, Result::Error(''));
$this->assertNull($target->called, 'The function should not have been called');
}
#[TestDox('ToOption succeeds for OK result')]
public function testToOptionSucceedsForOKResult()
{
$value = Result::toOption(Result::OK(99));
$this->assertTrue(Option::isSome($value), 'An "OK" result should map to a "Some" option');
$this->assertEquals(99, $value->get(), 'The value is not correct');
}
#[TestDox('ToOption succeeds for Error result')]
public function testToOptionSucceedsForErrorResult()
{
$value = Result::toOption(Result::Error('file not found'));
$this->assertTrue(Option::isNone($value), 'An "Error" result should map to a "None" option');
}
#[TestDox('Tap succeeds for OK result')]
public function testTapSucceedsForOKResult(): void
{
$value = '';
$original = Result::OK('working');
$tapped = Result::tap(function (Result $it) use (&$value) {
$value = Result::isOK($it) ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError();
}, $original);
$this->assertEquals('OK: working', $value, 'The tapped function was not called');
$this->assertSame($original, $tapped, 'The same result should have been returned');
}
#[TestDox('Tap succeeds for Error result')]
public function testTapSucceedsForErrorResult(): void
{
$value = '';
$original = Result::Error('failed');
$tapped = Result::tap(function (Result $it) use (&$value) {
$value = Result::isOK($it) ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError();
}, $original);
$this->assertEquals('Error: failed', $value, 'The tapped function was not called');
$this->assertSame($original, $tapped, 'The same result should have been returned');
}
}