/** * Unit tests for safe-json utility */ import { describe, it, expect } from 'vitest'; import { cleanObject } from './safe-json.js'; describe('cleanObject', () => { it('removes undefined values', () => { const input = { a: 1, b: undefined, c: 3 }; const result = cleanObject(input); expect(result).toEqual({ a: 1, c: 3 }); expect('b' in result).toBe(false); }); it('preserves null values (only removes undefined)', () => { const input = { a: 1, b: null, c: 3 }; const result = cleanObject(input); // cleanObject preserves null, only removes undefined expect(result).toEqual({ a: 1, b: null, c: 3 }); }); it('handles nested objects', () => { const input = { a: 1, nested: { b: 2, c: undefined } }; const result = cleanObject(input); expect(result.nested).toEqual({ b: 2 }); }); it('handles arrays with undefined (preserves null)', () => { const input = { arr: [1, undefined, 3, null] }; const result = cleanObject(input); // Arrays preserve their structure, undefined becomes undefined in array expect(result.arr).toEqual([1, undefined, 3, null]); }); it('preserves falsy values that are not null/undefined', () => { const input = { a: 0, b: '', c: false }; const result = cleanObject(input); expect(result).toEqual({ a: 0, b: '', c: false }); }); it('handles empty objects', () => { expect(cleanObject({})).toEqual({}); }); it('handles primitive inputs', () => { expect(cleanObject('string')).toBe('string'); expect(cleanObject(42)).toBe(42); expect(cleanObject(true)).toBe(true); }); it('respects max depth limit', () => { type NestedValue = { value?: string; nested?: NestedValue }; let deep: NestedValue = { value: 'deep' }; for (let i = 0; i < 15; i++) { deep = { nested: deep }; } expect(() => cleanObject(deep, 10)).not.toThrow(); }); it('uses the default depth limit for invalid maxDepth values', () => { const input = { nested: { value: 'safe' } }; expect(cleanObject(input, -1)).toEqual(input); expect(cleanObject(input, Number.NaN)).toEqual(input); }); it('handles circular reference prevention at max depth', () => { const obj: Record = { a: 1 }; obj.self = obj; expect(() => cleanObject(obj, 5)).not.toThrow(); }); it('preserves repeated non-circular references as full values', () => { const shared = { pinName: 'then_0', direction: 'Output' }; const input = { result: { pins: [shared] }, pins: [shared] }; const result = cleanObject(input); expect(result).toEqual({ result: { pins: [{ pinName: 'then_0', direction: 'Output' }] }, pins: [{ pinName: 'then_0', direction: 'Output' }] }); }); it('marks actual circular references', () => { const input: Record = { a: 1 }; input.self = input; const result = cleanObject(input); expect(result.self).toBe('[Circular Reference]'); }); it('handles Date objects (converts to empty object)', () => { const date = new Date('2024-01-01'); const input = { created: date }; const result = cleanObject(input); // Date objects are processed as objects using Object.keys, which returns [] expect(result.created).toEqual({}); }); it('handles empty arrays', () => { const input = { arr: [] }; const result = cleanObject(input); expect(result.arr).toEqual([]); }); it('handles NaN values', () => { const input = { a: 1, b: NaN }; const result = cleanObject(input); // NaN handling depends on implementation expect(result.a).toBe(1); }); });