import { ApplicationError } from '@/errors/application.error'; import { jsonParse, jsonStringify, deepCopy, isObjectEmpty, fileTypeFromMimeType } from '@/utils'; describe('isObjectEmpty', () => { it('should handle null and undefined', () => { expect(isObjectEmpty(null)).toEqual(true); expect(isObjectEmpty(undefined)).toEqual(true); }); it('should handle arrays', () => { expect(isObjectEmpty([])).toEqual(true); expect(isObjectEmpty([1, 2, 3])).toEqual(false); }); it('should handle Set and Map', () => { expect(isObjectEmpty(new Set())).toEqual(true); expect(isObjectEmpty(new Set([1, 2, 3]))).toEqual(false); expect(isObjectEmpty(new Map())).toEqual(true); expect( isObjectEmpty( new Map([ ['a', 1], ['b', 2], ]), ), ).toEqual(false); }); it('should handle Buffer, ArrayBuffer, and Uint8Array', () => { expect(isObjectEmpty(Buffer.from(''))).toEqual(true); expect(isObjectEmpty(Buffer.from('abcd'))).toEqual(false); expect(isObjectEmpty(Uint8Array.from([]))).toEqual(true); expect(isObjectEmpty(Uint8Array.from([1, 2, 3]))).toEqual(false); expect(isObjectEmpty(new ArrayBuffer(0))).toEqual(true); expect(isObjectEmpty(new ArrayBuffer(1))).toEqual(false); }); it('should handle plain objects', () => { expect(isObjectEmpty({})).toEqual(true); expect(isObjectEmpty({ a: 1, b: 2 })).toEqual(false); }); it('should handle instantiated classes', () => { expect(isObjectEmpty(new (class Test {})())).toEqual(true); expect( isObjectEmpty( new (class Test { prop = 123; })(), ), ).toEqual(false); }); it('should not call Object.keys unless a plain object', () => { const keySpy = jest.spyOn(Object, 'keys'); const { calls } = keySpy.mock; const assertCalls = (count: number) => { if (calls.length !== count) { throw new ApplicationError('`Object.keys()` was called an unexpected number of times', { extra: { times: calls.length }, }); } }; assertCalls(0); isObjectEmpty(null); assertCalls(0); isObjectEmpty([1, 2, 3]); assertCalls(0); isObjectEmpty(Buffer.from('123')); assertCalls(0); isObjectEmpty({}); assertCalls(1); }); }); describe('jsonParse', () => { it('parses JSON', () => { expect(jsonParse('[1, 2, 3]')).toEqual([1, 2, 3]); expect(jsonParse('{ "a": 1 }')).toEqual({ a: 1 }); }); it('optionally throws `errorMessage', () => { expect(() => { jsonParse('', { errorMessage: 'Invalid JSON' }); }).toThrow('Invalid JSON'); }); it('optionally returns a `fallbackValue`', () => { expect(jsonParse('', { fallbackValue: { foo: 'bar' } })).toEqual({ foo: 'bar' }); }); }); describe('jsonStringify', () => { const source: any = { a: 1, b: 2, d: new Date(1680089084200), r: new RegExp('^test$', 'ig') }; source.c = source; it('should throw errors on circular references by default', () => { expect(() => jsonStringify(source)).toThrow('Converting circular structure to JSON'); }); it('should break circular references when requested', () => { expect(jsonStringify(source, { replaceCircularRefs: true })).toEqual( '{"a":1,"b":2,"d":"2023-03-29T11:24:44.200Z","r":{},"c":"[Circular Reference]"}', ); }); it('should not detect duplicates as circular references', () => { const y = { z: 5 }; const x = [y, y, { y }]; expect(jsonStringify(x, { replaceCircularRefs: true })).toEqual( '[{"z":5},{"z":5},{"y":{"z":5}}]', ); }); }); describe('deepCopy', () => { it('should deep copy an object', () => { const serializable = { x: 1, y: 2, toJSON: () => 'x:1,y:2', }; const object = { deep: { props: { list: [{ a: 1 }, { b: 2 }, { c: 3 }], }, arr: [1, 2, 3], }, serializable, arr: [ { prop: { list: ['a', 'b', 'c'], }, }, ], func: () => {}, date: new Date(1667389172201), undef: undefined, nil: null, bool: true, num: 1, }; const copy = deepCopy(object); expect(copy).not.toBe(object); expect(copy.arr).toEqual(object.arr); expect(copy.arr).not.toBe(object.arr); expect(copy.date).toBe('2022-11-02T11:39:32.201Z'); expect(copy.serializable).toBe(serializable.toJSON()); expect(copy.deep.props).toEqual(object.deep.props); expect(copy.deep.props).not.toBe(object.deep.props); }); it('should avoid max call stack in case of circular deps', () => { const object: Record = { deep: { props: { list: [{ a: 1 }, { b: 2 }, { c: 3 }], }, arr: [1, 2, 3], }, arr: [ { prop: { list: ['a', 'b', 'c'], }, }, ], func: () => {}, date: new Date(1667389172201), undef: undefined, nil: null, bool: true, num: 1, }; object.circular = object; object.deep.props.circular = object; object.deep.arr.push(object); const copy = deepCopy(object); expect(copy).not.toBe(object); expect(copy.arr).toEqual(object.arr); expect(copy.arr).not.toBe(object.arr); expect(copy.date).toBe('2022-11-02T11:39:32.201Z'); expect(copy.deep.props.circular).toBe(copy); expect(copy.deep.props.circular).not.toBe(object); expect(copy.deep.arr.slice(-1)[0]).toBe(copy); expect(copy.deep.arr.slice(-1)[0]).not.toBe(object); }); }); describe('fileTypeFromMimeType', () => { it('should recognize json', () => { expect(fileTypeFromMimeType('application/json')).toEqual('json'); }); it('should recognize html', () => { expect(fileTypeFromMimeType('text/html')).toEqual('html'); }); it('should recognize image', () => { expect(fileTypeFromMimeType('image/jpeg')).toEqual('image'); expect(fileTypeFromMimeType('image/png')).toEqual('image'); expect(fileTypeFromMimeType('image/avif')).toEqual('image'); expect(fileTypeFromMimeType('image/webp')).toEqual('image'); }); it('should recognize audio', () => { expect(fileTypeFromMimeType('audio/wav')).toEqual('audio'); expect(fileTypeFromMimeType('audio/webm')).toEqual('audio'); expect(fileTypeFromMimeType('audio/ogg')).toEqual('audio'); expect(fileTypeFromMimeType('audio/mp3')).toEqual('audio'); }); it('should recognize video', () => { expect(fileTypeFromMimeType('video/mp4')).toEqual('video'); expect(fileTypeFromMimeType('video/webm')).toEqual('video'); expect(fileTypeFromMimeType('video/ogg')).toEqual('video'); }); it('should recognize text', () => { expect(fileTypeFromMimeType('text/plain')).toEqual('text'); expect(fileTypeFromMimeType('text/css')).toEqual('text'); expect(fileTypeFromMimeType('text/html')).not.toEqual('text'); expect(fileTypeFromMimeType('text/javascript')).toEqual('text'); expect(fileTypeFromMimeType('application/javascript')).toEqual('text'); }); it('should recognize pdf', () => { expect(fileTypeFromMimeType('application/pdf')).toEqual('pdf'); }); });