import { describe, expect, it } from 'vitest' import { deepClone } from '@/shared/helpers/deep-clone' describe('deepClone', () => { it('clona valores primitivos', () => { expect(deepClone(42)).toBe(42) expect(deepClone('abc')).toBe('abc') expect(deepClone(true)).toBe(true) expect(deepClone(null)).toBeNull() expect(deepClone(undefined)).toBeUndefined() expect(deepClone(123n)).toBe(123n) }) it('clona arrays y objetos anidados (referencias diferentes)', () => { const obj = { a: 1, b: { c: 2 } } const arr = [1, { x: 2 }] const src = { obj, arr } const cloned = deepClone(src) expect(cloned).not.toBe(src) expect(cloned.obj).not.toBe(src.obj) expect(cloned.arr).not.toBe(src.arr) expect(cloned).toEqual(src) // mutar clon no debe afectar al original cloned.obj.b.c = 99 expect(src.obj.b.c).toBe(2) }) it('maneja Date y RegExp', () => { const d = new Date() const r = /abc/gi const cd = deepClone(d) const cr = deepClone(r) expect(cd).not.toBe(d) expect(cd.getTime()).toBe(d.getTime()) expect(cr).not.toBe(r) expect(cr.source).toBe(r.source) expect(cr.flags).toBe(r.flags) }) it('clona Map y Set (clonando claves/valores)', () => { const keyObj = { k: 'v' } const m = new Map([[keyObj, { nested: 1 }]]) const s = new Set([keyObj, 2, 'x']) const cm = deepClone(m) const cs = deepClone(s) expect(cm).not.toBe(m) expect(cs).not.toBe(s) // Map: la clave será un objeto distinto pero con los mismos datos const [[clonedKey, clonedVal]] = Array.from(cm.entries()) expect(clonedKey).not.toBe(keyObj) expect(clonedKey).toEqual(keyObj) expect(clonedVal).toEqual({ nested: 1 }) // Set: debe contener un objeto equivalente al original const found = Array.from(cs.values()).find( (v) => typeof v === 'object' && (v as any).k === 'v', ) expect(found).toBeDefined() expect(found).not.toBe(keyObj) }) it('clona TypedArray, ArrayBuffer y DataView', () => { const buf = new ArrayBuffer(8) const dv = new DataView(buf) dv.setInt8(0, 42) const ta = new Uint8Array([1, 2, 3]) const cbuf = deepClone(buf) const cdv = deepClone(dv) const cta = deepClone(ta) expect(cbuf).not.toBe(buf) expect(cdv).not.toBe(dv) expect(cdv.getInt8(0)).toBe(42) expect(cta).not.toBe(ta) expect(Array.from(cta)).toEqual([1, 2, 3]) expect(cta).toBeInstanceOf(Uint8Array) }) it('preserva prototype y métodos de instancia', () => { class C { a = 1 method() { return this.a } } const inst = new C() inst.a = 5 const cloned = deepClone(inst as any) expect(cloned).not.toBe(inst) expect(cloned).toBeInstanceOf(C) expect(cloned.method()).toBe(5) }) it('mantiene referencias a funciones (no las clona)', () => { const fn = () => 1 const src = { fn } const cloned = deepClone(src as any) expect(cloned.fn).toBe(fn) }) it('maneja referencias circulares', () => { const a: any = { name: 'a' } a.self = a const c = deepClone(a) expect(c).not.toBe(a) expect(c.self).toBe(c) expect(c.name).toBe('a') }) it('preserva descriptores y propiedades con símbolos', () => { const s = Symbol('sym') const obj: any = {} Object.defineProperty(obj, 'hidden', { value: 42, enumerable: false, configurable: true, writable: true, }) obj[s] = { foo: 'bar' } const c = deepClone(obj) const desc = Object.getOwnPropertyDescriptor(c, 'hidden')! expect(desc.enumerable).toBe(false) expect(desc.value).toBe(42) const symKeys = Object.getOwnPropertySymbols(c) expect(symKeys.length).toBe(1) expect(c[s]).toEqual({ foo: 'bar' }) expect(c[s]).not.toBe(obj[s]) }) })