feat: implement deepClone function for deep cloning of objects and arrays

This commit is contained in:
2026-02-20 23:25:15 -05:00
parent 4417b7ade9
commit e0808de270
2 changed files with 238 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
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<any, any>([[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])
})
})