import { describe, it, expect } from 'vitest'; import { ValueObject, ValueObjectAlias } from './ValueObject'; // Concrete implementation for testing class TestValueObject implements ValueObject<{ name: string; value: number }> { readonly props: { name: string; value: number }; constructor(name: string, value: number) { this.props = { name, value }; } equals(other: ValueObject<{ name: string; value: number }>): boolean { if (!other) return false; return ( this.props.name === other.props.name && this.props.value === other.props.value ); } } describe('ValueObject', () => { describe('ValueObject interface', () => { it('should have readonly props property', () => { const vo = new TestValueObject('test', 42); expect(vo.props).toEqual({ name: 'test', value: 42 }); }); it('should have equals method', () => { const vo1 = new TestValueObject('test', 42); const vo2 = new TestValueObject('test', 42); const vo3 = new TestValueObject('different', 42); expect(vo1.equals(vo2)).toBe(true); expect(vo1.equals(vo3)).toBe(false); }); it('should return false when comparing with undefined', () => { const vo = new TestValueObject('test', 42); // Testing that equals method handles undefined gracefully const result = vo.equals as any; expect(result(undefined)).toBe(false); }); it('should return false when comparing with null', () => { const vo = new TestValueObject('test', 42); // Testing that equals method handles null gracefully const result = vo.equals as any; expect(result(null)).toBe(false); }); }); describe('ValueObjectAlias type', () => { it('should be assignable to ValueObject', () => { const vo: ValueObjectAlias<{ name: string }> = { props: { name: 'test' }, equals: (other) => other.props.name === 'test', }; expect(vo.props.name).toBe('test'); expect(vo.equals(vo)).toBe(true); }); }); }); describe('ValueObject behavior', () => { it('should support complex value objects', () => { interface AddressProps { street: string; city: string; zipCode: string; } class Address implements ValueObject { readonly props: AddressProps; constructor(street: string, city: string, zipCode: string) { this.props = { street, city, zipCode }; } equals(other: ValueObject): boolean { return ( this.props.street === other.props.street && this.props.city === other.props.city && this.props.zipCode === other.props.zipCode ); } } const address1 = new Address('123 Main St', 'New York', '10001'); const address2 = new Address('123 Main St', 'New York', '10001'); const address3 = new Address('456 Oak Ave', 'Boston', '02101'); expect(address1.equals(address2)).toBe(true); expect(address1.equals(address3)).toBe(false); }); it('should support immutable value objects', () => { class ImmutableValueObject implements ValueObject<{ readonly data: string[] }> { readonly props: { readonly data: string[] }; constructor(data: string[]) { this.props = { data: [...data] }; // Create a copy to ensure immutability } equals(other: ValueObject<{ readonly data: string[] }>): boolean { return ( this.props.data.length === other.props.data.length && this.props.data.every((item, index) => item === other.props.data[index]) ); } } const vo1 = new ImmutableValueObject(['a', 'b', 'c']); const vo2 = new ImmutableValueObject(['a', 'b', 'c']); const vo3 = new ImmutableValueObject(['a', 'b', 'd']); expect(vo1.equals(vo2)).toBe(true); expect(vo1.equals(vo3)).toBe(false); }); });