Convert tests to Pest
This commit is contained in:
		
							parent
							
								
									6779b2c554
								
							
						
					
					
						commit
						3cc780956d
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,3 @@ | ||||
| .idea | ||||
| vendor | ||||
| *.tests.txt | ||||
| *-tests.txt | ||||
|  | ||||
| @ -20,8 +20,8 @@ | ||||
|     "php": "8.2 - 8.3" | ||||
|   }, | ||||
|   "require-dev": { | ||||
|     "phpunit/phpunit": "^11", | ||||
|     "phpoption/phpoption": "^1" | ||||
|     "phpoption/phpoption": "^1", | ||||
|     "pestphp/pest": "^3.5" | ||||
|   }, | ||||
|   "autoload": { | ||||
|     "psr-4": { | ||||
| @ -30,10 +30,15 @@ | ||||
|   }, | ||||
|   "autoload-dev": { | ||||
|     "psr-4": { | ||||
|       "Test\\": "./tests" | ||||
|       "Tests\\": "./tests" | ||||
|     } | ||||
|   }, | ||||
|   "archive": { | ||||
|     "exclude": [ "/tests", "/.gitattributes", "/.gitignore", "/.git", "/composer.lock" ] | ||||
|   }, | ||||
|   "config": { | ||||
|     "allow-plugins": { | ||||
|       "pestphp/pest-plugin": true | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										2207
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2207
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										17
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" | ||||
|          bootstrap="vendor/autoload.php" | ||||
|          colors="true" | ||||
| > | ||||
|     <testsuites> | ||||
|         <testsuite name="Test Suite"> | ||||
|             <directory suffix="Test.php">./tests</directory> | ||||
|         </testsuite> | ||||
|     </testsuites> | ||||
|     <source> | ||||
|         <include> | ||||
|             <directory suffix=".php">./src</directory> | ||||
|         </include> | ||||
|     </source> | ||||
| </phpunit> | ||||
| @ -1,368 +0,0 @@ | ||||
| <?php | ||||
| /** | ||||
|  * @author Daniel J. Summers <daniel@bitbadger.solutions> | ||||
|  * @license MIT | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace Test; | ||||
| 
 | ||||
| use BadMethodCallException; | ||||
| use BitBadger\InspiredByFSharp\Option; | ||||
| use InvalidArgumentException; | ||||
| use PhpOption\{None, Some}; | ||||
| use PHPUnit\Framework\Attributes\TestDox; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| 
 | ||||
| /** | ||||
|  * Unit tests for the Option class | ||||
|  */ | ||||
| class OptionTest extends TestCase | ||||
| { | ||||
|     #[TestDox('Get succeeds for Some')]
 | ||||
|     public function testGetSucceedsForSome(): void | ||||
|     { | ||||
|         $it = Option::Some(9); | ||||
|         $this->assertTrue($it->isSome(), 'The option should have been "Some"'); | ||||
|         $this->assertEquals(9, $it->get(), 'The value was incorrect'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Get fails for None')]
 | ||||
|     public function testGetFailsForNone(): void | ||||
|     { | ||||
|         $this->expectException(InvalidArgumentException::class); | ||||
|         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('GetOrThrow succeeds with Some')]
 | ||||
|     public function testGetOrThrowSucceedsWithSome(): void | ||||
|     { | ||||
|         $value = Option::Some('no throw')->getOrThrow(fn() => new BadMethodCallException('oops')); | ||||
|         $this->assertEquals('no throw', $value, 'The "Some" value should have been returned'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('GetOrThrow succeeds with None')]
 | ||||
|     public function testGetOrThrowSucceedsWithNone(): void | ||||
|     { | ||||
|         $this->expectException(BadMethodCallException::class); | ||||
|         Option::None()->getOrThrow(fn() => new BadMethodCallException('oops')); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Bind succeeds with None')]
 | ||||
|     public function testBindSucceedsWithNone(): void | ||||
|     { | ||||
|         $original = Option::None(); | ||||
|         $bound    = $original->bind(fn($it) => Option::Some('value')); | ||||
|         $this->assertTrue($bound->isNone(), 'The option should have been None'); | ||||
|         $this->assertSame($original, $bound, 'The same None instance should have been returned'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Bind succeeds with Some and Some')]
 | ||||
|     public function testBindSucceedsWithSomeAndSome(): void | ||||
|     { | ||||
|         $bound = Option::Some('hello')->bind(fn($it) => Option::Some('goodbye')); | ||||
|         $this->assertTrue($bound->isSome(), 'The option should have been Some'); | ||||
|         $this->assertEquals('goodbye', $bound->get(), 'The bound function was not called'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Bind succeeds with Some and None')]
 | ||||
|     public function testBindSucceedsWithSomeAndNone(): void | ||||
|     { | ||||
|         $bound = Option::Some('greetings')->bind(fn($it) => Option::None()); | ||||
|         $this->assertTrue($bound->isNone(), 'The option should have been None'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds with None')]
 | ||||
|     public function testContainsSucceedsWithNone(): void | ||||
|     { | ||||
|         $this->assertFalse(Option::None()->contains(null), '"None" should always return false'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds with Some when strictly equal')]
 | ||||
|     public function testContainsSucceedsWithSomeWhenStrictlyEqual(): void | ||||
|     { | ||||
|         $this->assertTrue(Option::Some(3)->contains(3), '"Some" with strict equality should be true'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds with Some when strictly unequal')]
 | ||||
|     public function testContainsSucceedsWithSomeWhenStrictlyUnequal(): void | ||||
|     { | ||||
|         $this->assertFalse(Option::Some('3')->contains(3), '"Some" with strict equality should be false'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds with Some when loosely equal')]
 | ||||
|     public function testContainsSucceedsWithSomeWhenLooselyEqual(): void | ||||
|     { | ||||
|         $this->assertTrue(Option::Some('3')->contains(3, strict: false), '"Some" with loose equality should be true'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds with Some when loosely unequal')]
 | ||||
|     public function testContainsSucceedsWithSomeWhenLooselyUnequal(): void | ||||
|     { | ||||
|         $this->assertFalse(Option::Some('3')->contains(4, strict: false), '"Some" with loose equality should be false'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Exists succeeds with Some when matching')]
 | ||||
|     public function testExistsSucceedsWithSomeWhenMatching(): void | ||||
|     { | ||||
|         $this->assertTrue(Option::Some(14)->exists(fn($it) => $it < 100), 'Exists should have returned true'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Exists succeeds with Some when not matching')]
 | ||||
|     public function testExistsSucceedsWithSomeWhenNotMatching(): void | ||||
|     { | ||||
|         $this->assertFalse(Option::Some(14)->exists(fn($it) => $it > 100), 'Exists should have returned false'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Exists succeeds with None')]
 | ||||
|     public function testExistsSucceedsWithNone(): void | ||||
|     { | ||||
|         $this->assertFalse(Option::None()->exists(fn($it) => true), 'Exists should have returned false'); | ||||
|     } | ||||
| 
 | ||||
|     #[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('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('ToArray succeeds with Some')]
 | ||||
|     public function testToArraySucceedsWithSome(): void | ||||
|     { | ||||
|         $arr = Option::Some('15')->toArray(); | ||||
|         $this->assertNotNull($arr, 'The array should not have been null'); | ||||
|         $this->assertEquals(['15'], $arr, 'The array was not created correctly'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('ToArray succeeds with None')]
 | ||||
|     public function testToArraySucceedsWithNone(): void | ||||
|     { | ||||
|         $arr = Option::None()->toArray(); | ||||
|         $this->assertNotNull($arr, 'The array should not have been null'); | ||||
|         $this->assertEmpty($arr, 'The array should have been empty'); | ||||
|     } | ||||
| 
 | ||||
|     #[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($it->isSome(), 'The option should have been "Some"'); | ||||
|     } | ||||
| 
 | ||||
|     public function testSomeFailsWithNull(): void | ||||
|     { | ||||
|         $this->expectException(InvalidArgumentException::class); | ||||
|         Option::Some(null); | ||||
|     } | ||||
| 
 | ||||
|     public function testNoneSucceeds(): void | ||||
|     { | ||||
|         $it = Option::None(); | ||||
|         $this->assertTrue($it->isNone(), 'The option should have been "None"'); | ||||
|     } | ||||
| 
 | ||||
|     public function testOfSucceedsWithNull(): void | ||||
|     { | ||||
|         $it = Option::of(null); | ||||
|         $this->assertTrue($it->isNone(), '"null" should have created a "None" option'); | ||||
|     } | ||||
| 
 | ||||
|     public function testOfSucceedsWithNonNull(): void | ||||
|     { | ||||
|         $it = Option::of('test'); | ||||
|         $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'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Of succeeds with PhpOption\Some')]
 | ||||
|     public function testOfSucceedsWithPhpOptionSome(): void | ||||
|     { | ||||
|         $it = Option::of(Some::create('something')); | ||||
|         $this->assertTrue($it->isSome(), 'A "Some" PhpOption should have created a "Some" option'); | ||||
|         $this->assertEquals('something', $it->get(), 'The value was not assigned correctly'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Of succeeds with PhpOption\None')]
 | ||||
|     public function testOfSucceedsWithPhpOptionNone(): void | ||||
|     { | ||||
|         $it = Option::of(None::create()); | ||||
|         $this->assertTrue($it->isNone(), 'A "None" PhpOption should have created a "None" option'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								tests/Pest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								tests/Pest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| <?php | ||||
| 
 | ||||
| /* | ||||
| |-------------------------------------------------------------------------- | ||||
| | Test Case | ||||
| |-------------------------------------------------------------------------- | ||||
| | | ||||
| | The closure you provide to your test functions is always bound to a specific PHPUnit test | ||||
| | case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may | ||||
| | need to change it using the "pest()" function to bind a different classes or traits. | ||||
| | | ||||
| */ | ||||
| 
 | ||||
| // pest()->extend(Tests\TestCase::class)->in('Feature');
 | ||||
| 
 | ||||
| /* | ||||
| |-------------------------------------------------------------------------- | ||||
| | Expectations | ||||
| |-------------------------------------------------------------------------- | ||||
| | | ||||
| | When you're writing tests, you often need to check that values meet certain conditions. The | ||||
| | "expect()" function gives you access to a set of "expectations" methods that you can use | ||||
| | to assert different things. Of course, you may extend the Expectation API at any time. | ||||
| | | ||||
| */ | ||||
| 
 | ||||
| expect()->extend('toBeOne', function () { | ||||
|     return $this->toBe(1); | ||||
| }); | ||||
| 
 | ||||
| /* | ||||
| |-------------------------------------------------------------------------- | ||||
| | Functions | ||||
| |-------------------------------------------------------------------------- | ||||
| | | ||||
| | While Pest is very powerful out-of-the-box, you may have some testing code specific to your | ||||
| | project that you don't want to repeat in every file. Here you can also expose helpers as | ||||
| | global functions to help you to reduce the number of lines of code in your test files. | ||||
| | | ||||
| */ | ||||
| 
 | ||||
| function something() | ||||
| { | ||||
|     // ..
 | ||||
| } | ||||
| @ -1,313 +0,0 @@ | ||||
| <?php | ||||
| /** | ||||
|  * @author Daniel J. Summers <daniel@bitbadger.solutions> | ||||
|  * @license MIT | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace Test; | ||||
| 
 | ||||
| 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('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('Bind succeeds for OK with OK')]
 | ||||
|     public function testBindSucceedsForOKWithOK(): void | ||||
|     { | ||||
|         $result = Result::OK('one')->bind(fn($it) => Result::OK("$it two")); | ||||
|         $this->assertTrue($result->isOK(), 'The result should have been OK'); | ||||
|         $this->assertEquals('one two', $result->getOK(), 'The bound function was not called'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Bind succeeds for OK with Error')]
 | ||||
|     public function testBindSucceedsForOKWithError(): void | ||||
|     { | ||||
|         $result = Result::OK('three')->bind(fn($it) => Result::Error('back to two')); | ||||
|         $this->assertTrue($result->isError(), 'The result should have been Error'); | ||||
|         $this->assertEquals('back to two', $result->getError(), 'The bound function was not called'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Bind succeeds for Error')]
 | ||||
|     public function testBindSucceedsForError(): void | ||||
|     { | ||||
|         $original = Result::Error('oops'); | ||||
|         $result   = $original->bind(fn($it) => Result::OK('never mind - it worked!')); | ||||
|         $this->assertTrue($result->isError(), 'The result should have been Error'); | ||||
|         $this->assertSame($original, $result, 'The same Error result should have been returned'); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds for Error result')]
 | ||||
|     public function testContainsSucceedsForErrorResult(): void | ||||
|     { | ||||
|         $this->assertFalse(Result::Error('ouch')->contains('ouch'), '"Error" should always return false'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds for OK result when strictly equal')]
 | ||||
|     public function testContainsSucceedsForOKResultWhenStrictlyEqual(): void | ||||
|     { | ||||
|         $this->assertTrue(Result::OK(18)->contains(18), '"OK" with strict equality should be true'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds for OK result when strictly unequal')]
 | ||||
|     public function testContainsSucceedsForOKResultWhenStrictlyUnequal(): void | ||||
|     { | ||||
|         $this->assertFalse(Result::OK(18)->contains('18'), '"OK" with strict equality should be false'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds for OK result when loosely equal')]
 | ||||
|     public function testContainsSucceedsForOKResultWhenLooselyEqual(): void | ||||
|     { | ||||
|         $this->assertTrue(Result::OK(18)->contains('18', strict: false), '"OK" with loose equality should be true'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Contains succeeds for OK result when loosely unequal')]
 | ||||
|     public function testContainsSucceedsForOKResultWhenLooselyUnequal(): void | ||||
|     { | ||||
|         $this->assertFalse(Result::OK(18)->contains(17, strict: false), '"OK" with loose equality should be false'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Exists succeeds for OK result when matching')]
 | ||||
|     public function testExistsSucceedsForOKResultWhenMatching(): void | ||||
|     { | ||||
|         $this->assertTrue(Result::OK(14)->exists(fn($it) => $it < 100), 'Exists should have returned true'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Exists succeeds for OK result when not matching')]
 | ||||
|     public function testExistsSucceedsForOKResultWhenNotMatching(): void | ||||
|     { | ||||
|         $this->assertFalse(Result::OK(14)->exists(fn($it) => $it > 100), 'Exists should have returned false'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('Exists succeeds for Error result')]
 | ||||
|     public function testExistsSucceedsForErrorResult(): void | ||||
|     { | ||||
|         $this->assertFalse(Result::Error(true)->exists(fn($it) => true), 'Exists 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('ToArray succeeds for OK result')]
 | ||||
|     public function testToArraySucceedsForOKResult(): void | ||||
|     { | ||||
|         $arr = Result::OK('yay')->toArray(); | ||||
|         $this->assertNotNull($arr, 'The array should not have been null'); | ||||
|         $this->assertEquals(['yay'], $arr, 'The array was not created correctly'); | ||||
|     } | ||||
| 
 | ||||
|     #[TestDox('ToArray succeeds for Error result')]
 | ||||
|     public function testToArraySucceedsForErrorResult(): void | ||||
|     { | ||||
|         $arr = Result::Error('oh no')->toArray(); | ||||
|         $this->assertNotNull($arr, 'The array should not have been null'); | ||||
|         $this->assertEmpty($arr, 'The array should have been empty'); | ||||
|     } | ||||
| 
 | ||||
|     #[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(), '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(), '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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										256
									
								
								tests/Unit/OptionTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								tests/Unit/OptionTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,256 @@ | ||||
| <?php | ||||
| /** | ||||
|  * @author Daniel J. Summers <daniel@bitbadger.solutions> | ||||
|  * @license MIT | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| use BitBadger\InspiredByFSharp\Option; | ||||
| use PhpOption\{None, Some}; | ||||
| 
 | ||||
| describe('->get()', function () { | ||||
|     test('retrieves the value for Some', function () { | ||||
|         expect(Option::Some(9)->get())->toBe(9); | ||||
|     }); | ||||
|     test('throws an exception for None', function () { | ||||
|         expect(fn() => Option::None()->get())->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->isNone()', function () { | ||||
|     test('returns true for None', function () { | ||||
|         expect(Option::None()->isNone())->toBeTrue(); | ||||
|     }); | ||||
|     test('returns false for Some', function () { | ||||
|         expect(Option::Some(8)->isNone())->toBeFalse(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->isSome()', function () { | ||||
|     test('returns false for None', function () { | ||||
|         expect(Option::None()->isSome())->toBeFalse(); | ||||
|     }); | ||||
|     test('returns true for Some', function () { | ||||
|         expect(Option::Some('boo')->isSome())->toBeTrue(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->getOrDefault()', function () { | ||||
|     test('returns default value for None', function () { | ||||
|         expect(Option::None()->getOrDefault('yes'))->toBe('yes'); | ||||
|     }); | ||||
|     test('returns option value for Some', function () { | ||||
|         expect(Option::Some('no')->getOrDefault('yes'))->toBe('no'); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->getOrCall()', function () { | ||||
|     test('returns value from callable for None', function () { | ||||
|         expect(Option::None()->getOrCall(fn() => 'called'))->toBe('called'); | ||||
|     }); | ||||
|     test('returns option value for Some', function () { | ||||
|         expect(Option::Some('passed')->getOrCall(fn() => 'called'))->toBe('passed'); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->getOrThrow()', function () { | ||||
|     test('throws an exception for None', function () { | ||||
|         expect(fn() => Option::None()->getOrThrow(fn() => throw new BadMethodCallException())) | ||||
|             ->toThrow(BadMethodCallException::class); | ||||
|     }); | ||||
|     test('returns option value for Some', function () { | ||||
|         expect(Option::Some('no throw')->getOrThrow(fn() => throw new BadMethodCallException()))->toBe('no throw'); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->bind()', function () { | ||||
|     test('returns None when binding against None', function () { | ||||
|         $original = Option::None(); | ||||
|         $bound = $original->bind(fn($it) => Option::Some('value')); | ||||
|         expect($bound)->isNone()->toBeTrue()->and($bound)->toBe($original); | ||||
|     }); | ||||
|     test('returns Some when binding against Some with Some', function () { | ||||
|         expect(Option::Some('hello')->bind(fn($it) => Option::Some('goodbye'))) | ||||
|             ->isSome()->toBeTrue()->get()->toBe('goodbye'); | ||||
|     }); | ||||
|     test('returns None when binding against Some with None', function () { | ||||
|         expect(Option::Some('greetings')->bind(fn($it) => Option::None()))->isNone()->toBeTrue(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->contains()', function () { | ||||
|     test('returns false for None', function () { | ||||
|         expect(Option::None()->contains(null))->toBeFalse(); | ||||
|     }); | ||||
|     test('returns true for Some when strict equality is matched', function () { | ||||
|         expect(Option::Some(3)->contains(3))->toBeTrue(); | ||||
|     }); | ||||
|     test('returns false for Some when strict equality is not matched', function () { | ||||
|         expect(Option::Some('3')->contains(3))->toBeFalse(); | ||||
|     }); | ||||
|     test('returns true for Some when loose equality is matched', function () { | ||||
|         expect(Option::Some('3')->contains(3, strict: false))->toBeTrue(); | ||||
|     }); | ||||
|     test('returns false for Some when loose equality is not matched', function () { | ||||
|         expect(Option::Some('3')->contains(4, strict: false))->toBeFalse(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->exists()', function () { | ||||
|     test('returns false for None', function () { | ||||
|         expect(Option::None()->exists(fn($it) => true))->toBeFalse(); | ||||
|     }); | ||||
|     test('returns true for Some and matching condition', function () { | ||||
|         expect(Option::Some(14)->exists(fn($it) => $it < 100))->toBeTrue(); | ||||
|     }); | ||||
|     test('returns false for Some and non-matching condition', function () { | ||||
|         expect(Option::Some(14)->exists(fn($it) => $it > 100))->toBeFalse(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->map()', function () { | ||||
|     test('does nothing for None', function () { | ||||
|         $tattle = new class { public bool $called = false; }; | ||||
|         $none   = Option::None(); | ||||
|         $mapped = $none->map(function () use ($tattle) | ||||
|         { | ||||
|             $tattle->called = true; | ||||
|             return 'hello'; | ||||
|         }); | ||||
|         expect($mapped) | ||||
|             ->isNone()->toBeTrue() | ||||
|             ->and($tattle->called)->toBeFalse() | ||||
|             ->and($mapped)->toBe($none); | ||||
|     }); | ||||
|     test('maps value for Some', function () { | ||||
|         expect(Option::Some('abc ')->map(fn($it) => str_repeat($it, 2))) | ||||
|             ->isSome()->toBeTrue()->get()->toBe('abc abc '); | ||||
|     }); | ||||
|     test('throws an exception if mapping returns null', function () { | ||||
|         expect(fn() => Option::Some('oof')->map(fn($it) => null))->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->iter()', function () { | ||||
|     test('does nothing for None', function () { | ||||
|         $target = new class { public mixed $called = null; }; | ||||
|         Option::None()->iter(function () use ($target) { $target->called = 'uh oh'; }); | ||||
|         expect($target->called)->toBeNull(); | ||||
|     }); | ||||
|     test('iterates for Some', function () { | ||||
|         $target = new class { public mixed $called = null; }; | ||||
|         Option::Some(33)->iter(function ($it) use ($target) { $target->called = $it; }); | ||||
|         expect($target->called)->toBe(33); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->filter()', function () { | ||||
|     test('does nothing for None', function () { | ||||
|         $tattle   = new class { public bool $called = false; }; | ||||
|         $none     = Option::None(); | ||||
|         $filtered = $none->filter(function () use ($tattle) | ||||
|         { | ||||
|             $tattle->called = true; | ||||
|             return true; | ||||
|         }); | ||||
|         expect($filtered) | ||||
|             ->isNone()->toBeTrue() | ||||
|             ->and($tattle->called)->toBeFalse() | ||||
|             ->and($filtered)->toBe($none); | ||||
|     }); | ||||
|     test('returns Some when filter is matched', function () { | ||||
|         $some     = Option::Some(12); | ||||
|         $filtered = $some->filter(fn($it) => $it % 2 === 0); | ||||
|         expect($filtered) | ||||
|             ->isSome()->toBeTrue() | ||||
|             ->get()->toBe(12) | ||||
|             ->and($filtered)->toBe($some); | ||||
|     }); | ||||
|     test('returns None when filter is not matched', function () { | ||||
|         expect(Option::Some(23)->filter(fn($it) => $it % 2 === 0)->isNone())->toBeTrue(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->unwrap()', function () { | ||||
|     test('returns null for None', function () { | ||||
|         expect(Option::None()->unwrap())->toBeNull(); | ||||
|     }); | ||||
|     test('returns option value for Some', function () { | ||||
|         expect(Option::Some('boy howdy')->unwrap())->toBe('boy howdy'); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->tap()', function () { | ||||
|     test('is called for None', function () { | ||||
|         $value    = ''; | ||||
|         $original = Option::None(); | ||||
|         $tapped   = $original->tap( | ||||
|             function (Option $it) use (&$value) { $value = $it->isSome() ? $it->get() : 'none'; }); | ||||
|         expect($value)->toBe('none')->and($original)->toBe($tapped); | ||||
|     }); | ||||
|     test('is called for Some', function () { | ||||
|         $value    = ''; | ||||
|         $original = Option::Some('testing'); | ||||
|         $tapped   = $original->tap( | ||||
|             function (Option $it) use (&$value) { $value = $it->isSome() ? $it->get() : 'none'; }); | ||||
|         expect($value)->toBe('testing')->and($original)->toBe($tapped); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->toArray()', function () { | ||||
|     test('returns empty array for None', function () { | ||||
|         expect(Option::None()->toArray())->not->toBeNull()->toBeEmpty(); | ||||
|     }); | ||||
|     test('returns one-item array for Some', function () { | ||||
|         expect(Option::Some('15')->toArray())->not->toBeNull()->toBe(['15']); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->toPhpOption()', function () { | ||||
|     test('converts None', function () { | ||||
|         expect(Option::None()->toPhpOption()) | ||||
|             ->toBeInstanceOf('PhpOption\None') | ||||
|             ->not->toBeNull() | ||||
|             ->isDefined()->toBeFalse(); | ||||
|     }); | ||||
|     test('converts Some', function () { | ||||
|         expect(Option::Some('php')->toPhpOption()) | ||||
|             ->toBeInstanceOf('PhpOption\Some') | ||||
|             ->not->toBeNull() | ||||
|             ->isDefined()->toBeTrue() | ||||
|             ->get()->toBe('php'); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('::Some()', function () { | ||||
|     test('creates a Some option when given a value', function () { | ||||
|         expect(Option::Some('hello'))->not->toBeNull()->isSome()->toBeTrue(); | ||||
|     }); | ||||
|     test('throws an exception when given null', function () { | ||||
|         expect(fn() => Option::Some(null))->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('::None()', function () { | ||||
|     test('creates a None option', function () { | ||||
|         expect(Option::None())->not->toBeNull()->isNone()->toBeTrue(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('::of()', function () { | ||||
|     test('creates a None option when given null', function () { | ||||
|         expect(Option::of(null))->not->toBeNull()->isNone()->toBeTrue(); | ||||
|     }); | ||||
|     test('creates a Some option when given a value', function () { | ||||
|         expect(Option::of('test'))->not->toBeNull() | ||||
|             ->isSome()->toBeTrue() | ||||
|             ->get()->toBe('test'); | ||||
|     }); | ||||
|     test('creates a None option when given PhpOption\None', function () { | ||||
|         expect(Option::of(None::create()))->isNone()->toBeTrue(); | ||||
|     }); | ||||
|     test('creates a Some option when given PhpOption\Some', function () { | ||||
|         expect(Option::of(Some::create('something')))->isSome()->toBeTrue()->get()->toBe('something'); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										206
									
								
								tests/Unit/ResultTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								tests/Unit/ResultTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,206 @@ | ||||
| <?php | ||||
| /** | ||||
|  * @author Daniel J. Summers <daniel@bitbadger.solutions> | ||||
|  * @license MIT | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| use BitBadger\InspiredByFSharp\{Option, Result}; | ||||
| 
 | ||||
| describe('->getOK()', function () { | ||||
|     test('returns OK value for OK result', function () { | ||||
|         expect(Result::OK('yay')->getOK())->toBe('yay'); | ||||
|     }); | ||||
|     test('throws an exception for Error result', function () { | ||||
|         expect(fn() => Result::Error('whoops')->getOK())->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->getError()', function () { | ||||
|     test('throws an exception for OK result', function () { | ||||
|         expect(fn() => Result::OK('yeah')->getError())->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
|     test('returns Error value for Error result', function () { | ||||
|         expect(Result::Error('boo')->getError())->toBe('boo'); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->isOK()', function () { | ||||
|     test('returns true for OK result', function () { | ||||
|         expect(Result::OK('ok')->isOK())->toBeTrue(); | ||||
|     }); | ||||
|     test('returns false for Error result', function () { | ||||
|         expect(Result::Error('error')->isOK())->toBeFalse(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->isError()', function () { | ||||
|     test('returns false for OK result', function () { | ||||
|         expect(Result::OK('fine')->isError())->toBeFalse(); | ||||
|     }); | ||||
|     test('returns true for Error result', function () { | ||||
|         expect(Result::Error('not ok')->isError())->toBeTrue(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->bind()', function () { | ||||
|     test('returns OK when binding against OK with OK', function () { | ||||
|         expect(Result::OK('one')->bind(fn($it) => Result::OK("$it two"))) | ||||
|             ->isOK()->toBeTrue()->getOK()->toBe('one two'); | ||||
|     }); | ||||
|     test('returns Error when binding against OK with Error', function () { | ||||
|         expect(Result::OK('three')->bind(fn($it) => Result::Error('back to two'))) | ||||
|             ->isError()->toBeTrue()->getError()->toBe('back to two'); | ||||
|     }); | ||||
|     test('returns Error when binding against Error', function () { | ||||
|         $original = Result::Error('oops'); | ||||
|         $result   = $original->bind(fn($it) => Result::OK('never mind - it worked!')); | ||||
|         expect($result->isError())->toBeTrue() | ||||
|             ->and($result)->toBe($original); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->contains()', function () { | ||||
|     test('returns true when OK is strictly equal', function () { | ||||
|         expect(Result::OK(18)->contains(18))->toBeTrue(); | ||||
|     }); | ||||
|     test('returns false when OK is not strictly equal', function () { | ||||
|         expect(Result::OK(18)->contains('18'))->toBeFalse(); | ||||
|     }); | ||||
|     test('returns true when OK is loosely equal', function () { | ||||
|         expect(Result::OK(18)->contains('18', strict: false))->toBeTrue(); | ||||
|     }); | ||||
|     test('returns false when OK is not loosely equal', function () { | ||||
|         expect(Result::OK(18)->contains(17, strict: false))->toBeFalse(); | ||||
|     }); | ||||
|     test('returns false for Error', function () { | ||||
|         expect(Result::Error('ouch')->contains('ouch'))->toBeFalse(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->exists()', function () { | ||||
|     test('returns true for OK when condition matches', function () { | ||||
|         expect(Result::OK(14)->exists(fn($it) => $it < 100))->toBeTrue(); | ||||
|     }); | ||||
|     test('returns false for OK when condition does not match', function () { | ||||
|         expect(Result::OK(14)->exists(fn($it) => $it > 100))->toBeFalse(); | ||||
|     }); | ||||
|     test('returns false for Error', function () { | ||||
|         expect(Result::Error(true)->exists(fn($it) => true))->toBeFalse(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->map()', function () { | ||||
|     test('maps value for OK', function () { | ||||
|         expect(Result::OK('yard')->map(fn($it) => strrev($it))) | ||||
|             ->isOK()->toBeTrue()->getOK()->toBe('dray'); | ||||
|     }); | ||||
|     test('throws an exception for OK when mapping result is null', function () { | ||||
|         expect(fn() => Result::OK('not null')->map(fn($it) => null))->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
|     test('does nothing for Error', function () { | ||||
|         $tattle = new class { public bool $called = false; }; | ||||
|         $error  = Result::Error('nope'); | ||||
|         $mapped = $error->map(function () use ($tattle) | ||||
|         { | ||||
|             $tattle->called = true; | ||||
|             return 'hello'; | ||||
|         }); | ||||
|         expect($mapped) | ||||
|             ->isError()->toBeTrue() | ||||
|             ->and($tattle->called)->toBeFalse() | ||||
|             ->and($mapped)->toBe($error); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->mapError()', function () { | ||||
|     test('does nothing for OK', function () { | ||||
|         $tattle = new class { public bool $called = false; }; | ||||
|         $ok     = Result::OK('sure'); | ||||
|         $mapped = $ok->mapError(function () use ($tattle) | ||||
|         { | ||||
|             $tattle->called = true; | ||||
|             return 'hello'; | ||||
|         }); | ||||
|         expect($mapped) | ||||
|             ->isOK()->toBeTrue() | ||||
|             ->and($tattle->called)->toBeFalse() | ||||
|             ->and($mapped)->toBe($ok); | ||||
|     }); | ||||
|     test('maps value for Error', function () { | ||||
|         expect(Result::Error('taco')->mapError(fn($it) => str_repeat('*', strlen($it)))) | ||||
|             ->isError()->toBeTrue()->getError()->toBe('****'); | ||||
|     }); | ||||
|     test('throws an exception for Error when mapping result is null', function () { | ||||
|         expect(fn() => Result::Error('pizza')->mapError(fn($it) => null))->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->iter()', function () { | ||||
|     test('iterates for OK', function () { | ||||
|         $target = new class { public mixed $called = null; }; | ||||
|         Result::OK(77)->iter(function ($it) use ($target) { $target->called = $it; }); | ||||
|         expect($target->called)->toBe(77); | ||||
|     }); | ||||
|     test('does nothing for Error', function () { | ||||
|         $target = new class { public mixed $called = null; }; | ||||
|         Result::Error('')->iter(function () use ($target) { $target->called = 'uh oh'; }); | ||||
|         expect($target->called)->toBeNull(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->toArray()', function () { | ||||
|     test('returns a one-item array for OK', function () { | ||||
|         expect(Result::OK('yay')->toArray())->not->toBeNull()->toBe(['yay']); | ||||
|     }); | ||||
|     test('returns an empty array for Error', function () { | ||||
|         expect(Result::Error('oh no')->toArray())->not->toBeNull()->toBeEmpty(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->toOption()', function () { | ||||
|     test('returns a Some option for OK', function () { | ||||
|         expect(Result::OK(99)->toOption())->isSome()->toBeTrue()->get()->toBe(99); | ||||
|     }); | ||||
|     test('returns a None option for Error', function () { | ||||
|         expect(Result::Error('file not found')->toOption())->isNone()->toBeTrue(); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('->tap()', function () { | ||||
|     test('is called for OK', function () { | ||||
|         $value = ''; | ||||
|         $original = Result::OK('working'); | ||||
|         $tapped = $original->tap(function (Result $it) use (&$value) { | ||||
|             $value = $it->isOK() ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError(); | ||||
|         }); | ||||
|         expect($value)->toBe('OK: working')->and($tapped)->toBe($original); | ||||
|     }); | ||||
|     test('is called for Error', function () { | ||||
|         $value    = ''; | ||||
|         $original = Result::Error('failed'); | ||||
|         $tapped   = $original->tap(function (Result $it) use (&$value) { | ||||
|             $value = $it->isOK() ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError(); | ||||
|         }); | ||||
|         expect($value)->toBe('Error: failed')->and($tapped)->toBe($original); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('::OK()', function () { | ||||
|     test('creates an OK result for a non-null value', function () { | ||||
|         expect(Result::OK('something'))->isOK()->toBeTrue()->getOK()->toBe('something'); | ||||
|     }); | ||||
|     test('throws an exception for OK with a null value', function () { | ||||
|         expect(fn() => Result::OK(null))->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| describe('::Error()', function () { | ||||
|     test('creates an Error result for a non-null value', function () { | ||||
|         expect(Result::Error('sad trombone'))->isError()->toBeTrue()->getError()->toBe('sad trombone'); | ||||
|     }); | ||||
|     test('throws an exception for Error with a null value', function () { | ||||
|         expect(fn() => Result::Error(null))->toThrow(InvalidArgumentException::class); | ||||
|     }); | ||||
| }); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user