Files
native-monad/src/result/spec/namespace.spec.ts
2026-03-30 08:34:35 +02:00

511 lines
14 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { Result, ResultInterface, Ok, Err, ok, err } from '../';
describe('Result namespace - Runtime Tests', () => {
describe('Result.from', () => {
it('should return Ok when function succeeds', () => {
// Arrange
const fn = () => 42;
// Act
const result = Result.from(fn);
// Assert
expect(result.isOk()).toBe(true);
if (result.isOk()) {
expect(result.value).toBe(42);
}
});
it('should return Err when function throws', () => {
// Arrange
const error = new Error('test error');
const fn = () => { throw error; };
// Act
const result = Result.from(fn);
// Assert
expect(result.isErr()).toBe(true);
if (result.isErr()) {
expect(result.err).toBe(error);
}
});
it('should apply errorMap when function throws and errorMap is provided', () => {
// Arrange
const fn = () => { throw new Error('original'); };
const errorMap = (e: unknown) => `Mapped: ${(e as Error).message}`;
// Act
const result = Result.from(fn, errorMap);
// Assert
expect(result.isErr()).toBe(true);
if (result.isErr()) {
expect(result.err).toBe('Mapped: original');
}
});
it('should not call errorMap when function succeeds', () => {
// Arrange
const fn = () => 42;
const errorMapSpy = vi.fn((e: unknown) => `Mapped: ${e}`);
// Act
const result = Result.from(fn, errorMapSpy);
// Assert
expect(result.isOk()).toBe(true);
expect(errorMapSpy).not.toHaveBeenCalled();
});
it('should handle JSON.parse correctly', () => {
// Arrange & Act
const validResult = Result.from(() => JSON.parse('{"valid": true}'));
const invalidResult = Result.from(() => JSON.parse('invalid'));
// Assert
expect(validResult.isOk()).toBe(true);
if (validResult.isOk()) {
expect(validResult.value).toEqual({ valid: true });
}
expect(invalidResult.isErr()).toBe(true);
});
it('should handle functions returning falsy values', () => {
// Arrange & Act
const zeroResult = Result.from(() => 0);
const falseResult = Result.from(() => false);
const emptyResult = Result.from(() => '');
const nullResult = Result.from(() => null);
const undefinedResult = Result.from(() => undefined);
// Assert
expect(zeroResult.isOk()).toBe(true);
expect(falseResult.isOk()).toBe(true);
expect(emptyResult.isOk()).toBe(true);
expect(nullResult.isOk()).toBe(true);
expect(undefinedResult.isOk()).toBe(true);
if (zeroResult.isOk()) expect(zeroResult.value).toBe(0);
if (falseResult.isOk()) expect(falseResult.value).toBe(false);
if (emptyResult.isOk()) expect(emptyResult.value).toBe('');
if (nullResult.isOk()) expect(nullResult.value).toBeNull();
if (undefinedResult.isOk()) expect(undefinedResult.value).toBeUndefined();
});
});
describe('Result.isResult', () => {
it('should return true for Ok instances', () => {
// Arrange & Act & Assert
expect(Result.isResult(ok(42))).toBe(true);
expect(Result.isResult(ok('hello'))).toBe(true);
expect(Result.isResult(ok(null))).toBe(true);
expect(Result.isResult(new Ok(42))).toBe(true);
});
it('should return true for Err instances', () => {
// Arrange & Act & Assert
expect(Result.isResult(err('error'))).toBe(true);
expect(Result.isResult(err(404))).toBe(true);
expect(Result.isResult(err(null))).toBe(true);
expect(Result.isResult(new Err('error'))).toBe(true);
});
it('should return false for non-Result values', () => {
// Arrange & Act & Assert
expect(Result.isResult(42)).toBe(false);
expect(Result.isResult('hello')).toBe(false);
expect(Result.isResult(null)).toBe(false);
expect(Result.isResult(undefined)).toBe(false);
expect(Result.isResult(true)).toBe(false);
expect(Result.isResult({ value: 42 })).toBe(false);
expect(Result.isResult([1, 2, 3])).toBe(false);
expect(Result.isResult({ isOk: () => true })).toBe(false);
});
});
describe('Result.all', () => {
it('should return Ok with all values when all results are Ok', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
ok(2) as Result<number, string>,
ok(3) as Result<number, string>,
];
// Act
const result = Result.all(results);
// Assert
expect(result.isOk()).toBe(true);
if (result.isOk()) {
expect(result.value).toEqual([1, 2, 3]);
}
});
it('should return the first Err when any result is Err', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
err('first error') as Result<number, string>,
ok(3) as Result<number, string>,
err('second error') as Result<number, string>,
];
// Act
const result = Result.all(results);
// Assert
expect(result.isErr()).toBe(true);
if (result.isErr()) {
expect(result.err).toBe('first error');
}
});
it('should return Ok with empty array for empty input', () => {
// Arrange
const results: Result<number, string>[] = [];
// Act
const result = Result.all(results);
// Assert
expect(result.isOk()).toBe(true);
if (result.isOk()) {
expect(result.value).toEqual([]);
}
});
it('should handle single element arrays', () => {
// Arrange
const okResults: Result<number, string>[] = [ok(42) as Result<number, string>];
const errResults: Result<number, string>[] = [err('error') as Result<number, string>];
// Act
const okResult = Result.all(okResults);
const errResult = Result.all(errResults);
// Assert
expect(okResult.isOk()).toBe(true);
if (okResult.isOk()) expect(okResult.value).toEqual([42]);
expect(errResult.isErr()).toBe(true);
if (errResult.isErr()) expect(errResult.err).toBe('error');
});
it('should handle complex value types', () => {
// Arrange
type User = { id: number; name: string };
const results: Result<User, string>[] = [
ok({ id: 1, name: 'Alice' }) as Result<User, string>,
ok({ id: 2, name: 'Bob' }) as Result<User, string>,
];
// Act
const result = Result.all(results);
// Assert
expect(result.isOk()).toBe(true);
if (result.isOk()) {
expect(result.value).toEqual([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
}
});
});
describe('Result.partition', () => {
it('should partition results into values and errors', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
err('error1') as Result<number, string>,
ok(3) as Result<number, string>,
err('error2') as Result<number, string>,
];
// Act
const partitioned = Result.partition(results);
// Assert
expect(partitioned.values).toEqual([1, 3]);
expect(partitioned.errors).toEqual(['error1', 'error2']);
});
it('should handle all Ok results', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
ok(2) as Result<number, string>,
];
// Act
const partitioned = Result.partition(results);
// Assert
expect(partitioned.values).toEqual([1, 2]);
expect(partitioned.errors).toEqual([]);
});
it('should handle all Err results', () => {
// Arrange
const results: Result<number, string>[] = [
err('error1') as Result<number, string>,
err('error2') as Result<number, string>,
];
// Act
const partitioned = Result.partition(results);
// Assert
expect(partitioned.values).toEqual([]);
expect(partitioned.errors).toEqual(['error1', 'error2']);
});
it('should handle empty array', () => {
// Arrange
const results: Result<number, string>[] = [];
// Act
const partitioned = Result.partition(results);
// Assert
expect(partitioned.values).toEqual([]);
expect(partitioned.errors).toEqual([]);
});
});
describe('Result.compact', () => {
it('should extract values from Ok instances', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
err('error') as Result<number, string>,
ok(3) as Result<number, string>,
];
// Act
const values = Result.compact(results);
// Assert
expect(values).toEqual([1, 3]);
});
it('should return empty array when all are Err', () => {
// Arrange
const results: Result<number, string>[] = [
err('error1') as Result<number, string>,
err('error2') as Result<number, string>,
];
// Act
const values = Result.compact(results);
// Assert
expect(values).toEqual([]);
});
it('should return all values when all are Ok', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
ok(2) as Result<number, string>,
ok(3) as Result<number, string>,
];
// Act
const values = Result.compact(results);
// Assert
expect(values).toEqual([1, 2, 3]);
});
it('should handle empty array', () => {
// Arrange
const results: Result<number, string>[] = [];
// Act
const values = Result.compact(results);
// Assert
expect(values).toEqual([]);
});
it('should handle falsy Ok values', () => {
// Arrange
const results: Result<number | boolean | string, string>[] = [
ok(0) as Result<number, string>,
ok(false) as Result<boolean, string>,
ok('') as Result<string, string>,
err('error') as Result<number, string>,
];
// Act
const values = Result.compact(results);
// Assert
expect(values).toEqual([0, false, '']);
});
});
describe('Result.some', () => {
it('should return true when at least one result is Ok', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
err('error') as Result<number, string>,
ok(3) as Result<number, string>,
];
// Act
const result = Result.some(results);
// Assert
expect(result).toBe(true);
});
it('should return false when all results are Err', () => {
// Arrange
const results: Result<number, string>[] = [
err('error1') as Result<number, string>,
err('error2') as Result<number, string>,
];
// Act
const result = Result.some(results);
// Assert
expect(result).toBe(false);
});
it('should return false for empty array', () => {
// Arrange
const results: Result<number, string>[] = [];
// Act
const result = Result.some(results);
// Assert
expect(result).toBe(false);
});
it('should return true when all results are Ok', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
ok(2) as Result<number, string>,
];
// Act
const result = Result.some(results);
// Assert
expect(result).toBe(true);
});
});
describe('Result.every', () => {
it('should return true when all results are Ok', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
ok(2) as Result<number, string>,
];
// Act
const result = Result.every(results);
// Assert
expect(result).toBe(true);
});
it('should return false when any result is Err', () => {
// Arrange
const results: Result<number, string>[] = [
ok(1) as Result<number, string>,
err('error') as Result<number, string>,
];
// Act
const result = Result.every(results);
// Assert
expect(result).toBe(false);
});
it('should return true for empty array', () => {
// Arrange
const results: Result<number, string>[] = [];
// Act
const result = Result.every(results);
// Assert
expect(result).toBe(true);
});
it('should return false when all results are Err', () => {
// Arrange
const results: Result<number, string>[] = [
err('error1') as Result<number, string>,
err('error2') as Result<number, string>,
];
// Act
const result = Result.every(results);
// Assert
expect(result).toBe(false);
});
});
describe('Integration tests', () => {
it('should work with Result.from and Result.all together', () => {
// Arrange
const parse = (s: string) => Result.from(() => JSON.parse(s), () => `Invalid JSON: ${s}`);
// Act
const allValid = Result.all([
parse('{"a": 1}'),
parse('{"b": 2}'),
]);
const someInvalid = Result.all([
parse('{"a": 1}'),
parse('invalid'),
]);
// Assert
expect(allValid.isOk()).toBe(true);
if (allValid.isOk()) {
expect(allValid.value).toEqual([{ a: 1 }, { b: 2 }]);
}
expect(someInvalid.isErr()).toBe(true);
if (someInvalid.isErr()) {
expect(someInvalid.err).toBe('Invalid JSON: invalid');
}
});
it('should work with Result.from and Result.partition together', () => {
// Arrange
const inputs = ['1', 'invalid', '3', 'also invalid'];
const results = inputs.map(s =>
Result.from(() => parseInt(s), () => `Bad: ${s}`)
).map((r): Result<number, string> =>
(r as ResultInterface<number, string>).flatMap((n): Result<number, string> => isNaN(n) ? err(`NaN from parse`) : ok(n))
);
// Act
const partitioned = Result.partition(results);
// Assert
expect(partitioned.values).toEqual([1, 3]);
expect(partitioned.errors).toEqual(['NaN from parse', 'NaN from parse']);
});
});
});