ts-prime
Version:
A utility library for JavaScript and Typescript.
212 lines (174 loc) • 5.63 kB
text/typescript
import * as assert from 'assert';
import { clone } from './clone';
const eq = (a: any, b: any) => {
expect(a).toEqual(b);
};
describe('deep clone integers, strings and booleans', () => {
it('clones integers', () => {
eq(clone(-4), -4);
eq(clone(9007199254740991), 9007199254740991);
});
it('clones floats', () => {
eq(clone(-4.5), -4.5);
eq(clone(0.0), 0.0);
});
it('clones strings', () => {
eq(clone('foo'), 'foo');
});
it('clones booleans', () => {
eq(clone(true), true);
});
});
describe('deep clone objects', () => {
it('clones shallow object', () => {
const obj = { a: 1, b: 'foo', c: true, d: new Date(2013, 11, 25) };
const cloned = clone(obj);
obj.c = false;
obj.d.setDate(31);
assert.notStrictEqual(cloned, { a: 1, b: 'foo', c: true, d: new Date(2013, 11, 25) });
});
it('clones deep object', () => {
const obj = { a: { b: { c: 'foo' } } };
const cloned = clone(obj);
obj.a.b.c = 'bar';
eq(cloned, { a: { b: { c: 'foo' } } });
});
it('clones objects with circular references', () => {
const x: any = { c: null };
const y = { a: x };
const z = { b: y };
x.c = z;
const cloned = clone(x);
assert.notStrictEqual(x, cloned);
assert.notStrictEqual(x.c, cloned.c);
assert.notStrictEqual(x.c.b, cloned.c.b);
assert.notStrictEqual(x.c.b.a, cloned.c.b.a);
assert.notStrictEqual(x.c.b.a.c, cloned.c.b.a.c);
eq(Object.keys(cloned), Object.keys(x));
eq(Object.keys(cloned.c), Object.keys(x.c));
eq(Object.keys(cloned.c.b), Object.keys(x.c.b));
eq(Object.keys(cloned.c.b.a), Object.keys(x.c.b.a));
eq(Object.keys(cloned.c.b.a.c), Object.keys(x.c.b.a.c));
x.c.b = 1;
assert.notDeepEqual(cloned.c.b, x.c.b);
});
// it('clone instances', () => {
// const Obj = function(x) {
// this.x = x;
// };
// Obj.prototype.get = () => {
// return this.x;
// };
// Obj.prototype.set = function(x) {
// this.x = x;
// };
// const obj = new Obj(10);
// eq(obj.get(), 10);
// const cloned = clone(obj);
// eq(cloned.get(), 10);
// assert.notStrictEqual(obj, cloned);
// obj.set(11);
// eq(obj.get(), 11);
// eq(cloned.get(), 10);
// });
});
describe('deep clone arrays', () => {
it('clones shallow arrays', () => {
const list = [1, 2, 3];
const cloned = clone(list);
list.pop();
eq(cloned, [1, 2, 3]);
});
it('clones deep arrays', () => {
const list: any = [1, [1, 2, 3], [[[5]]]];
const cloned = clone(list);
assert.notStrictEqual(list, cloned);
assert.notStrictEqual(list[2], cloned[2]);
assert.notStrictEqual(list[2][0], cloned[2][0]);
eq(cloned, [1, [1, 2, 3], [[[5]]]]);
});
});
describe('deep clone functions', () => {
it('keep reference to function', () => {
const fn = (x: any) => {
return x + x;
};
const list = [{ a: fn }];
const cloned = clone(list);
eq(cloned[0].a(10), 20);
eq(list[0].a, cloned[0].a);
});
});
describe('built-in types', () => {
it('clones Date object', () => {
const date = new Date(2014, 10, 14, 23, 59, 59, 999);
const cloned = clone(date);
eq(date, cloned);
eq(cloned, new Date(2014, 10, 14, 23, 59, 59, 999));
eq(cloned.getDay(), 5); // friday
});
it('clones RegExp object', () => {
[/x/, /x/g, /x/i, /x/m, /x/gi, /x/gm, /x/im, /x/gim].forEach(pattern => {
const cloned = clone(pattern);
assert.notStrictEqual(cloned, pattern);
eq(cloned.constructor, RegExp);
eq(cloned.source, pattern.source);
eq(cloned.global, pattern.global);
eq(cloned.ignoreCase, pattern.ignoreCase);
eq(cloned.multiline, pattern.multiline);
});
});
});
describe('deep clone deep nested mixed objects', () => {
it('clones array with objects', () => {
const list: any = [{ a: { b: 1 } }, [{ c: { d: 1 } }]];
const cloned = clone(list);
list[1][0] = null;
eq(cloned, [{ a: { b: 1 } }, [{ c: { d: 1 } }]]);
});
it('clones array with arrays', () => {
const list: any[][] = [[1], [[3]]];
const cloned = clone(list);
list[1][0] = null;
eq(cloned, [[1], [[3]]]);
});
it('clones array with mutual ref object', () => {
const obj = { a: 1 };
const list = [{ b: obj }, { b: obj }];
const cloned = clone(list);
assert.strictEqual(list[0].b, list[1].b);
assert.strictEqual(cloned[0].b, cloned[1].b);
assert.notStrictEqual(cloned[0].b, list[0].b);
assert.notStrictEqual(cloned[1].b, list[1].b);
eq(cloned[0].b, { a: 1 });
eq(cloned[1].b, { a: 1 });
obj.a = 2;
eq(cloned[0].b, { a: 1 });
eq(cloned[1].b, { a: 1 });
});
});
describe('deep clone edge cases', () => {
it('nulls, undefineds and empty objects and arrays', () => {
eq(clone(null), null);
eq(clone(undefined), undefined);
assert.notStrictEqual(clone(undefined), null);
const obj = {};
assert.notStrictEqual(clone(obj), obj);
const list: any = [];
assert.notStrictEqual(clone(list), list);
});
});
// describe('Let `clone` use an arbitrary user defined `clone` method', () => {
// it('dispatches to `clone` method if present', () => {
// function ArbitraryClone(x: any) {
// this.value = x;
// }
// ArbitraryClone.prototype.clone = () => {
// return new ArbitraryClone(this.value);
// };
// const obj = new ArbitraryClone(42);
// const arbitraryClonedObj = clone(obj);
// eq(arbitraryClonedObj, new ArbitraryClone(42));
// eq(arbitraryClonedObj instanceof ArbitraryClone, true);
// });
// });