Add Option and tests
This commit is contained in:
		
							parent
							
								
									e16bf30dc3
								
							
						
					
					
						commit
						bfc27ccef5
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | .idea | ||||||
|  | vendor | ||||||
							
								
								
									
										38
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | { | ||||||
|  |   "name": "bit-badger/inspired-by-fsharp", | ||||||
|  |   "description": "PHP utility classes whose functionality is inspired by their F# counterparts", | ||||||
|  |   "keywords": ["option", "result"], | ||||||
|  |   "license": "MIT", | ||||||
|  |   "authors": [ | ||||||
|  |     { | ||||||
|  |       "name": "Daniel J. Summers", | ||||||
|  |       "email": "daniel@bitbadger.solutions", | ||||||
|  |       "homepage": "https://bitbadger.solutions", | ||||||
|  |       "role": "Developer" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "support": { | ||||||
|  |     "email": "daniel@bitbadger.solutions", | ||||||
|  |     "source": "https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp", | ||||||
|  |     "rss": "https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp.rss" | ||||||
|  |   }, | ||||||
|  |   "require": { | ||||||
|  |     "php": "^8" | ||||||
|  |   }, | ||||||
|  |   "require-dev": { | ||||||
|  |     "phpunit/phpunit": "^11" | ||||||
|  |   }, | ||||||
|  |   "autoload": { | ||||||
|  |     "psr-4": { | ||||||
|  |       "BitBadger\\InspiredByFSharp\\": "./src" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "autoload-dev": { | ||||||
|  |     "psr-4": { | ||||||
|  |       "Test\\": "./tests" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "archive": { | ||||||
|  |     "exclude": [ "/tests", "/.gitattributes", "/.gitignore", "/.git", "/composer.lock" ] | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										1653
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1653
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										205
									
								
								src/Option.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								src/Option.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | |||||||
|  | <?php | ||||||
|  | /** | ||||||
|  |  * @author Daniel J. Summers <daniel@bitbadger.solutions> | ||||||
|  |  * @license MIT | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | declare(strict_types=1); | ||||||
|  | 
 | ||||||
|  | namespace BitBadger\InspiredByFSharp; | ||||||
|  | 
 | ||||||
|  | use InvalidArgumentException; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Option represents a value that may (or may not) exist. | ||||||
|  |  * | ||||||
|  |  * Idiomatic F# does not use `null`; rather, values that can be present or missing are represented as the `Option` type.
 | ||||||
|  |  * `Option`s can be `Some` or `None`, and they can be treated as collections with zero or one item (so functions like | ||||||
|  |  * `map` and `iter` become de facto conditional operators). | ||||||
|  |  * | ||||||
|  |  * `Option::Some(T)` and `Option::None()` create instances. `get()` is available on options, but will throw an exception | ||||||
|  |  * if called on a `None` option. The remaining functions are statically available, and should be provided an `Option` | ||||||
|  |  * instance as their final parameter. | ||||||
|  |  * | ||||||
|  |  * @template T The type of value represented by this option | ||||||
|  |  */ | ||||||
|  | readonly class Option | ||||||
|  | { | ||||||
|  |     /** @var ?T $value The value for this option  */ | ||||||
|  |     private mixed $value; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @param T|null $value The possibly null value for this option | ||||||
|  |      */ | ||||||
|  |     private function __construct(mixed $value = null) | ||||||
|  |     { | ||||||
|  |         $this->value = $value; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the value of this option | ||||||
|  |      * | ||||||
|  |      * @return T The value of the option | ||||||
|  |      */ | ||||||
|  |     public function get(): mixed | ||||||
|  |     { | ||||||
|  |         return match (true) { | ||||||
|  |             self::isSome($this) => $this->value, | ||||||
|  |             default             => throw new InvalidArgumentException('Cannot get the value of a None option'), | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a `Some` option with the given value | ||||||
|  |      * | ||||||
|  |      * @param T $value The value for the option | ||||||
|  |      * @return Option<T> The `Some` option with the given value | ||||||
|  |      */ | ||||||
|  |     public static function Some(mixed $value): self | ||||||
|  |     { | ||||||
|  |         if (is_null($value)) { | ||||||
|  |             throw new InvalidArgumentException('Cannot create a Some option with null'); | ||||||
|  |         } | ||||||
|  |         return new self($value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a `None` option | ||||||
|  |      * | ||||||
|  |      * @return Option<T> A `None` option | ||||||
|  |      */ | ||||||
|  |     public static function None(): self | ||||||
|  |     { | ||||||
|  |         return new self(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create an option from a value | ||||||
|  |      * | ||||||
|  |      * @param ?T $value The possibly null value from which an option should be constructed | ||||||
|  |      * @return Option<T> The optional value | ||||||
|  |      */ | ||||||
|  |     public static function of(mixed $value): self | ||||||
|  |     { | ||||||
|  |         return match (true) { | ||||||
|  |             // TODO: can we do this check without requiring this package?
 | ||||||
|  |             // $value instanceof PhpOption => $value->isDefined() ? self::Some($value->get()) : self::None(),
 | ||||||
|  |             default                     => new self($value), | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Does this option have a `None` value? | ||||||
|  |      * | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      * @return bool True if the option is `None`, false if it is `Some` | ||||||
|  |      */ | ||||||
|  |     public static function isNone(Option $it): bool | ||||||
|  |     { | ||||||
|  |         return is_null($it->value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Does this option have a `Some` value? | ||||||
|  |      * | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      * @return bool True if the option is `Some`, false if it is `None` | ||||||
|  |      */ | ||||||
|  |     public static function isSome(Option $it): bool | ||||||
|  |     { | ||||||
|  |         return !self::isNone($it); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the value, or a default value, from an option | ||||||
|  |      * | ||||||
|  |      * @param T $default The default value to return if the option is `None` | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      * @return T The `Some` value, or the default value if the option is `None` | ||||||
|  |      */ | ||||||
|  |     public static function defaultValue(mixed $default, Option $it): mixed | ||||||
|  |     { | ||||||
|  |         return self::isSome($it) ? $it->get() : $default; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the value, or return the value of a callable function
 | ||||||
|  |      * | ||||||
|  |      * @template U The return type of the callable provided | ||||||
|  |      * @param callable(): U $f The callable function to use for `None` options | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      * @return T|mixed The value if `Some`, the result of the callable if `None` | ||||||
|  |      */ | ||||||
|  |     public static function getOrCall(callable $f, Option $it): mixed | ||||||
|  |     { | ||||||
|  |         return self::isSome($it) ? $it->get() : $f(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Map this optional value to another value | ||||||
|  |      * | ||||||
|  |      * @template U The type of the mapping function
 | ||||||
|  |      * @param callable(T): U $f The mapping function
 | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      * @return Option<U> A `Some` instance with the transformed value if `Some`, `None` otherwise | ||||||
|  |      */ | ||||||
|  |     public static function map(callable $f, Option $it): self | ||||||
|  |     { | ||||||
|  |         return self::isSome($it) ? self::Some($f($it->get())) : self::None(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Execute a function on the value (if it exists) | ||||||
|  |      * | ||||||
|  |      * @param callable(T): void $f The function to call | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      */ | ||||||
|  |     public static function iter(callable $f, Option $it): void | ||||||
|  |     { | ||||||
|  |         if (self::isSome($it)) { | ||||||
|  |             $f($it->get()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Transform an option into `None` if it does not match the given function
 | ||||||
|  |      * | ||||||
|  |      * @param callable(T): bool $f The filter function to run | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      * @return Option<T> The option, if it was `Some` and the function returned true; `None` otherwise | ||||||
|  |      */ | ||||||
|  |     public static function filter(callable $f, Option $it): self | ||||||
|  |     { | ||||||
|  |         return match (true) { | ||||||
|  |             self::isNone($it) => self::None(), | ||||||
|  |             default           => $f($it->get()) ? self::Some($it->get()) : self::None(), | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Does the option have the given value? | ||||||
|  |      * | ||||||
|  |      * @param T $value The value to be checked | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      * @param bool $strict True for strict equality (`===`), false for loose equality (`==`) | ||||||
|  |      * @return bool True if the value matches, false if not; `None` always returns false | ||||||
|  |      */ | ||||||
|  |     public static function is(mixed $value, Option $it, bool $strict = true): bool | ||||||
|  |     { | ||||||
|  |         return match (true) { | ||||||
|  |             self::isNone($it) => false, | ||||||
|  |             default           => $strict ? $it->value === $value : $it->value == $value, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Safely retrieve the optional value as a nullable value | ||||||
|  |      * | ||||||
|  |      * @param Option<T> $it The option in question | ||||||
|  |      * @return ?T The value for `Some` instances, `null` for `None` instances | ||||||
|  |      */ | ||||||
|  |     public static function unwrap(Option $it): mixed | ||||||
|  |     { | ||||||
|  |         return self::isSome($it) ? $it->get() : null; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										240
									
								
								tests/OptionTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								tests/OptionTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,240 @@ | |||||||
|  | <?php | ||||||
|  | /** | ||||||
|  |  * @author Daniel J. Summers <daniel@bitbadger.solutions> | ||||||
|  |  * @license MIT | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | declare(strict_types=1); | ||||||
|  | 
 | ||||||
|  | namespace Test; | ||||||
|  | 
 | ||||||
|  | use BitBadger\InspiredByFSharp\Option; | ||||||
|  | use InvalidArgumentException; | ||||||
|  | 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(Option::isSome($it), '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(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function testSomeSucceedsWithValue(): void | ||||||
|  |     { | ||||||
|  |         $it = Option::Some('hello'); | ||||||
|  |         $this->assertTrue(Option::isSome($it), '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(Option::isNone($it), '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'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function testOfSucceedsWithNonNull(): void | ||||||
|  |     { | ||||||
|  |         $it = Option::of('test'); | ||||||
|  |         $this->assertTrue(Option::isSome($it), 'A non-null value should have created a "Some" option'); | ||||||
|  |         $this->assertEquals('test', $it->get(), 'The value was not assigned correctly'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[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'); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user