add js-fp3.tests
This commit is contained in:
@@ -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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user