add js-fp3.tests

This commit is contained in:
Oli Sturm
2026-04-27 13:19:17 +01:00
parent 3194231879
commit 8d22f0f1a8
7 changed files with 1191 additions and 0 deletions
+203
View File
@@ -0,0 +1,203 @@
import { describe, it, expect, vi } from 'vitest';
import {
ok,
fail,
catchResult,
bind,
map,
tap,
tapError,
mapError,
match,
switch_,
pipe,
} from '../js-fp3/library/result.js';
describe('ok / fail construction', () => {
it('ok is success', () => {
const result = ok(42);
expect(result.isSuccess).toBe(true);
expect(result.isFailure).toBe(false);
});
it('fail is failure', () => {
const result = fail('oops');
expect(result.isFailure).toBe(true);
expect(result.isSuccess).toBe(false);
});
it('ok exposes value', () => {
const result = ok(42);
expect(result.value).toBe(42);
});
it('fail exposes error', () => {
const result = fail('oops');
expect(result.error).toBe('oops');
});
});
describe('match', () => {
it('calls onSuccess for ok', () => {
const result = ok(10);
const output = match((v) => `value:${v}`, (e) => `error:${e}`)(result);
expect(output).toBe('value:10');
});
it('calls onFailure for fail', () => {
const result = fail('bad');
const output = match((v) => `value:${v}`, (e) => `error:${e}`)(result);
expect(output).toBe('error:bad');
});
});
describe('switch_', () => {
it('calls onSuccess for ok', () => {
const result = ok(7);
let called = false;
switch_(() => { called = true; }, () => {})(result);
expect(called).toBe(true);
});
it('calls onFailure for fail', () => {
const result = fail('err');
let called = false;
switch_(() => {}, () => { called = true; })(result);
expect(called).toBe(true);
});
});
describe('catchResult', () => {
it('returns ok when no exception is thrown', () => {
const result = catchResult(() => 99, (ex) => ex.message);
expect(result.isSuccess).toBe(true);
expect(result.value).toBe(99);
});
it('returns fail when an exception is thrown', () => {
const result = catchResult(
() => { throw new Error('boom'); },
(ex) => ex.message
);
expect(result.isFailure).toBe(true);
expect(result.error).toBe('boom');
});
});
describe('bind', () => {
it('chains to the next result on success', () => {
const result = bind((v) => ok(`got ${v}`))(ok(5));
expect(result.value).toBe('got 5');
});
it('short-circuits on failure without calling the binder', () => {
const binder = vi.fn((v) => ok(`got ${v}`));
const result = bind(binder)(fail('nope'));
expect(binder).not.toHaveBeenCalled();
expect(result.error).toBe('nope');
});
it('propagates the inner failure when the binder returns fail', () => {
const result = bind(() => fail('inner fail'))(ok(5));
expect(result.error).toBe('inner fail');
});
});
describe('map', () => {
it('transforms the value on success', () => {
const result = map((v) => v * 2)(ok(3));
expect(result.value).toBe(6);
});
it('does not call the mapper on failure', () => {
const mapper = vi.fn((v) => v * 2);
const result = map(mapper)(fail('err'));
expect(mapper).not.toHaveBeenCalled();
expect(result.error).toBe('err');
});
});
describe('tap', () => {
it('calls the action and returns the original result on success', () => {
let seen = -1;
const result = tap((v) => { seen = v; })(ok(8));
expect(seen).toBe(8);
expect(result.value).toBe(8);
});
it('does not call the action on failure', () => {
const action = vi.fn();
tap(action)(fail('err'));
expect(action).not.toHaveBeenCalled();
});
});
describe('tapError', () => {
it('calls the action and returns the original result on failure', () => {
let seen = '';
const result = tapError((e) => { seen = e; })(fail('bad'));
expect(seen).toBe('bad');
expect(result.error).toBe('bad');
});
it('does not call the action on success', () => {
const action = vi.fn();
tapError(action)(ok(1));
expect(action).not.toHaveBeenCalled();
});
});
describe('mapError', () => {
it('transforms the error on failure', () => {
const result = mapError((e) => e.length)(fail('oops'));
expect(result.error).toBe(4);
});
it('does not call the mapper on success', () => {
const mapper = vi.fn((e) => e.length);
const result = mapError(mapper)(ok(1));
expect(mapper).not.toHaveBeenCalled();
expect(result.value).toBe(1);
});
});
describe('pipe', () => {
it('applies functions left to right starting from the initial result', () => {
const result = pipe(ok(2), map((v) => v + 1), map((v) => v * 3));
expect(result.value).toBe(9);
});
it('short-circuits on the first failure', () => {
const mapper = vi.fn((v) => v * 2);
const result = pipe(fail('stop'), map(mapper));
expect(mapper).not.toHaveBeenCalled();
expect(result.error).toBe('stop');
});
});