* @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'); }); });