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

228 lines
5.6 KiB
JavaScript

import { Option, Some, None } from '../src/option.js'
import { UnwrapError } from '../src/error.js'
import { Result } from '../src/result.js'
import { describe, it, mock } from 'node:test'
import assert from 'node:assert/strict'
const some = v => new Some(v)
const eq = assert.deepStrictEqual.bind(assert)
describe('Option', () => {
it('Option.some should return a Some', async () => {
eq(Option.some(1), new Some(1))
// fantasy-land/of
eq(Option['fantasy-land/of'](1), new Some(1))
})
it('Some.bind(fn) should call fn', async () => {
const a = some(1)
let res = a.bind(x => some(x * 2))
eq(res, some(2))
// fantasy-land/chain
res = a['fantasy-land/chain'](x => some(x * 2))
eq(res, some(2))
})
it('None.bind(fn) should not execute fn', async () => {
const fn = mock.fn(v => some(v * 2))
let res = None.bind(fn)
eq(res, None)
eq(fn.mock.callCount(), 0)
// fantasy-land/chain
res = None['fantasy-land/chain'](fn)
eq(res, None)
eq(fn.mock.callCount(), 0)
})
it('Some.map(fn) should transform the inner value', () => {
const a = some(1)
let res = a.map(x => x + 1)
eq(res, some(2))
// fantasy-land/map
res = a['fantasy-land/map'](x => x + 1)
eq(res, some(2))
})
it('None.map(fn) should not execute fn', async () => {
const fn = mock.fn(v => some(v * 2))
let res = None.map(fn)
eq(res, None)
eq(fn.mock.callCount(), 0)
// fantasy-land/chain
res = None['fantasy-land/map'](fn)
eq(res, None)
eq(fn.mock.callCount(), 0)
})
it('Some.and(other)', () => {
const a = some(1)
const b = some(2)
assert.strictEqual(a.and(b), b)
})
it('None.and(other)', () => {
assert.strictEqual(None.and(some(1)), None)
})
it('Some.alt(other) should return itself', () => {
const self = some(1)
const other = some(2)
let res = self.alt(other)
assert.strictEqual(res, self)
// fantasy-land/alt
res = self['fantasy-land/alt'](other)
assert.strictEqual(res, self)
})
it('None.alt(other) should return other', () => {
const other = some(2)
let res = None.alt(other)
assert.strictEqual(res, other)
// fantasy-land/alt
res = None['fantasy-land/alt'](other)
assert.strictEqual(res, other)
})
it('Option.orElse()', () => {
const nothing = mock.fn(() => None)
const chocolate = mock.fn(() => some('chocolate'))
eq(some('vanilla').orElse(chocolate), some('vanilla'))
eq(chocolate.mock.callCount(), 0)
eq(None.orElse(chocolate), some('chocolate'))
eq(chocolate.mock.callCount(), 1)
eq(None.orElse(nothing), None)
eq(nothing.mock.callCount(), 1)
})
it('Some.filter(predicate)', () => {
const a = some(1)
assert.strictEqual(a.filter(x => x == 1), a)
assert.strictEqual(a.filter(x => x == 2), None)
// fantasy-land/filter
assert.strictEqual(a['fantasy-land/filter'](x => x == 1), a)
})
it('None.filter(predicate)', () => {
assert.strictEqual(None.filter(x => x == 1), None)
// fantasy-land/filter
assert.strictEqual(None['fantasy-land/filter'](x => x == 1), None)
})
it('Some.okOr(err)', () => {
eq(some(1).okOr(2), Result.ok(1))
})
it('None.okOr(err)', () => {
eq(None.okOr(2), Result.err(2))
})
it('Some.okOrElse(err)', () => {
eq(some(1).okOrElse(() => 2), Result.ok(1))
})
it('None.okOr(err)', () => {
eq(None.okOrElse(() => 2), Result.err(2))
})
it('Some.equals(other)', () => {
const a = some(1)
assert.ok(a.equals(some(1)))
assert(!a.equals(some(2)))
assert(!a.equals(None))
// fantasy-land/equals
assert.ok(a['fantasy-land/equals'](some(1)))
})
it('None.equals(other)', () => {
assert.ok(None.equals(None))
assert(!None.equals(some(1)))
// fantasy-land/equals
assert.ok(None['fantasy-land/equals'](None))
})
it('Some.lte(other)', () => {
const a = some(1)
assert.ok(a.lte(some(1)))
assert.ok(a.lte(some(2)))
assert.ok(!a.lte(some(0)))
assert(!a.lte(None))
// fantasy-land/lte
assert.ok(a['fantasy-land/lte'](some(1)))
})
it('None.lte(other)', () => {
assert(!None.lte(None))
assert(!None.lte(some(1)))
// fantasy-land/lte
assert.ok(!None['fantasy-land/lte'](None))
})
it('Some.flatten()', async () => {
eq(some(1).flatten(), some(1))
eq(some(some(1)).flatten(), some(1))
eq(some(some(some(1))).flatten(), some(some(1)))
eq(some(None).flatten(), None)
})
it('None.flatten()', async () => {
eq(None.flatten(), None)
})
it('Some.inspect(fn) should call fn', async () => {
const fn = mock.fn(_x => { })
some(1).inspect(fn)
eq(fn.mock.callCount(), 1)
})
it('None.inspect(fn) should not call fn', async () => {
const fn = mock.fn(_x => { })
None.inspect(fn)
eq(fn.mock.callCount(), 0)
})
it('Some.unwrap() returns inner value', async () => {
const a = some(1)
assert.doesNotThrow(a.unwrap.bind(a), UnwrapError)
eq(a.unwrap(), 1)
})
it('None.unwrap() throws UnwrapError', async () => {
assert.throws(None.unwrap, UnwrapError)
})
it('Some.unwrapOr(value) returns inner value', async () => {
eq(some(1).unwrapOr(2), 1)
})
it('None.unwrapOr(value) returns value', async () => {
eq(None.unwrapOr(2), 2)
})
it('Some.unwrapOrElse(fn) returns inner value', async () => {
const fn = mock.fn(() => 2)
const a = some(1)
eq(a.unwrapOrElse(fn), 1)
eq(fn.mock.callCount(), 0)
})
it('None.unwrapOrElse(fn) returns result of fn', async () => {
const fn = mock.fn(() => 2)
eq(None.unwrapOrElse(fn), 2)
eq(fn.mock.callCount(), 1)
})
})