120 lines
3.8 KiB
TypeScript
120 lines
3.8 KiB
TypeScript
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<AddressProps> {
|
|
readonly props: AddressProps;
|
|
|
|
constructor(street: string, city: string, zipCode: string) {
|
|
this.props = { street, city, zipCode };
|
|
}
|
|
|
|
equals(other: ValueObject<AddressProps>): 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);
|
|
});
|
|
});
|