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 = some('hello') as Option; // 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 = some(complexObject) as Option; // 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[] = [ some(0) as Option, some(false) as Option, some('') as Option, some(null) as Option, some(undefined) as Option ]; 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 = none() as Option; // 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 = none() as Option; const stringNone: Option = none() as Option; 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): string { if (option.isNone()) { return 'none'; } return `value: ${option.value}`; } const someOption: Option = some(42) as Option; const noneOption: Option = none() as Option; // 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 = some(10) as Option; const option2: Option = some('test') as Option; const option3: Option = none() as Option; // 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 = some('hello') as Option; const noneOption: Option = none() as Option; // 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 = some(deepObject) as Option; // 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 = some(mixedArray) as Option; // 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'); } }); }); });