mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
- Add test/setup.ts with max 10 concurrent screen sessions limiter - Add orphaned Claude/screen process cleanup before/after tests - Add semaphore-based screen slot acquisition for concurrency control - Update vitest.config.ts with setupFiles and fileParallelism: false - Add 16 new test files for comprehensive coverage - Update README badge to show 1337 total tests - Update CLAUDE.md with test setup documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
644 lines
20 KiB
TypeScript
644 lines
20 KiB
TypeScript
/**
|
|
* @fileoverview Tests for validation utilities
|
|
*
|
|
* Tests input validation, type checking, and schema validation
|
|
* patterns used throughout the application.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
describe('Validation Utilities', () => {
|
|
describe('Type Guards', () => {
|
|
const isString = (value: unknown): value is string => {
|
|
return typeof value === 'string';
|
|
};
|
|
|
|
const isNumber = (value: unknown): value is number => {
|
|
return typeof value === 'number' && !isNaN(value);
|
|
};
|
|
|
|
const isBoolean = (value: unknown): value is boolean => {
|
|
return typeof value === 'boolean';
|
|
};
|
|
|
|
const isArray = <T>(value: unknown, itemGuard?: (item: unknown) => item is T): value is T[] => {
|
|
if (!Array.isArray(value)) return false;
|
|
if (itemGuard) {
|
|
return value.every(itemGuard);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const isObject = (value: unknown): value is Record<string, unknown> => {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
};
|
|
|
|
it('should detect strings', () => {
|
|
expect(isString('hello')).toBe(true);
|
|
expect(isString('')).toBe(true);
|
|
expect(isString(123)).toBe(false);
|
|
expect(isString(null)).toBe(false);
|
|
});
|
|
|
|
it('should detect numbers', () => {
|
|
expect(isNumber(123)).toBe(true);
|
|
expect(isNumber(0)).toBe(true);
|
|
expect(isNumber(-5.5)).toBe(true);
|
|
expect(isNumber(NaN)).toBe(false);
|
|
expect(isNumber('123')).toBe(false);
|
|
});
|
|
|
|
it('should detect booleans', () => {
|
|
expect(isBoolean(true)).toBe(true);
|
|
expect(isBoolean(false)).toBe(true);
|
|
expect(isBoolean(1)).toBe(false);
|
|
expect(isBoolean('true')).toBe(false);
|
|
});
|
|
|
|
it('should detect arrays', () => {
|
|
expect(isArray([])).toBe(true);
|
|
expect(isArray([1, 2, 3])).toBe(true);
|
|
expect(isArray('array')).toBe(false);
|
|
expect(isArray({})).toBe(false);
|
|
});
|
|
|
|
it('should detect typed arrays', () => {
|
|
expect(isArray([1, 2, 3], isNumber)).toBe(true);
|
|
expect(isArray(['a', 'b'], isString)).toBe(true);
|
|
expect(isArray([1, 'a'], isNumber)).toBe(false);
|
|
});
|
|
|
|
it('should detect objects', () => {
|
|
expect(isObject({})).toBe(true);
|
|
expect(isObject({ key: 'value' })).toBe(true);
|
|
expect(isObject(null)).toBe(false);
|
|
expect(isObject([])).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Nullish Checks', () => {
|
|
const isNullish = (value: unknown): value is null | undefined => {
|
|
return value === null || value === undefined;
|
|
};
|
|
|
|
const isNotNullish = <T>(value: T | null | undefined): value is T => {
|
|
return value !== null && value !== undefined;
|
|
};
|
|
|
|
const coalesce = <T>(value: T | null | undefined, defaultValue: T): T => {
|
|
return isNotNullish(value) ? value : defaultValue;
|
|
};
|
|
|
|
it('should detect null', () => {
|
|
expect(isNullish(null)).toBe(true);
|
|
});
|
|
|
|
it('should detect undefined', () => {
|
|
expect(isNullish(undefined)).toBe(true);
|
|
});
|
|
|
|
it('should not detect falsy values as nullish', () => {
|
|
expect(isNullish(0)).toBe(false);
|
|
expect(isNullish('')).toBe(false);
|
|
expect(isNullish(false)).toBe(false);
|
|
});
|
|
|
|
it('should coalesce nullish values', () => {
|
|
expect(coalesce(null, 'default')).toBe('default');
|
|
expect(coalesce(undefined, 'default')).toBe('default');
|
|
expect(coalesce('value', 'default')).toBe('value');
|
|
expect(coalesce(0, 10)).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('String Validation', () => {
|
|
const isNonEmptyString = (value: unknown): value is string => {
|
|
return typeof value === 'string' && value.length > 0;
|
|
};
|
|
|
|
const isEmail = (value: string): boolean => {
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
};
|
|
|
|
const isUrl = (value: string): boolean => {
|
|
try {
|
|
new URL(value);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const isAlphanumeric = (value: string): boolean => {
|
|
return /^[a-zA-Z0-9]+$/.test(value);
|
|
};
|
|
|
|
const isSlug = (value: string): boolean => {
|
|
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
|
|
};
|
|
|
|
it('should validate non-empty strings', () => {
|
|
expect(isNonEmptyString('hello')).toBe(true);
|
|
expect(isNonEmptyString('')).toBe(false);
|
|
expect(isNonEmptyString(null)).toBe(false);
|
|
});
|
|
|
|
it('should validate email format', () => {
|
|
expect(isEmail('user@example.com')).toBe(true);
|
|
expect(isEmail('user+tag@example.co.uk')).toBe(true);
|
|
expect(isEmail('invalid')).toBe(false);
|
|
expect(isEmail('@example.com')).toBe(false);
|
|
});
|
|
|
|
it('should validate URL format', () => {
|
|
expect(isUrl('https://example.com')).toBe(true);
|
|
expect(isUrl('http://localhost:3000')).toBe(true);
|
|
expect(isUrl('not a url')).toBe(false);
|
|
});
|
|
|
|
it('should validate alphanumeric strings', () => {
|
|
expect(isAlphanumeric('abc123')).toBe(true);
|
|
expect(isAlphanumeric('ABC')).toBe(true);
|
|
expect(isAlphanumeric('abc-123')).toBe(false);
|
|
expect(isAlphanumeric('')).toBe(false);
|
|
});
|
|
|
|
it('should validate slugs', () => {
|
|
expect(isSlug('hello-world')).toBe(true);
|
|
expect(isSlug('test123')).toBe(true);
|
|
expect(isSlug('Hello-World')).toBe(false);
|
|
expect(isSlug('-invalid')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Number Validation', () => {
|
|
const isPositive = (value: number): boolean => {
|
|
return value > 0;
|
|
};
|
|
|
|
const isNonNegative = (value: number): boolean => {
|
|
return value >= 0;
|
|
};
|
|
|
|
const isInteger = (value: number): boolean => {
|
|
return Number.isInteger(value);
|
|
};
|
|
|
|
const isInRange = (value: number, min: number, max: number): boolean => {
|
|
return value >= min && value <= max;
|
|
};
|
|
|
|
const isPort = (value: number): boolean => {
|
|
return isInteger(value) && isInRange(value, 1, 65535);
|
|
};
|
|
|
|
it('should validate positive numbers', () => {
|
|
expect(isPositive(1)).toBe(true);
|
|
expect(isPositive(0)).toBe(false);
|
|
expect(isPositive(-1)).toBe(false);
|
|
});
|
|
|
|
it('should validate non-negative numbers', () => {
|
|
expect(isNonNegative(0)).toBe(true);
|
|
expect(isNonNegative(1)).toBe(true);
|
|
expect(isNonNegative(-1)).toBe(false);
|
|
});
|
|
|
|
it('should validate integers', () => {
|
|
expect(isInteger(5)).toBe(true);
|
|
expect(isInteger(5.5)).toBe(false);
|
|
expect(isInteger(-10)).toBe(true);
|
|
});
|
|
|
|
it('should validate range', () => {
|
|
expect(isInRange(5, 1, 10)).toBe(true);
|
|
expect(isInRange(1, 1, 10)).toBe(true);
|
|
expect(isInRange(10, 1, 10)).toBe(true);
|
|
expect(isInRange(0, 1, 10)).toBe(false);
|
|
});
|
|
|
|
it('should validate port numbers', () => {
|
|
expect(isPort(80)).toBe(true);
|
|
expect(isPort(3000)).toBe(true);
|
|
expect(isPort(0)).toBe(false);
|
|
expect(isPort(65536)).toBe(false);
|
|
expect(isPort(3000.5)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Object Validation', () => {
|
|
const hasProperty = <K extends string>(
|
|
obj: unknown,
|
|
key: K
|
|
): obj is Record<K, unknown> => {
|
|
return typeof obj === 'object' && obj !== null && key in obj;
|
|
};
|
|
|
|
const hasRequiredProperties = (
|
|
obj: unknown,
|
|
keys: string[]
|
|
): obj is Record<string, unknown> => {
|
|
if (typeof obj !== 'object' || obj === null) return false;
|
|
return keys.every(key => key in obj);
|
|
};
|
|
|
|
const validateShape = <T>(
|
|
obj: unknown,
|
|
validators: Record<keyof T, (value: unknown) => boolean>
|
|
): obj is T => {
|
|
if (typeof obj !== 'object' || obj === null) return false;
|
|
const record = obj as Record<string, unknown>;
|
|
return Object.entries(validators).every(([key, validate]) => {
|
|
return validate(record[key]);
|
|
});
|
|
};
|
|
|
|
it('should check property existence', () => {
|
|
expect(hasProperty({ name: 'test' }, 'name')).toBe(true);
|
|
expect(hasProperty({ name: 'test' }, 'age')).toBe(false);
|
|
expect(hasProperty(null, 'name')).toBe(false);
|
|
});
|
|
|
|
it('should check required properties', () => {
|
|
const obj = { name: 'test', age: 25 };
|
|
expect(hasRequiredProperties(obj, ['name', 'age'])).toBe(true);
|
|
expect(hasRequiredProperties(obj, ['name', 'email'])).toBe(false);
|
|
});
|
|
|
|
it('should validate object shape', () => {
|
|
const isUser = (obj: unknown) => validateShape(obj, {
|
|
name: (v) => typeof v === 'string',
|
|
age: (v) => typeof v === 'number',
|
|
});
|
|
|
|
expect(isUser({ name: 'John', age: 25 })).toBe(true);
|
|
expect(isUser({ name: 'John' })).toBe(false);
|
|
expect(isUser({ name: 123, age: 25 })).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Array Validation', () => {
|
|
const isNonEmpty = <T>(arr: T[]): arr is [T, ...T[]] => {
|
|
return arr.length > 0;
|
|
};
|
|
|
|
const hasMinLength = <T>(arr: T[], min: number): boolean => {
|
|
return arr.length >= min;
|
|
};
|
|
|
|
const hasMaxLength = <T>(arr: T[], max: number): boolean => {
|
|
return arr.length <= max;
|
|
};
|
|
|
|
const hasLength = <T>(arr: T[], min: number, max?: number): boolean => {
|
|
if (max === undefined) {
|
|
return arr.length === min;
|
|
}
|
|
return arr.length >= min && arr.length <= max;
|
|
};
|
|
|
|
const allUnique = <T>(arr: T[]): boolean => {
|
|
return new Set(arr).size === arr.length;
|
|
};
|
|
|
|
it('should validate non-empty arrays', () => {
|
|
expect(isNonEmpty([1])).toBe(true);
|
|
expect(isNonEmpty([])).toBe(false);
|
|
});
|
|
|
|
it('should validate minimum length', () => {
|
|
expect(hasMinLength([1, 2, 3], 2)).toBe(true);
|
|
expect(hasMinLength([1], 2)).toBe(false);
|
|
});
|
|
|
|
it('should validate maximum length', () => {
|
|
expect(hasMaxLength([1, 2], 3)).toBe(true);
|
|
expect(hasMaxLength([1, 2, 3, 4], 3)).toBe(false);
|
|
});
|
|
|
|
it('should validate exact length', () => {
|
|
expect(hasLength([1, 2, 3], 3)).toBe(true);
|
|
expect(hasLength([1, 2], 3)).toBe(false);
|
|
});
|
|
|
|
it('should validate length range', () => {
|
|
expect(hasLength([1, 2, 3], 2, 5)).toBe(true);
|
|
expect(hasLength([1], 2, 5)).toBe(false);
|
|
expect(hasLength([1, 2, 3, 4, 5, 6], 2, 5)).toBe(false);
|
|
});
|
|
|
|
it('should check uniqueness', () => {
|
|
expect(allUnique([1, 2, 3])).toBe(true);
|
|
expect(allUnique([1, 2, 2])).toBe(false);
|
|
expect(allUnique([])).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Custom Validators', () => {
|
|
interface ValidationResult {
|
|
valid: boolean;
|
|
errors: string[];
|
|
}
|
|
|
|
const createValidator = <T>(
|
|
validations: Array<{
|
|
check: (value: T) => boolean;
|
|
message: string;
|
|
}>
|
|
) => {
|
|
return (value: T): ValidationResult => {
|
|
const errors = validations
|
|
.filter(v => !v.check(value))
|
|
.map(v => v.message);
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
};
|
|
};
|
|
};
|
|
|
|
it('should validate with no errors', () => {
|
|
const validatePassword = createValidator<string>([
|
|
{ check: (v) => v.length >= 8, message: 'Must be at least 8 characters' },
|
|
{ check: (v) => /[A-Z]/.test(v), message: 'Must contain uppercase' },
|
|
{ check: (v) => /[0-9]/.test(v), message: 'Must contain number' },
|
|
]);
|
|
|
|
const result = validatePassword('Password123');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
|
|
it('should collect all errors', () => {
|
|
const validatePassword = createValidator<string>([
|
|
{ check: (v) => v.length >= 8, message: 'Must be at least 8 characters' },
|
|
{ check: (v) => /[A-Z]/.test(v), message: 'Must contain uppercase' },
|
|
{ check: (v) => /[0-9]/.test(v), message: 'Must contain number' },
|
|
]);
|
|
|
|
const result = validatePassword('pass');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Must be at least 8 characters');
|
|
expect(result.errors).toContain('Must contain uppercase');
|
|
expect(result.errors).toContain('Must contain number');
|
|
});
|
|
});
|
|
|
|
describe('Schema Validation', () => {
|
|
type SchemaType = 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
|
|
interface SchemaField {
|
|
type: SchemaType;
|
|
required?: boolean;
|
|
minLength?: number;
|
|
maxLength?: number;
|
|
min?: number;
|
|
max?: number;
|
|
pattern?: RegExp;
|
|
items?: SchemaField;
|
|
properties?: Record<string, SchemaField>;
|
|
}
|
|
|
|
const validateField = (value: unknown, schema: SchemaField): string[] => {
|
|
const errors: string[] = [];
|
|
|
|
// Check required
|
|
if (value === undefined || value === null) {
|
|
if (schema.required) {
|
|
errors.push('Value is required');
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
// Check type
|
|
switch (schema.type) {
|
|
case 'string':
|
|
if (typeof value !== 'string') {
|
|
errors.push('Expected string');
|
|
} else {
|
|
if (schema.minLength && value.length < schema.minLength) {
|
|
errors.push(`Minimum length is ${schema.minLength}`);
|
|
}
|
|
if (schema.maxLength && value.length > schema.maxLength) {
|
|
errors.push(`Maximum length is ${schema.maxLength}`);
|
|
}
|
|
if (schema.pattern && !schema.pattern.test(value)) {
|
|
errors.push('Value does not match pattern');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'number':
|
|
if (typeof value !== 'number') {
|
|
errors.push('Expected number');
|
|
} else {
|
|
if (schema.min !== undefined && value < schema.min) {
|
|
errors.push(`Minimum value is ${schema.min}`);
|
|
}
|
|
if (schema.max !== undefined && value > schema.max) {
|
|
errors.push(`Maximum value is ${schema.max}`);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'boolean':
|
|
if (typeof value !== 'boolean') {
|
|
errors.push('Expected boolean');
|
|
}
|
|
break;
|
|
|
|
case 'array':
|
|
if (!Array.isArray(value)) {
|
|
errors.push('Expected array');
|
|
} else if (schema.items) {
|
|
value.forEach((item, i) => {
|
|
const itemErrors = validateField(item, schema.items!);
|
|
errors.push(...itemErrors.map(e => `[${i}]: ${e}`));
|
|
});
|
|
}
|
|
break;
|
|
|
|
case 'object':
|
|
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
errors.push('Expected object');
|
|
} else if (schema.properties) {
|
|
const obj = value as Record<string, unknown>;
|
|
for (const [key, fieldSchema] of Object.entries(schema.properties)) {
|
|
const fieldErrors = validateField(obj[key], fieldSchema);
|
|
errors.push(...fieldErrors.map(e => `${key}: ${e}`));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return errors;
|
|
};
|
|
|
|
it('should validate strings', () => {
|
|
const schema: SchemaField = { type: 'string', required: true, minLength: 3 };
|
|
expect(validateField('hello', schema)).toHaveLength(0);
|
|
expect(validateField('ab', schema)).toContain('Minimum length is 3');
|
|
expect(validateField(undefined, schema)).toContain('Value is required');
|
|
});
|
|
|
|
it('should validate numbers', () => {
|
|
const schema: SchemaField = { type: 'number', min: 0, max: 100 };
|
|
expect(validateField(50, schema)).toHaveLength(0);
|
|
expect(validateField(-1, schema)).toContain('Minimum value is 0');
|
|
expect(validateField(101, schema)).toContain('Maximum value is 100');
|
|
});
|
|
|
|
it('should validate booleans', () => {
|
|
const schema: SchemaField = { type: 'boolean' };
|
|
expect(validateField(true, schema)).toHaveLength(0);
|
|
expect(validateField('true', schema)).toContain('Expected boolean');
|
|
});
|
|
|
|
it('should validate arrays', () => {
|
|
const schema: SchemaField = {
|
|
type: 'array',
|
|
items: { type: 'number' },
|
|
};
|
|
expect(validateField([1, 2, 3], schema)).toHaveLength(0);
|
|
expect(validateField([1, 'a', 3], schema)).toContain('[1]: Expected number');
|
|
});
|
|
|
|
it('should validate objects', () => {
|
|
const schema: SchemaField = {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string', required: true },
|
|
age: { type: 'number' },
|
|
},
|
|
};
|
|
expect(validateField({ name: 'John', age: 25 }, schema)).toHaveLength(0);
|
|
expect(validateField({ age: 25 }, schema)).toContain('name: Value is required');
|
|
});
|
|
|
|
it('should validate string patterns', () => {
|
|
const schema: SchemaField = {
|
|
type: 'string',
|
|
pattern: /^[a-z]+$/,
|
|
};
|
|
expect(validateField('hello', schema)).toHaveLength(0);
|
|
expect(validateField('Hello', schema)).toContain('Value does not match pattern');
|
|
});
|
|
});
|
|
|
|
describe('Sanitization', () => {
|
|
const sanitizeString = (value: unknown, defaultValue: string = ''): string => {
|
|
if (typeof value === 'string') return value;
|
|
if (value === null || value === undefined) return defaultValue;
|
|
return String(value);
|
|
};
|
|
|
|
const sanitizeNumber = (value: unknown, defaultValue: number = 0): number => {
|
|
if (typeof value === 'number' && !isNaN(value)) return value;
|
|
const parsed = Number(value);
|
|
return isNaN(parsed) ? defaultValue : parsed;
|
|
};
|
|
|
|
const sanitizeBoolean = (value: unknown, defaultValue: boolean = false): boolean => {
|
|
if (typeof value === 'boolean') return value;
|
|
if (value === 'true' || value === '1') return true;
|
|
if (value === 'false' || value === '0') return false;
|
|
return defaultValue;
|
|
};
|
|
|
|
const sanitizeArray = <T>(value: unknown, defaultValue: T[] = []): T[] => {
|
|
if (Array.isArray(value)) return value;
|
|
return defaultValue;
|
|
};
|
|
|
|
it('should sanitize strings', () => {
|
|
expect(sanitizeString('hello')).toBe('hello');
|
|
expect(sanitizeString(123)).toBe('123');
|
|
expect(sanitizeString(null, 'default')).toBe('default');
|
|
});
|
|
|
|
it('should sanitize numbers', () => {
|
|
expect(sanitizeNumber(42)).toBe(42);
|
|
expect(sanitizeNumber('42')).toBe(42);
|
|
expect(sanitizeNumber('invalid', 0)).toBe(0);
|
|
});
|
|
|
|
it('should sanitize booleans', () => {
|
|
expect(sanitizeBoolean(true)).toBe(true);
|
|
expect(sanitizeBoolean('true')).toBe(true);
|
|
expect(sanitizeBoolean('1')).toBe(true);
|
|
expect(sanitizeBoolean('false')).toBe(false);
|
|
expect(sanitizeBoolean(null, false)).toBe(false);
|
|
});
|
|
|
|
it('should sanitize arrays', () => {
|
|
expect(sanitizeArray([1, 2, 3])).toEqual([1, 2, 3]);
|
|
expect(sanitizeArray('not array', [])).toEqual([]);
|
|
expect(sanitizeArray(null, [1])).toEqual([1]);
|
|
});
|
|
});
|
|
|
|
describe('Deep Equality', () => {
|
|
const deepEqual = (a: unknown, b: unknown): boolean => {
|
|
if (a === b) return true;
|
|
|
|
if (typeof a !== typeof b) return false;
|
|
|
|
if (a === null || b === null) return a === b;
|
|
|
|
if (typeof a !== 'object') return false;
|
|
|
|
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
|
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
if (a.length !== b.length) return false;
|
|
return a.every((val, i) => deepEqual(val, b[i]));
|
|
}
|
|
|
|
const aObj = a as Record<string, unknown>;
|
|
const bObj = b as Record<string, unknown>;
|
|
|
|
const keysA = Object.keys(aObj);
|
|
const keysB = Object.keys(bObj);
|
|
|
|
if (keysA.length !== keysB.length) return false;
|
|
|
|
return keysA.every(key => deepEqual(aObj[key], bObj[key]));
|
|
};
|
|
|
|
it('should compare primitives', () => {
|
|
expect(deepEqual(1, 1)).toBe(true);
|
|
expect(deepEqual('a', 'a')).toBe(true);
|
|
expect(deepEqual(1, 2)).toBe(false);
|
|
});
|
|
|
|
it('should compare arrays', () => {
|
|
expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true);
|
|
expect(deepEqual([1, 2], [1, 2, 3])).toBe(false);
|
|
expect(deepEqual([1, 2, 3], [1, 3, 2])).toBe(false);
|
|
});
|
|
|
|
it('should compare objects', () => {
|
|
expect(deepEqual({ a: 1 }, { a: 1 })).toBe(true);
|
|
expect(deepEqual({ a: 1 }, { a: 2 })).toBe(false);
|
|
expect(deepEqual({ a: 1 }, { b: 1 })).toBe(false);
|
|
});
|
|
|
|
it('should compare nested structures', () => {
|
|
expect(deepEqual(
|
|
{ a: { b: [1, 2] } },
|
|
{ a: { b: [1, 2] } }
|
|
)).toBe(true);
|
|
expect(deepEqual(
|
|
{ a: { b: [1, 2] } },
|
|
{ a: { b: [1, 3] } }
|
|
)).toBe(false);
|
|
});
|
|
|
|
it('should handle null and undefined', () => {
|
|
expect(deepEqual(null, null)).toBe(true);
|
|
expect(deepEqual(undefined, undefined)).toBe(true);
|
|
expect(deepEqual(null, undefined)).toBe(false);
|
|
});
|
|
});
|
|
});
|