object-deep-compare
Version:
A type-safe collection of comparison methods for objects and arrays in TypeScript/JavaScript
238 lines (200 loc) • 7.45 kB
text/typescript
import * as deepCompare from '../index';
import { TypedComparisonResult, TypedDetailedDifference, TypeSafeComparisonOptions } from '../src/types';
describe('Type-Safe Comparison Tests', () => {
// Define interfaces for type testing
interface User {
id: number;
name: string;
email: string;
role?: string;
[key: string]: unknown;
}
interface SimpleUser {
userId: number;
username: string;
userEmail: string;
[key: string]: unknown;
}
interface Customer {
id: number;
name: string;
email: string;
subscription: {
plan: string;
active: boolean;
};
[key: string]: unknown;
}
let user: User;
let simpleUser: SimpleUser;
let customer: Customer;
beforeEach(() => {
user = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
role: 'admin'
};
simpleUser = {
userId: 1,
username: 'John Doe',
userEmail: 'john@example.com'
};
customer = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
subscription: {
plan: 'premium',
active: true
}
};
});
describe('TypeSafeCompareArrays', () => {
it('should compare arrays of different types with type information', () => {
const array1 = [1, 2, 3];
const array2 = ['1', '2', '3'];
// With strict comparison
const result1 = deepCompare.TypeSafeCompareArrays(array1, array2);
expect(result1.isEqual).toBe(false);
expect(result1.firstType).toBe('array');
expect(result1.secondType).toBe('array');
// With non-strict comparison
const result2 = deepCompare.TypeSafeCompareArrays(array1, array2, { strict: false });
expect(result2.isEqual).toBe(true);
});
it('should handle arrays with objects', () => {
const array1 = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
const array2 = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
const result = deepCompare.TypeSafeCompareArrays(array1, array2);
expect(result.isEqual).toBe(true);
});
});
describe('TypeSafeCompareObjects', () => {
it('should compare objects of different types', () => {
// Create mapping for different property names
const options: TypeSafeComparisonOptions<SimpleUser, User> = {
propertyMapping: {
userId: 'id',
username: 'name',
userEmail: 'email'
},
// Set strict to false to handle property type differences
strict: false
};
const result = deepCompare.TypeSafeCompareObjects(simpleUser, user, options);
// Since the first object still has properties that don't exist in the second, it's not equal
// The property mapping only adds mapped properties, doesn't remove unmapped ones
expect(result.isEqual).toBe(false);
expect(result.firstType).toBe('object');
expect(result.secondType).toBe('object');
// When we create objects with only the mapped properties, they should be equal
const simpleUserMappedOnly = {
userId: 1,
username: 'John Doe',
userEmail: 'john@example.com'
};
const userEquivalent = {
id: 1,
name: 'John Doe',
email: 'john@example.com'
};
const mappedResult = deepCompare.TypeSafeCompareObjects(simpleUserMappedOnly, userEquivalent, options);
expect(mappedResult.isEqual).toBe(true);
});
it('should fail when objects are not compatible', () => {
const result = deepCompare.TypeSafeCompareObjects(simpleUser, user);
expect(result.isEqual).toBe(false);
});
it('should handle nested objects', () => {
const extendedUser = {
...user,
settings: {
theme: 'dark',
notifications: true
}
};
const result = deepCompare.TypeSafeCompareObjects(user, extendedUser);
expect(result.isEqual).toBe(false);
});
});
describe('TypeSafeCompareValuesWithDetailedDifferences', () => {
it('should provide detailed differences with type information', () => {
const options: TypeSafeComparisonOptions<User, Customer> = {
includeTypeInfo: true
};
const result = deepCompare.TypeSafeCompareValuesWithDetailedDifferences(user, customer, options);
// Should find 2 differences: role missing, subscription added
expect(result.length).toBe(2);
// Verify first difference (role missing)
const roleDiff = result.find(diff => diff.path === 'role');
expect(roleDiff).toBeDefined();
expect(roleDiff?.type).toBe('removed');
expect(roleDiff?.oldValue).toBe('admin');
expect(roleDiff?.oldValueType).toBe('string');
// Verify second difference (subscription added)
const subscriptionDiff = result.find(diff => diff.path === 'subscription');
expect(subscriptionDiff).toBeDefined();
expect(subscriptionDiff?.type).toBe('added');
expect(subscriptionDiff?.newValueType).toBe('object');
});
});
describe('ObjectsAreEqual type guard', () => {
it('should act as a TypeScript type guard', () => {
// Create an object that has all properties from both types
const combinedObject = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
role: 'admin',
subscription: {
plan: 'premium',
active: true
}
};
const someObject: User | Customer = combinedObject;
// We expect the objects to be equal (since combinedObject has all properties of customer)
const isEqual = deepCompare.ObjectsAreEqual(someObject, customer);
// The objects should be equal
expect(isEqual).toBe(true);
// If they are equal, TypeScript narrows the type
if (isEqual) {
// TypeScript should now know that someObject is compatible with Customer
expect(someObject.subscription).toBeDefined();
// In the combined object, the role should also be defined
expect(someObject.role).toBeDefined();
}
});
it('should correctly identify unequal objects', () => {
const modifiedCustomer = { ...customer, id: 999 };
if (deepCompare.ObjectsAreEqual(user, modifiedCustomer)) {
// This branch should not execute in this test case
expect(true).toBe(false);
} else {
expect(true).toBe(true);
}
});
});
describe('IsSubset', () => {
it('should check if an object is a subset of another', () => {
const userSubset = {
id: 1,
name: 'John Doe'
};
expect(deepCompare.IsSubset(user, userSubset)).toBe(true);
expect(deepCompare.IsSubset(userSubset, user)).toBe(false);
});
});
describe('GetCommonStructure', () => {
it('should identify common structure between different objects', () => {
const common = deepCompare.GetCommonStructure(user, customer);
// Should have id, name, and email properties
expect(common).toHaveProperty('id');
expect(common).toHaveProperty('name');
expect(common).toHaveProperty('email');
// Should not have role or subscription properties
expect(common).not.toHaveProperty('role');
expect(common).not.toHaveProperty('subscription');
});
});
});