257 lines
9.2 KiB
PHP
257 lines
9.2 KiB
PHP
|
<?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');
|
||
|
});
|
||
|
});
|