kojima/tests/result.test.js
2025-07-03 09:34:08 -04:00

276 lines
6.4 KiB
JavaScript

import { Result, Ok, Err } from '../src/result.js'
import { UnwrapError } from '../src/error.js'
import { Option } from '../src/option.js'
import { describe, it, mock } from 'node:test'
import assert from 'node:assert/strict'
const ok = v => new Ok(v)
const err = e => new Err(e)
const eq = assert.deepStrictEqual.bind(assert)
describe('Result', () => {
it('Result.ok should return an Ok value', async () => {
eq(Result.ok(1), new Ok(1))
// fantasy-land/of
eq(Result['fantasy-land/of'](1), new Ok(1))
})
it('Ok.bind(fn) should call fn', async () => {
const a = ok(1)
const fn = x => ok(x * 2)
let res = a.bind(fn)
eq(res, ok(2))
// fantasy-land/chain
res = a['fantasy-land/chain'](fn)
eq(res, ok(2))
// static-land chain
res = Result.chain(fn, a)
eq(res, ok(2))
})
it('Err.bind(fn) should not execute fn', async () => {
const fn = mock.fn(v => ok(v * 2))
const e = err(new Error('failed'))
let res = e.bind(fn)
eq(res, err(new Error('failed')))
eq(fn.mock.callCount(), 0)
// fantasy-land/chain
res = e['fantasy-land/chain'](fn)
eq(res, err(new Error('failed')))
eq(fn.mock.callCount(), 0)
// static-land chain
res = Result.chain(fn, e)
eq(res, err(new Error('failed')))
eq(fn.mock.callCount(), 0)
})
it('Ok.map(fn) should transform the inner value', () => {
const a = ok(1)
const fn = x => x + 1
let res = a.map(fn)
eq(res, ok(2))
// fantasy-land/map
res = a['fantasy-land/map'](fn)
eq(res, ok(2))
// static-land map
res = Result.map(fn, a)
eq(res, ok(2))
})
it('Err.map(fn) should not execute fn', async () => {
const fn = mock.fn(v => Result.ok(v * 2))
const e = err(new Error('failed'))
let res = e.map(fn)
eq(res, err(new Error('failed')))
eq(fn.mock.callCount(), 0)
// fantasy-land/chain
res = e['fantasy-land/map'](fn)
eq(res, err(new Error('failed')))
eq(fn.mock.callCount(), 0)
// static-land chain
res = Result.map(fn, e)
eq(res, err(new Error('failed')))
eq(fn.mock.callCount(), 0)
})
it('Ok.and(other)', () => {
const a = ok(1)
const b = ok(2)
assert.strictEqual(a.and(b), b)
})
it('Err.and(other)', () => {
const a = err(1)
const b = ok(2)
assert.strictEqual(a.and(b), a)
})
it('Ok.alt(other) should return itself', () => {
const self = ok(1)
const other = ok(2)
let res = self.alt(other)
assert.strictEqual(res, self)
// fantasy-land/alt
res = self['fantasy-land/alt'](other)
assert.strictEqual(res, self)
// static-land alt
res = Result.alt(other, self)
assert.strictEqual(res, self)
})
it('Err.alt(other) should return other', () => {
const self = err(new Error('failure'))
const other = ok(2)
let res = self.alt(other)
assert.strictEqual(res, other)
// fantasy-land/alt
res = self['fantasy-land/alt'](other)
assert.strictEqual(res, other)
// static-land alt
res = Result.alt(other, self)
assert.strictEqual(res, other)
})
it('Result.orElse(fn)', () => {
const sq = x => ok(x * x)
eq(ok(2).orElse(sq).orElse(sq), ok(2))
eq(ok(2).orElse(err).orElse(sq), ok(2))
eq(err(3).orElse(sq).orElse(err), ok(9))
eq(err(3).orElse(err).orElse(err), err(3))
})
it('Ok.ok()', () => {
eq(ok(1).ok(), Option.some(1))
})
it('Err.ok()', () => {
eq(err(1).ok(), Option.none())
})
it('Ok.err()', () => {
eq(ok(1).err(), Option.none())
})
it('Err.err()', () => {
eq(err(1).err(), Option.some(1))
})
it('Ok.equals(other)', () => {
const a = ok(1)
assert.ok(a.equals(ok(1)))
assert(!a.equals(ok(2)))
assert(!a.equals(err(1)))
// fantasy-land/equals
assert.ok(a['fantasy-land/equals'](ok(1)))
// static-land equals
assert.ok(Result.equals(ok(1), a))
})
it('Err.equals(other)', () => {
const a = err(1)
assert.ok(a.equals(err(1)))
assert(!a.equals(err(2)))
assert(!a.equals(ok(1)))
// fantasy-land/equals
assert.ok(a['fantasy-land/equals'](err(1)))
// static-land equals
assert.ok(Result.equals(err(1), a))
})
it('Ok.lte(other)', () => {
const a = ok(1)
assert.ok(a.lte(ok(1)))
assert.ok(a.lte(ok(2)))
assert.ok(!a.lte(ok(0)))
assert(!a.lte(err(1)))
// fantasy-land/lte
assert.ok(a['fantasy-land/lte'](ok(1)))
// static-land lte
assert.ok(Result.lte(ok(1), a))
})
it('Err.lte(other)', () => {
const a = err(1)
assert.ok(a.lte(err(1)))
assert.ok(a.lte(err(2)))
assert.ok(!a.lte(err(0)))
assert(!a.lte(ok(1)))
// fantasy-land/lte
assert.ok(a['fantasy-land/lte'](err(1)))
// static-land lte
assert.ok(Result.lte(err(1), a))
})
it('Ok.flatten()', async () => {
eq(ok(1).flatten(), ok(1))
eq(ok(ok(1)).flatten(), ok(1))
eq(ok(ok(ok(1))).flatten(), ok(ok(1)))
eq(ok(err(1)).flatten(), err(1))
})
it('Err.flatten()', async () => {
eq(err(1).flatten(), err(1))
eq(err(err(1)).flatten(), err(1))
})
it('Ok.inspect(fn) should call fn', async () => {
const fn = mock.fn(_x => { })
ok(1).inspect(fn)
eq(fn.mock.callCount(), 1)
})
it('Err.inspect(fn) should not call fn', async () => {
const fn = mock.fn(_x => { })
err(1).inspect(fn)
eq(fn.mock.callCount(), 0)
})
it('Ok.unwrap() returns inner value', async () => {
const a = ok(1)
assert.doesNotThrow(a.unwrap.bind(a), UnwrapError)
eq(a.unwrap(), 1)
})
it('Err.unwrap() throws UnwrapError', async () => {
assert.throws(err(1).unwrap, UnwrapError)
})
it('Ok.unwrapErr() throws UnwrapError', async () => {
const a = ok(1)
assert.throws(a.unwrapErr, UnwrapError)
})
it('Err.unwrapErr() returns inner error', async () => {
const a = err(1)
assert.doesNotThrow(a.unwrapErr.bind(a), UnwrapError)
eq(a.unwrapErr(), 1)
})
it('Ok.unwrapOr(value) returns inner value', async () => {
eq(ok(1).unwrapOr(2), 1)
})
it('Err.unwrapOr(value) returns value', async () => {
eq(err(1).unwrapOr(2), 2)
})
it('Ok.unwrapOrElse(fn) returns inner value', async () => {
const fn = mock.fn(() => 2)
const a = ok(1)
eq(a.unwrapOrElse(fn), 1)
eq(fn.mock.callCount(), 0)
})
it('Err.unwrapOrElse(fn) returns result of fn', async () => {
const fn = mock.fn(() => 2)
const a = err(1)
eq(a.unwrapOrElse(fn), 2)
eq(fn.mock.callCount(), 1)
})
})