371 lines
12 KiB
TypeScript
371 lines
12 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { Result } from './Result';
|
|
|
|
describe('Result', () => {
|
|
describe('Result.ok()', () => {
|
|
it('should create a success result with a value', () => {
|
|
const result = Result.ok('success-value');
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.isErr()).toBe(false);
|
|
expect(result.unwrap()).toBe('success-value');
|
|
});
|
|
|
|
it('should create a success result with undefined value', () => {
|
|
const result = Result.ok(undefined);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.isErr()).toBe(false);
|
|
expect(result.unwrap()).toBe(undefined);
|
|
});
|
|
|
|
it('should create a success result with null value', () => {
|
|
const result = Result.ok(null);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.isErr()).toBe(false);
|
|
expect(result.unwrap()).toBe(null);
|
|
});
|
|
|
|
it('should create a success result with complex object', () => {
|
|
const complexValue = { id: 123, name: 'test', nested: { data: 'value' } };
|
|
const result = Result.ok(complexValue);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual(complexValue);
|
|
});
|
|
|
|
it('should create a success result with array', () => {
|
|
const arrayValue = [1, 2, 3, 'test'];
|
|
const result = Result.ok(arrayValue);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toEqual(arrayValue);
|
|
});
|
|
});
|
|
|
|
describe('Result.err()', () => {
|
|
it('should create an error result with an error', () => {
|
|
const error = new Error('test error');
|
|
const result = Result.err(error);
|
|
|
|
expect(result.isOk()).toBe(false);
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr()).toBe(error);
|
|
});
|
|
|
|
it('should create an error result with string error', () => {
|
|
const result = Result.err('string error');
|
|
|
|
expect(result.isOk()).toBe(false);
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr()).toBe('string error');
|
|
});
|
|
|
|
it('should create an error result with object error', () => {
|
|
const error = { code: 'VALIDATION_ERROR', message: 'Invalid input' };
|
|
const result = Result.err(error);
|
|
|
|
expect(result.isOk()).toBe(false);
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr()).toEqual(error);
|
|
});
|
|
|
|
it('should create an error result with custom error type', () => {
|
|
interface CustomError {
|
|
code: string;
|
|
details: Record<string, unknown>;
|
|
}
|
|
|
|
const error: CustomError = {
|
|
code: 'NOT_FOUND',
|
|
details: { id: '123' }
|
|
};
|
|
|
|
const result = Result.err<unknown, CustomError>(error);
|
|
|
|
expect(result.isOk()).toBe(false);
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr()).toEqual(error);
|
|
});
|
|
});
|
|
|
|
describe('Result.isOk()', () => {
|
|
it('should return true for success results', () => {
|
|
const result = Result.ok('value');
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
|
|
it('should return false for error results', () => {
|
|
const result = Result.err(new Error('error'));
|
|
expect(result.isOk()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Result.isErr()', () => {
|
|
it('should return false for success results', () => {
|
|
const result = Result.ok('value');
|
|
expect(result.isErr()).toBe(false);
|
|
});
|
|
|
|
it('should return true for error results', () => {
|
|
const result = Result.err(new Error('error'));
|
|
expect(result.isErr()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Result.unwrap()', () => {
|
|
it('should return the value for success results', () => {
|
|
const result = Result.ok('test-value');
|
|
expect(result.unwrap()).toBe('test-value');
|
|
});
|
|
|
|
it('should throw error for error results', () => {
|
|
const result = Result.err(new Error('test error'));
|
|
expect(() => result.unwrap()).toThrow('Called unwrap on an error result');
|
|
});
|
|
|
|
it('should return complex values for success results', () => {
|
|
const complexValue = { id: 123, data: { nested: 'value' } };
|
|
const result = Result.ok(complexValue);
|
|
expect(result.unwrap()).toEqual(complexValue);
|
|
});
|
|
|
|
it('should return arrays for success results', () => {
|
|
const arrayValue = [1, 2, 3];
|
|
const result = Result.ok(arrayValue);
|
|
expect(result.unwrap()).toEqual(arrayValue);
|
|
});
|
|
});
|
|
|
|
describe('Result.unwrapOr()', () => {
|
|
it('should return the value for success results', () => {
|
|
const result = Result.ok('actual-value');
|
|
expect(result.unwrapOr('default-value')).toBe('actual-value');
|
|
});
|
|
|
|
it('should return default value for error results', () => {
|
|
const result = Result.err(new Error('error'));
|
|
expect(result.unwrapOr('default-value')).toBe('default-value');
|
|
});
|
|
|
|
it('should return default value when value is undefined', () => {
|
|
const result = Result.ok(undefined);
|
|
expect(result.unwrapOr('default-value')).toBe(undefined);
|
|
});
|
|
|
|
it('should return default value when value is null', () => {
|
|
const result = Result.ok(null);
|
|
expect(result.unwrapOr('default-value')).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('Result.unwrapErr()', () => {
|
|
it('should return the error for error results', () => {
|
|
const error = new Error('test error');
|
|
const result = Result.err(error);
|
|
expect(result.unwrapErr()).toBe(error);
|
|
});
|
|
|
|
it('should throw error for success results', () => {
|
|
const result = Result.ok('value');
|
|
expect(() => result.unwrapErr()).toThrow('Called unwrapErr on a success result');
|
|
});
|
|
|
|
it('should return string errors', () => {
|
|
const result = Result.err('string error');
|
|
expect(result.unwrapErr()).toBe('string error');
|
|
});
|
|
|
|
it('should return object errors', () => {
|
|
const error = { code: 'ERROR', message: 'Something went wrong' };
|
|
const result = Result.err(error);
|
|
expect(result.unwrapErr()).toEqual(error);
|
|
});
|
|
});
|
|
|
|
describe('Result.map()', () => {
|
|
it('should transform success values', () => {
|
|
const result = Result.ok(5);
|
|
const mapped = result.map((x) => x * 2);
|
|
|
|
expect(mapped.isOk()).toBe(true);
|
|
expect(mapped.unwrap()).toBe(10);
|
|
});
|
|
|
|
it('should not transform error results', () => {
|
|
const error = new Error('test error');
|
|
const result = Result.err<number, Error>(error);
|
|
const mapped = result.map((x) => x * 2);
|
|
|
|
expect(mapped.isErr()).toBe(true);
|
|
expect(mapped.unwrapErr()).toBe(error);
|
|
});
|
|
|
|
it('should handle complex transformations', () => {
|
|
const result = Result.ok({ id: 1, name: 'test' });
|
|
const mapped = result.map((obj) => ({ ...obj, name: obj.name.toUpperCase() }));
|
|
|
|
expect(mapped.isOk()).toBe(true);
|
|
expect(mapped.unwrap()).toEqual({ id: 1, name: 'TEST' });
|
|
});
|
|
|
|
it('should handle array transformations', () => {
|
|
const result = Result.ok([1, 2, 3]);
|
|
const mapped = result.map((arr) => arr.map((x) => x * 2));
|
|
|
|
expect(mapped.isOk()).toBe(true);
|
|
expect(mapped.unwrap()).toEqual([2, 4, 6]);
|
|
});
|
|
});
|
|
|
|
describe('Result.mapErr()', () => {
|
|
it('should transform error values', () => {
|
|
const error = new Error('original error');
|
|
const result = Result.err<string, Error>(error);
|
|
const mapped = result.mapErr((e) => new Error(`wrapped: ${e.message}`));
|
|
|
|
expect(mapped.isErr()).toBe(true);
|
|
expect(mapped.unwrapErr().message).toBe('wrapped: original error');
|
|
});
|
|
|
|
it('should not transform success results', () => {
|
|
const result = Result.ok('value');
|
|
const mapped = result.mapErr((e) => new Error(`wrapped: ${e.message}`));
|
|
|
|
expect(mapped.isOk()).toBe(true);
|
|
expect(mapped.unwrap()).toBe('value');
|
|
});
|
|
|
|
it('should handle string error transformations', () => {
|
|
const result = Result.err('error message');
|
|
const mapped = result.mapErr((e) => e.toUpperCase());
|
|
|
|
expect(mapped.isErr()).toBe(true);
|
|
expect(mapped.unwrapErr()).toBe('ERROR MESSAGE');
|
|
});
|
|
|
|
it('should handle object error transformations', () => {
|
|
const error = { code: 'ERROR', message: 'Something went wrong' };
|
|
const result = Result.err(error);
|
|
const mapped = result.mapErr((e) => ({ ...e, code: `WRAPPED_${e.code}` }));
|
|
|
|
expect(mapped.isErr()).toBe(true);
|
|
expect(mapped.unwrapErr()).toEqual({ code: 'WRAPPED_ERROR', message: 'Something went wrong' });
|
|
});
|
|
});
|
|
|
|
describe('Result.andThen()', () => {
|
|
it('should chain success results', () => {
|
|
const result1 = Result.ok(5);
|
|
const result2 = result1.andThen((x) => Result.ok(x * 2));
|
|
|
|
expect(result2.isOk()).toBe(true);
|
|
expect(result2.unwrap()).toBe(10);
|
|
});
|
|
|
|
it('should propagate errors through chain', () => {
|
|
const error = new Error('first error');
|
|
const result1 = Result.err<number, Error>(error);
|
|
const result2 = result1.andThen((x) => Result.ok(x * 2));
|
|
|
|
expect(result2.isErr()).toBe(true);
|
|
expect(result2.unwrapErr()).toBe(error);
|
|
});
|
|
|
|
it('should handle error in chained function', () => {
|
|
const result1 = Result.ok(5);
|
|
const result2 = result1.andThen((x) => Result.err(new Error(`error at ${x}`)));
|
|
|
|
expect(result2.isErr()).toBe(true);
|
|
expect(result2.unwrapErr().message).toBe('error at 5');
|
|
});
|
|
|
|
it('should support multiple chaining steps', () => {
|
|
const result = Result.ok(2)
|
|
.andThen((x) => Result.ok(x * 3))
|
|
.andThen((x) => Result.ok(x + 1))
|
|
.andThen((x) => Result.ok(x * 2));
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBe(14); // ((2 * 3) + 1) * 2 = 14
|
|
});
|
|
|
|
it('should stop chaining on first error', () => {
|
|
const result = Result.ok(2)
|
|
.andThen((x) => Result.ok(x * 3))
|
|
.andThen((x) => Result.err<number, Error>(new Error(`stopped at ${x}`)))
|
|
.andThen((x) => Result.ok(x + 1)); // This should not execute
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr().message).toBe('stopped at 6');
|
|
});
|
|
});
|
|
|
|
describe('Result.value getter', () => {
|
|
it('should return value for success results', () => {
|
|
const result = Result.ok('test-value');
|
|
expect(result.value).toBe('test-value');
|
|
});
|
|
|
|
it('should return undefined for error results', () => {
|
|
const result = Result.err(new Error('error'));
|
|
expect(result.value).toBe(undefined);
|
|
});
|
|
|
|
it('should return undefined for success results with undefined value', () => {
|
|
const result = Result.ok(undefined);
|
|
expect(result.value).toBe(undefined);
|
|
});
|
|
});
|
|
|
|
describe('Result.error getter', () => {
|
|
it('should return error for error results', () => {
|
|
const error = new Error('test error');
|
|
const result = Result.err(error);
|
|
expect(result.error).toBe(error);
|
|
});
|
|
|
|
it('should return undefined for success results', () => {
|
|
const result = Result.ok('value');
|
|
expect(result.error).toBe(undefined);
|
|
});
|
|
|
|
it('should return string errors', () => {
|
|
const result = Result.err('string error');
|
|
expect(result.error).toBe('string error');
|
|
});
|
|
});
|
|
|
|
describe('Result type safety', () => {
|
|
it('should work with custom error codes', () => {
|
|
type MyErrorCode = 'NOT_FOUND' | 'VALIDATION_ERROR' | 'PERMISSION_DENIED';
|
|
|
|
const successResult = Result.ok<string, MyErrorCode>('data');
|
|
const errorResult = Result.err<string, MyErrorCode>('NOT_FOUND');
|
|
|
|
expect(successResult.isOk()).toBe(true);
|
|
expect(errorResult.isErr()).toBe(true);
|
|
});
|
|
|
|
it('should work with ApplicationErrorCode pattern', () => {
|
|
interface ApplicationErrorCode<Code extends string, Details = unknown> {
|
|
code: Code;
|
|
details?: Details;
|
|
}
|
|
|
|
type MyErrorCodes = 'USER_NOT_FOUND' | 'INVALID_EMAIL';
|
|
|
|
const successResult = Result.ok<string, ApplicationErrorCode<MyErrorCodes>>('user');
|
|
const errorResult = Result.err<string, ApplicationErrorCode<MyErrorCodes>>({
|
|
code: 'USER_NOT_FOUND',
|
|
details: { userId: '123' }
|
|
});
|
|
|
|
expect(successResult.isOk()).toBe(true);
|
|
expect(errorResult.isErr()).toBe(true);
|
|
expect(errorResult.unwrapErr().code).toBe('USER_NOT_FOUND');
|
|
});
|
|
});
|
|
});
|