initial commit
This commit is contained in:
318
src/option/spec/is.spec.ts
Normal file
318
src/option/spec/is.spec.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Option, Some, None, some, none } from '../';
|
||||
import {fail} from "node:assert";
|
||||
|
||||
describe('Option isSome/isNone - Runtime Tests', () => {
|
||||
describe('isSome() method', () => {
|
||||
it('should return true for Some instances', () => {
|
||||
// Arrange
|
||||
const testCases = [
|
||||
some(42),
|
||||
some('hello'),
|
||||
some(true),
|
||||
some({ id: 1 }),
|
||||
some([1, 2, 3]),
|
||||
some(null),
|
||||
some(undefined),
|
||||
some(0),
|
||||
some(false),
|
||||
some('')
|
||||
];
|
||||
|
||||
testCases.forEach(option => {
|
||||
// Act & Assert
|
||||
expect(option.isSome()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false for None instances', () => {
|
||||
// Arrange
|
||||
const noneOption = none();
|
||||
|
||||
// Act & Assert
|
||||
expect(noneOption.isSome()).toBe(false);
|
||||
});
|
||||
|
||||
it('should enable access to value property after type narrowing', () => {
|
||||
// Arrange
|
||||
const option: Option<string> = some('hello') as Option<string>;
|
||||
|
||||
// Act & Assert
|
||||
if (option.isSome()) {
|
||||
expect(option.value).toBe('hello');
|
||||
expect(typeof option.value).toBe('string');
|
||||
} else {
|
||||
fail('Expected option to be Some');
|
||||
}
|
||||
});
|
||||
|
||||
it('should work with complex objects', () => {
|
||||
// Arrange
|
||||
const complexObject = {
|
||||
id: 1,
|
||||
name: 'test',
|
||||
nested: { data: [1, 2, 3] },
|
||||
fn: (x: number) => x * 2
|
||||
};
|
||||
const option: Option<typeof complexObject> = some(complexObject) as Option<typeof complexObject>;
|
||||
|
||||
// Act & Assert
|
||||
if (option.isSome()) {
|
||||
expect(option.value).toBe(complexObject);
|
||||
expect(option.value.id).toBe(1);
|
||||
expect(option.value.name).toBe('test');
|
||||
expect(option.value.nested.data).toEqual([1, 2, 3]);
|
||||
expect(option.value.fn(5)).toBe(10);
|
||||
} else {
|
||||
fail('Expected option to be Some');
|
||||
}
|
||||
});
|
||||
|
||||
it('should work with falsy values that are still Some', () => {
|
||||
// Arrange
|
||||
const falsy: Option<number | boolean | string | null | undefined>[] = [
|
||||
some(0) as Option<number>,
|
||||
some(false) as Option<boolean>,
|
||||
some('') as Option<string>,
|
||||
some(null) as Option<null>,
|
||||
some(undefined) as Option<undefined>
|
||||
];
|
||||
|
||||
falsy.forEach(option => {
|
||||
// Act & Assert
|
||||
expect(option.isSome()).toBe(true);
|
||||
if (option.isSome()) {
|
||||
// Value should be accessible even if falsy
|
||||
expect(option.value !== undefined || option.value === undefined).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNone() method', () => {
|
||||
it('should return true for None instances', () => {
|
||||
// Arrange
|
||||
const noneOption = none();
|
||||
const noneAsOption: Option<any> = none() as Option<any>;
|
||||
|
||||
// Act & Assert
|
||||
expect(noneOption.isNone()).toBe(true);
|
||||
expect(noneAsOption.isNone()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for Some instances', () => {
|
||||
// Arrange
|
||||
const testCases = [
|
||||
some(42),
|
||||
some('hello'),
|
||||
some(true),
|
||||
some({ id: 1 }),
|
||||
some([1, 2, 3]),
|
||||
some(null),
|
||||
some(undefined),
|
||||
some(0),
|
||||
some(false),
|
||||
some('')
|
||||
];
|
||||
|
||||
testCases.forEach(option => {
|
||||
// Act & Assert
|
||||
expect(option.isNone()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with different generic types', () => {
|
||||
// Arrange
|
||||
const numberNone: Option<number> = none() as Option<number>;
|
||||
const stringNone: Option<string> = none() as Option<string>;
|
||||
const objectNone: Option<{ id: number }> = none() as Option<{ id: number }>;
|
||||
|
||||
// Act & Assert
|
||||
expect(numberNone.isNone()).toBe(true);
|
||||
expect(stringNone.isNone()).toBe(true);
|
||||
expect(objectNone.isNone()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mutual exclusivity', () => {
|
||||
it('should be mutually exclusive for Some instances', () => {
|
||||
// Arrange
|
||||
const testCases = [
|
||||
some(42),
|
||||
some('hello'),
|
||||
some(null),
|
||||
some(undefined),
|
||||
some(false),
|
||||
some(0)
|
||||
];
|
||||
|
||||
testCases.forEach(option => {
|
||||
// Act & Assert
|
||||
expect(option.isSome()).toBe(true);
|
||||
expect(option.isNone()).toBe(false);
|
||||
expect(option.isSome() && option.isNone()).toBe(false);
|
||||
// @ts-expect-error - TypeScript should not allow this
|
||||
expect(option.isSome() || option.isNone()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be mutually exclusive for None instances', () => {
|
||||
// Arrange
|
||||
const noneOption = none();
|
||||
|
||||
// Act & Assert
|
||||
expect(noneOption.isSome()).toBe(false);
|
||||
expect(noneOption.isNone()).toBe(true);
|
||||
// @ts-expect-error - TypeScript should not allow this
|
||||
expect(noneOption.isSome() && noneOption.isNone()).toBe(false);
|
||||
expect(noneOption.isSome() || noneOption.isNone()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Type narrowing in control flow', () => {
|
||||
it('should enable type-safe value access in if statements', () => {
|
||||
// Arrange
|
||||
const option: Option<{ name: string; age: number }> = some({ name: 'John', age: 30 }) as Option<{ name: string; age: number }>;
|
||||
|
||||
// Act & Assert
|
||||
if (option.isSome()) {
|
||||
expect(option.value.name).toBe('John');
|
||||
expect(option.value.age).toBe(30);
|
||||
expect(typeof option.value.name).toBe('string');
|
||||
expect(typeof option.value.age).toBe('number');
|
||||
} else {
|
||||
fail('Expected option to be Some');
|
||||
}
|
||||
});
|
||||
|
||||
it('should work with early returns', () => {
|
||||
// Arrange
|
||||
function processOption(option: Option<number>): string {
|
||||
if (option.isNone()) {
|
||||
return 'none';
|
||||
}
|
||||
return `value: ${option.value}`;
|
||||
}
|
||||
|
||||
const someOption: Option<number> = some(42) as Option<number>;
|
||||
const noneOption: Option<number> = none() as Option<number>;
|
||||
|
||||
// Act
|
||||
const someResult = processOption(someOption);
|
||||
const noneResult = processOption(noneOption);
|
||||
|
||||
// Assert
|
||||
expect(someResult).toBe('value: 42');
|
||||
expect(noneResult).toBe('none');
|
||||
});
|
||||
|
||||
it('should work in complex conditional logic', () => {
|
||||
// Arrange
|
||||
const option1: Option<number> = some(10) as Option<number>;
|
||||
const option2: Option<string> = some('test') as Option<string>;
|
||||
const option3: Option<boolean> = none() as Option<boolean>;
|
||||
|
||||
// Act & Assert
|
||||
if (option1.isSome() && option2.isSome()) {
|
||||
expect(option1.value + option2.value.length).toBe(14);
|
||||
} else {
|
||||
fail('Expected both options to be Some');
|
||||
}
|
||||
|
||||
if (option1.isSome() && option3.isNone()) {
|
||||
expect(option1.value).toBe(10);
|
||||
} else {
|
||||
fail('Expected option1 to be Some and option3 to be None');
|
||||
}
|
||||
});
|
||||
|
||||
it('should work with negated conditions', () => {
|
||||
// Arrange
|
||||
const someOption: Option<string> = some('hello') as Option<string>;
|
||||
const noneOption: Option<string> = none() as Option<string>;
|
||||
|
||||
// Act & Assert
|
||||
if (!someOption.isNone()) {
|
||||
expect(someOption.value).toBe('hello');
|
||||
} else {
|
||||
fail('Expected option to be Some');
|
||||
}
|
||||
|
||||
if (!noneOption.isSome()) {
|
||||
expect(noneOption.isNone()).toBe(true);
|
||||
} else {
|
||||
fail('Expected option to be None');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance and consistency', () => {
|
||||
it('should return consistent results across multiple calls', () => {
|
||||
// Arrange
|
||||
const someOption = some(42);
|
||||
const noneOption = none();
|
||||
|
||||
// Act & Assert - Multiple calls should return same result
|
||||
expect(someOption.isSome()).toBe(someOption.isSome());
|
||||
expect(someOption.isNone()).toBe(someOption.isNone());
|
||||
expect(noneOption.isSome()).toBe(noneOption.isSome());
|
||||
expect(noneOption.isNone()).toBe(noneOption.isNone());
|
||||
});
|
||||
|
||||
it('should be callable without side effects', () => {
|
||||
// Arrange
|
||||
const option = some({ counter: 0 });
|
||||
|
||||
// Act
|
||||
const before = option.value.counter;
|
||||
option.isSome();
|
||||
option.isNone();
|
||||
option.isSome();
|
||||
const after = option.value.counter;
|
||||
|
||||
// Assert
|
||||
expect(before).toBe(after);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle deeply nested objects', () => {
|
||||
// Arrange
|
||||
const deepObject = {
|
||||
level1: {
|
||||
level2: {
|
||||
level3: {
|
||||
value: 'deep'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const option: Option<typeof deepObject> = some(deepObject) as Option<typeof deepObject>;
|
||||
|
||||
// Act & Assert
|
||||
if (option.isSome()) {
|
||||
expect(option.value.level1.level2.level3.value).toBe('deep');
|
||||
} else {
|
||||
fail('Expected option to be Some');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle arrays with mixed types', () => {
|
||||
// Arrange
|
||||
const mixedArray = [1, 'string', true, null, { id: 1 }];
|
||||
const option: Option<typeof mixedArray> = some(mixedArray) as Option<typeof mixedArray>;
|
||||
|
||||
// Act & Assert
|
||||
if (option.isSome()) {
|
||||
expect(option.value).toHaveLength(5);
|
||||
expect(option.value[0]).toBe(1);
|
||||
expect(option.value[1]).toBe('string');
|
||||
expect(option.value[2]).toBe(true);
|
||||
expect(option.value[3]).toBeNull();
|
||||
expect(option.value[4]).toEqual({ id: 1 });
|
||||
} else {
|
||||
fail('Expected option to be Some');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user