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

390 lines
12 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { Query, OkSome, OkNone, ErrNone, okSome, okNone, errNone } from '../';
describe('Query constructors and factories - Runtime Tests', () => {
describe('OkSome constructor', () => {
it('should create OkSome instance with provided value', () => {
// Arrange
const value = 42;
// Act
const result = new OkSome(value);
// Assert
expect(result).toBeInstanceOf(OkSome);
expect(result.value).toBe(value);
});
it('should store different types of values correctly', () => {
// Arrange
const numberValue = 42;
const stringValue = 'hello';
const objectValue = { id: 1, name: 'test' };
const arrayValue = [1, 2, 3];
const functionValue = (x: number) => x * 2;
// Act
const numberOkSome = new OkSome(numberValue);
const stringOkSome = new OkSome(stringValue);
const objectOkSome = new OkSome(objectValue);
const arrayOkSome = new OkSome(arrayValue);
const functionOkSome = new OkSome(functionValue);
// Assert
expect(numberOkSome.value).toBe(numberValue);
expect(stringOkSome.value).toBe(stringValue);
expect(objectOkSome.value).toBe(objectValue);
expect(arrayOkSome.value).toBe(arrayValue);
expect(functionOkSome.value).toBe(functionValue);
});
it('should implement QueryInterface methods', () => {
// Arrange
const instance = new OkSome(42);
// Assert
expect(typeof instance.isSome).toBe('function');
expect(typeof instance.isNone).toBe('function');
expect(typeof instance.isErr).toBe('function');
expect(typeof instance.map).toBe('function');
expect(typeof instance.mapErr).toBe('function');
expect(typeof instance.flatMap).toBe('function');
expect(typeof instance.flatMapErr).toBe('function');
expect(typeof instance.match).toBe('function');
expect(typeof instance.filter).toBe('function');
expect(typeof instance.toNullable).toBe('function');
expect(typeof instance.toUndefined).toBe('function');
expect(typeof instance.toString).toBe('function');
});
it('should correctly identify as OkSome', () => {
// Arrange
const instance = new OkSome(42);
// Act & Assert
expect(instance.isSome()).toBe(true);
expect(instance.isNone()).toBe(false);
expect(instance.isErr()).toBe(false);
});
});
describe('OkNone constructor', () => {
it('should create OkNone instance', () => {
// Act
const result = new OkNone();
// Assert
expect(result).toBeInstanceOf(OkNone);
});
it('should implement QueryInterface methods', () => {
// Arrange
const instance = new OkNone();
// Assert
expect(typeof instance.isSome).toBe('function');
expect(typeof instance.isNone).toBe('function');
expect(typeof instance.isErr).toBe('function');
expect(typeof instance.map).toBe('function');
expect(typeof instance.mapErr).toBe('function');
expect(typeof instance.flatMap).toBe('function');
expect(typeof instance.flatMapErr).toBe('function');
expect(typeof instance.match).toBe('function');
expect(typeof instance.filter).toBe('function');
expect(typeof instance.toNullable).toBe('function');
expect(typeof instance.toUndefined).toBe('function');
expect(typeof instance.toString).toBe('function');
});
it('should correctly identify as OkNone', () => {
// Arrange
const instance = new OkNone();
// Act & Assert
expect(instance.isSome()).toBe(false);
expect(instance.isNone()).toBe(true);
expect(instance.isErr()).toBe(false);
});
});
describe('ErrNone constructor', () => {
it('should create ErrNone instance with provided error', () => {
// Arrange
const error = 'something went wrong';
// Act
const result = new ErrNone(error);
// Assert
expect(result).toBeInstanceOf(ErrNone);
expect(result.err).toBe(error);
});
it('should store different types of errors correctly', () => {
// Arrange
const stringError = 'error';
const numberError = 404;
const objectError = { code: 'NOT_FOUND', message: 'not found' };
const errorInstance = new Error('fail');
// Act
const stringErrNone = new ErrNone(stringError);
const numberErrNone = new ErrNone(numberError);
const objectErrNone = new ErrNone(objectError);
const errorErrNone = new ErrNone(errorInstance);
// Assert
expect(stringErrNone.err).toBe(stringError);
expect(numberErrNone.err).toBe(numberError);
expect(objectErrNone.err).toBe(objectError);
expect(errorErrNone.err).toBe(errorInstance);
});
it('should implement QueryInterface methods', () => {
// Arrange
const instance = new ErrNone('error');
// Assert
expect(typeof instance.isSome).toBe('function');
expect(typeof instance.isNone).toBe('function');
expect(typeof instance.isErr).toBe('function');
expect(typeof instance.map).toBe('function');
expect(typeof instance.mapErr).toBe('function');
expect(typeof instance.flatMap).toBe('function');
expect(typeof instance.flatMapErr).toBe('function');
expect(typeof instance.match).toBe('function');
expect(typeof instance.filter).toBe('function');
expect(typeof instance.toNullable).toBe('function');
expect(typeof instance.toUndefined).toBe('function');
expect(typeof instance.toString).toBe('function');
});
it('should correctly identify as ErrNone', () => {
// Arrange
const instance = new ErrNone('error');
// Act & Assert
expect(instance.isSome()).toBe(false);
expect(instance.isNone()).toBe(false);
expect(instance.isErr()).toBe(true);
});
});
describe('okSome() factory function', () => {
it('should create OkSome instance with provided value', () => {
// Arrange
const value = 42;
// Act
const result = okSome(value);
// Assert
expect(result).toBeInstanceOf(OkSome);
expect(result.value).toBe(value);
});
it('should work with various data types', () => {
// Arrange
const testCases = [
42,
'hello',
true,
{ id: 1, name: 'test' },
[1, 2, 3],
(x: number) => x * 2,
];
testCases.forEach((testValue) => {
// Act
const result = okSome(testValue);
// Assert
expect(result).toBeInstanceOf(OkSome);
expect(result.value).toBe(testValue);
expect(result.isSome()).toBe(true);
expect(result.isNone()).toBe(false);
expect(result.isErr()).toBe(false);
});
});
it('should create new instances for each call', () => {
// Arrange
const value = 42;
// Act
const first = okSome(value);
const second = okSome(value);
// Assert
expect(first).toBeInstanceOf(OkSome);
expect(second).toBeInstanceOf(OkSome);
expect(first).not.toBe(second);
expect(first.value).toBe(second.value);
});
});
describe('okNone() factory function', () => {
it('should return OkNone instance', () => {
// Act
const result = okNone();
// Assert
expect(result).toBeInstanceOf(OkNone);
expect(result.isSome()).toBe(false);
expect(result.isNone()).toBe(true);
expect(result.isErr()).toBe(false);
});
it('should return the same singleton instance', () => {
// Act
const first = okNone();
const second = okNone();
const third = okNone();
// Assert
expect(first).toBe(second);
expect(second).toBe(third);
expect(first).toBe(third);
});
it('should return a frozen instance', () => {
// Act
const instance = okNone();
// Assert
expect(Object.isFrozen(instance)).toBe(true);
});
it('should not accept any arguments', () => {
// Act & Assert
expect(() => okNone()).not.toThrow();
// @ts-expect-error Testing runtime behavior when called incorrectly
expect(() => okNone(42)).not.toThrow();
});
});
describe('errNone() factory function', () => {
it('should create ErrNone instance with provided error', () => {
// Arrange
const error = 'something failed';
// Act
const result = errNone(error);
// Assert
expect(result).toBeInstanceOf(ErrNone);
expect(result.err).toBe(error);
});
it('should work with various error types', () => {
// Arrange
const testCases = [
'error string',
42,
new Error('fail'),
{ code: 'ERR', message: 'failed' },
['err1', 'err2'],
];
testCases.forEach((testError) => {
// Act
const result = errNone(testError);
// Assert
expect(result).toBeInstanceOf(ErrNone);
expect(result.err).toBe(testError);
expect(result.isSome()).toBe(false);
expect(result.isNone()).toBe(false);
expect(result.isErr()).toBe(true);
});
});
it('should create new instances for each call', () => {
// Arrange
const error = 'error';
// Act
const first = errNone(error);
const second = errNone(error);
// Assert
expect(first).not.toBe(second);
expect(first.err).toBe(second.err);
});
});
describe('toString()', () => {
it('should return correct string for OkSome', () => {
// Arrange & Act & Assert
expect(okSome(42).toString()).toBe('OkSome(42)');
expect(okSome('hello').toString()).toBe('OkSome(hello)');
expect(okSome(true).toString()).toBe('OkSome(true)');
});
it('should return correct string for OkNone', () => {
// Arrange & Act & Assert
expect(okNone().toString()).toBe('OkNone()');
});
it('should return correct string for ErrNone', () => {
// Arrange & Act & Assert
expect(errNone('error').toString()).toBe('ErrNone(error)');
expect(errNone(404).toString()).toBe('ErrNone(404)');
});
});
describe('Integration tests', () => {
it('should allow creating mixed arrays of Queries', () => {
// Arrange & Act
const queries: Query<number, string>[] = [
okSome(1) as Query<number, string>,
okSome(2) as Query<number, string>,
okNone() as Query<number, string>,
errNone('error') as Query<number, string>,
okSome(3) as Query<number, string>,
];
// Assert
expect(queries).toHaveLength(5);
expect(queries[0].isSome()).toBe(true);
expect(queries[1].isSome()).toBe(true);
expect(queries[2].isNone()).toBe(true);
expect(queries[3].isErr()).toBe(true);
expect(queries[4].isSome()).toBe(true);
});
it('should enable type narrowing in runtime', () => {
// Arrange
const query: Query<string, number> = okSome('hello') as Query<string, number>;
// Act & Assert
if (query.isSome()) {
expect(query.value).toBe('hello');
expect(typeof query.value).toBe('string');
} else {
fail('Expected query to be OkSome');
}
});
it('should handle edge cases correctly', () => {
// Arrange & Act
const zeroOkSome = okSome(0);
const falseOkSome = okSome(false);
const emptyOkSome = okSome('');
const emptyArrayOkSome = okSome([]);
// Assert - All should be OkSome instances even with "falsy" values
expect(zeroOkSome.isSome()).toBe(true);
expect(falseOkSome.isSome()).toBe(true);
expect(emptyOkSome.isSome()).toBe(true);
expect(emptyArrayOkSome.isSome()).toBe(true);
expect(zeroOkSome.value).toBe(0);
expect(falseOkSome.value).toBe(false);
expect(emptyOkSome.value).toBe('');
expect(emptyArrayOkSome.value).toEqual([]);
});
});
});