* @license MIT */ declare(strict_types=1); namespace Test; use BitBadger\InspiredByFSharp\Option; use BitBadger\InspiredByFSharp\Result; use InvalidArgumentException; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; /** * Unit tests for the Result class */ class ResultTest extends TestCase { #[TestDox('GetOK succeeds for OK result')] public function testGetOKSucceedsForOKResult(): void { $result = Result::OK('yay'); $this->assertEquals('yay', $result->getOK(), 'The OK result should have been returned'); } #[TestDox('GetOK fails for Error result')] public function testGetOKFailsForErrorResult(): void { $this->expectException(InvalidArgumentException::class); Result::Error('whoops')->getOK(); } #[TestDox('GetError succeeds for Error result')] public function testGetErrorSucceedsForErrorResult(): void { $result = Result::Error('boo'); $this->assertEquals('boo', $result->getError(), 'The Error result should have been returned'); } #[TestDox('GetError fails for OK result')] public function testGetErrorFailsForOKResult(): void { $this->expectException(InvalidArgumentException::class); Result::OK('yeah')->getError(); } #[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->assertEquals('something', $result->getOK(), 'The "OK" value was incorrect'); } #[TestDox('OK fails for null result')] public function testOKFailsForNullResult(): void { $this->expectException(InvalidArgumentException::class); Result::OK(null); } #[TestDox('Error succeeds for non null result')] public function testErrorSucceedsForNonNullResult(): void { $result = Result::Error('sad trombone'); $this->assertTrue(Result::isError($result), 'The result should have been "Error"'); $this->assertEquals('sad trombone', $result->getError(), 'The "Error" value was incorrect'); } #[TestDox('Error fails for null result')] public function testErrorFailsForNullResult(): void { $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'); } }