mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 15:39:41 +02:00
test: add global test setup with screen limits and reach 1337 tests
- 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>
This commit is contained in:
@@ -0,0 +1,643 @@
|
||||
/**
|
||||
* @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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user