import { assert } from 'chai'; import { create, assign } from '../../../../../../lib/core/object'; import { MetaContext, $CONSTRUCTOR, $NAME } from '../../../../../../lib/experimental/meta/MetaContext'; import { $DESERIALIZE, withDeserialize } from '../../../../../../lib/experimental/meta/operations/base/$deserialize'; import { withRestore } from '../../../../../../lib/experimental/meta/operations/base/$restore'; import { withRegisterClass } from '../../../../../../lib/experimental/meta/extensions/registerClass'; import { withRegisterFunction } from '../../../../../../lib/experimental/meta/extensions/registerFunction'; import constructBuiltins from '../../../../../../lib/experimental/meta/operations/builtins/$construct'; import restoreBuiltins from '../../../../../../lib/experimental/meta/operations/builtins/$restore'; const deserialize = (function () { const context = new (withRegisterFunction(withRegisterClass(withDeserialize(withRestore(MetaContext)))))(); context.registerAll(constructBuiltins).registerAll(restoreBuiltins); function deserialize(state: any) { return context[$DESERIALIZE](state) as T; }; deserialize.registerClass = function (props: any) { return context.registerClass(props); }; deserialize.registerFunction = function (props: any) { return context.registerFunction(props); }; return deserialize; })(); describe('experimental.meta.operations.builtins.$deserialize', () => { describe('string', () => { it('string', () => { const state = 'abc'; const value = deserialize(state); assert.strictEqual(typeof value, 'string'); assert.strictEqual(value, 'abc'); }); }); describe('String', () => { it('String', () => { const state = { String: 'abc' }; const value = deserialize(state); assert.instanceOf(value, String); assert.strictEqual(value.valueOf(), 'abc'); }); }); describe('number', () => { it('number', () => { const state = 123; const value = deserialize(state); assert.strictEqual(typeof value, 'number'); assert.strictEqual(value, 123); }); }); describe('Number', () => { it('Number', () => { const state = { Number: 123 }; const value = deserialize(state); assert.instanceOf(value, Number); assert.strictEqual(value.valueOf(), 123); }); }); describe('boolean', () => { it('boolean', () => { const state = true; const value = deserialize(state); assert.strictEqual(typeof value, 'boolean'); assert.strictEqual(value, true); }); }); describe('Boolean', () => { it('Boolean', () => { const state = { Boolean: true }; const value = deserialize(state); assert.instanceOf(value, Boolean); assert.strictEqual(value.valueOf(), true); }); }); describe('undefined', () => { it('undefined', () => { const state = { undefined: {} }; const value = deserialize(state); assert.strictEqual(typeof value, 'undefined'); assert.strictEqual(value, undefined); }); }); describe('null', () => { it('null', () => { const state = null; const value = deserialize(state); assert.strictEqual(typeof value, 'object'); assert.strictEqual(value, null); }); }); describe('Object', () => { test.each([ [ {}, {}, (obj: any) => { obj.foo = 'bar'; } ], [ { a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 }, (obj: any) => { delete obj.a; obj.b = -2; } ], [ { p: { x: 1, y: 2 }, x: 3, foo: 'bar', v: [1, 2, 3] }, { p: { x: 1, y: 2 }, x: 3, foo: 'bar', v: [1, 2, 3] }, (obj: any) => { obj.p.x = -1; obj.v[0] = -1; } ] ])('Object#%#', (state: any, result: any, mutate: (obj: any) => void) => { const object = deserialize(state); assert.instanceOf(object, Object); assert.deepEqual(object, result); mutate(state); assert.deepEqual(object, result); }); }); describe('object', () => { test.each([ [ {}, {}, (obj: any) => { obj.foo = 'bar'; } ], [ { a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 }, (obj: any) => { delete obj.a; obj.b = -2; } ], [ { p: { x: 1, y: 2 }, x: 3, foo: 'bar', v: [1, 2, 3] }, { p: { x: 1, y: 2 }, x: 3, foo: 'bar', v: [1, 2, 3] }, (obj: any) => { obj.p.x = -1; obj.v[0] = -1; } ] ])('object#%#', (stateData: any, resultData: any, mutate: (obj: any) => void) => { const state = { object: stateData }; const result = assign(create(), resultData as any); const object = deserialize(state); assert.strictEqual(typeof object, 'object'); assert.isUndefined(object.constructor); assert.deepEqual(object, result); mutate(state.object); assert.deepEqual(object, result); }); }); describe('function', () => { it('function', () => { function add(a: number, b: number) { return a + b; } function mul(a: number, b: number) { return a * b; } deserialize.registerFunction({ [$CONSTRUCTOR]: add, [$NAME]: 'add' }); assert.deepEqual(deserialize({ mul: {} }), { mul: {} }); const value = deserialize({ add: {} }); assert.strictEqual(value, add); }); }); describe('Date', () => { it('Date', () => { const now = Date.now(); const state = { Date: now }; const date = deserialize(state); assert.instanceOf(date, Date); assert.strictEqual(date.getTime(), now); }); }); describe('Array', () => { test.each([ [ [], [], (array: any[]) => { array.push(1); } ], [ [1, 2, 3], [1, 2, 3], (array: any[]) => { array.splice(1, 1); } ], [ [1, { x: 1, y: 2 }, [1, 2, 3], 'foo'], [1, { x: 1, y: 2 }, [1, 2, 3], 'foo'], (array: any[]) => { array[1].x = -1; array[2][0] = -1; } ], ])('Array#%#', (state: any[], result: any[], mutate: (array: any[]) => void) => { const array = deserialize(state); assert.isTrue(array instanceof Array); assert.deepEqual(array, result); mutate(state); assert.deepEqual(array, result); }); }); describe('Map', () => { test.each([ [ [], [], (values: [any, any][]) => { values.push(['foo', 'bar']); } ], [ [['a', 1], ['b', 2], ['c', 3]], [['a', 1], ['b', 2], ['c', 3]], (values: [any, any][]) => { values.push(['d', 4]); values.splice(1, 1); values[0][1] = -1; } ], [ [[1, 'a'], ['1', 'b']], [[1, 'a'], ['1', 'b']], (values: [any, any][]) => { values.push(['2', 'c']); values.splice(1, 1); } ], [ [['p', { x: 1, y: 2 }], ['foo', 'bar'], ['v', [1, 2, 3]]], [['p', { x: 1, y: 2 }], ['foo', 'bar'], ['v', [1, 2, 3]]], (values: [any, any][]) => { const p1 = values[0][1]; p1.x = 3; p1.y = 4; } ], [ [[{ x: 1, y: 2 }, 3], [[1, 2, 3], 6]], [[{ x: 1, y: 2 }, 3], [[1, 2, 3], 6]], (values: [any, any][]) => { values[0][0].x = -3; values[0][0].y = -2; } ], ])('Map#%#', (stateData: [any, any][], resultData: [any, any][], mutate: (values: [any, any][]) => void) => { const state = { Map: stateData }; const result = new Map(resultData); const map = deserialize(state); assert.instanceOf(map, Map); assert.deepEqual(map, result); mutate(state.Map); assert.deepEqual(map, result); }); }); describe('Set', () => { test.each([ [ [], [], (values: any[]) => { values.push('foo'); } ], [ ['a', 'b', 'c'], ['a', 'b', 'c'], (values: any[]) => { values.splice(1, 1); values.push('d'); } ], [ ['1', 1, '2'], ['1', 1, '2'], (values: any[]) => { values.splice(1, 1); } ], [ [{ x: 1, y: 2 }, 'foo', [1, 2, 3]], [{ x: 1, y: 2 }, 'foo', [1, 2, 3]], (values: any[]) => { values[0].x = -1; values[0].y = -2; } ], ])('Set#%#', (stateData: any[], resultData: any[], mutate: (values: any[]) => void) => { const state = { Set: stateData }; const result = new Set(resultData); const set = deserialize(state); assert.instanceOf(set, Set); assert.deepEqual(set, result); mutate(state.Set); assert.deepEqual(set, result); }); }); describe('TypedArrays', () => { describe('Uint8Array', () => { test.each([ [ [], [], (_: number[]) => { } ], [ [11, 21, 31], [11, 21, 31], (array: number[]) => { array[0] = 41; } ] ])('Uint8Array#%#', (stateData: number[], resultData: number[], mutate: (array: number[]) => void) => { const state = { Uint8Array: stateData }; const array = new Uint8Array(resultData); const result = deserialize(state); assert.deepEqual(array, result); mutate(state.Uint8Array); assert.deepEqual(array, result); }); }); }); describe('class', () => { class Point { constructor(public x = 0, public y = 0) { } } class Polygon { constructor(public points: Point[] = []) { } } class Triangle extends Polygon { constructor(p1: Point, p2: Point, p3: Point, public color = 'black') { super([p1, p2, p3]); } } deserialize.registerClass({ [$CONSTRUCTOR]: Point, [$NAME]: 'Point' }); deserialize.registerClass({ [$CONSTRUCTOR]: Polygon, [$NAME]: 'Polygon' }); deserialize.registerClass({ [$CONSTRUCTOR]: Triangle, [$NAME]: 'Triangle' }); describe('Point', () => { test.each([ [ { Point: { x: 0, y: 0 } }, new Point(), (_: any) => { } ], [ { Point: { x: 1, y: 2 } }, new Point(1, 2), (state: any) => { state.x = -1; state.y = -2; } ] ])('Point#%#', (state: any, result: Point, mutate: (state: any) => void) => { const point = deserialize(state); assert.deepEqual(point, result); mutate(state.Point); assert.deepEqual(point, result); }); }); describe('Polygon', () => { test.each([ [ { Polygon: { points: [] } }, new Polygon(), (_: any) => { } ], [ { Polygon: { points: [{ Point: { x: 1, y: 2 } }, { Point: { x: 2, y: 3 } }, { Point: { x: 3, y: 1 } }] } }, new Polygon([new Point(1, 2), new Point(2, 3), new Point(3, 1)]), (state: any) => { state.points[0].x = -1; state.points[0].y = -2; state.points.splice(1, 1); } ] ])('Polygon#%#', (state: any, result: Polygon, mutate: (state: any) => void) => { const poly = deserialize(state); assert.deepEqual(poly, result); mutate(state.Polygon); assert.deepEqual(poly, result); }); }); describe('Triangle', () => { test.each([ [ { Triangle: { points: [{ Point: { x: 1, y: 2 } }, { Point: { x: 2, y: 3 } }, { Point: { x: 3, y: 1 } }], color: 'red' } }, new Triangle(new Point(1, 2), new Point(2, 3), new Point(3, 1), 'red'), (state: any) => { state.points[0].x = -1; state.points[0].y = -2; state.points.splice(1, 1); } ] ])('Triangle#%#', (state: any, result: Triangle, mutate: (tri: Triangle) => void) => { const tri = deserialize(state); assert.deepEqual(tri, result); mutate(state.Triangle); assert.deepEqual(tri, result); }); }); }); });