inspired-by-fsharp/tests/Unit/ResultTest.php

207 lines
7.6 KiB
PHP
Raw Normal View History

2024-11-21 01:27:18 +00:00
<?php
/**
* @author Daniel J. Summers <daniel@bitbadger.solutions>
* @license MIT
*/
declare(strict_types=1);
use BitBadger\InspiredByFSharp\{Option, Result};
describe('->ok', function () {
test('returns OK value for OK result', function () {
expect(Result::OK('yay')->ok)->toBe('yay');
});
test('throws an exception for Error result', function () {
expect(fn() => Result::Error('whoops')->ok)->toThrow(InvalidArgumentException::class);
});
});
describe('->error', function () {
test('throws an exception for OK result', function () {
expect(fn() => Result::OK('yeah')->error)->toThrow(InvalidArgumentException::class);
});
test('returns Error value for Error result', function () {
expect(Result::Error('boo')->error)->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()->ok->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()->error->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()->ok->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()->error->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()->value->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->ok : 'Error: ' . $it->error;
});
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->ok : 'Error: ' . $it->error;
});
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()->ok->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()->error->toBe('sad trombone');
});
test('throws an exception for Error with a null value', function () {
expect(fn() => Result::Error(null))->toThrow(InvalidArgumentException::class);
});
});