import { $NAME, $CONSTRUCTOR, MetaContext, MetaObject } from '../../../../lib/experimental/meta/MetaContext'; describe('experimental.meta.MetaContext', () => { const $DESCRIPTION = '$description'; const $ID = '$id'; const $SALARY = '$salary'; const $EQUAL = '$equal'; class Point { constructor(public x = 0, public y = 0) { } } class Person { constructor(public name = '') { } } class Employee extends Person { constructor(public position = '', name?: string) { super(name); } } describe('register', () => { it('should register new properties by name', () => { const person = { [$NAME]: 'person', [$DESCRIPTION]: 'person type' }; const context = new MetaContext(); expect(context.register(person)).toEqual(person); expect(context.getName('person')).toEqual(person); expect(context.names()).toEqual(['person']); expect(context.constructors()).toEqual([]); }); it('should merge properties by name', () => { const person = { [$NAME]: 'person', [$DESCRIPTION]: 'person type', [$ID]: 1 }; const renewedPerson = { [$NAME]: 'person', [$DESCRIPTION]: 'person type', [$ID]: 2, [$SALARY]: 10000 }; const context = new MetaContext(); expect(context.register(person)).toEqual(person); expect(context.getName('person')).toEqual(person); expect(context.register({ [$NAME]: 'person', [$ID]: 2, [$SALARY]: 10000 })).toEqual(renewedPerson); expect(context.getName('person')).toEqual(renewedPerson); expect(context.names()).toEqual(['person']); expect(context.constructors()).toEqual([]); }); it('should not register properties without name', () => { const context = new MetaContext(); expect(context.register({ [$ID]: -1 })).toBeUndefined(); expect(context.register({ [$NAME]: '', [$ID]: -1 })).toBeUndefined(); expect(context.names()).toEqual([]); expect(context.constructors()).toEqual([]); }); it('should throw an error when registring properties with existing name and new constructor', () => { const context = new MetaContext(); context.register({ [$NAME]: 'person', [$DESCRIPTION]: 'person type', }); expect(() => { context.register({ [$NAME]: 'person', [$CONSTRUCTOR]: Person }); }).toThrowError(TypeError); expect(context.names()).toEqual(['person']); expect(context.constructors()).toEqual([]); }); it('should register new properties by name and constructor', () => { const person = { [$CONSTRUCTOR]: Person, [$NAME]: 'Person', [$DESCRIPTION]: 'Person class' }; const context = new MetaContext(); expect(context.register(person)).toEqual(person); expect(context.getName('Person')).toEqual(person); expect(context.getConstructor(Person)).toEqual(person); expect(context.names()).toEqual(['Person']); expect(context.constructors()).toEqual([Person]); }); it('should merge properties by name and constructor', () => { const person = { [$CONSTRUCTOR]: Person, [$NAME]: 'Person', [$DESCRIPTION]: 'Person class', [$ID]: 1 }; const renewedPerson = { [$CONSTRUCTOR]: Person, [$NAME]: 'Person', [$DESCRIPTION]: 'Person class', [$ID]: 2, [$SALARY]: 10000 }; const context = new MetaContext(); expect(context.register(person)).toEqual(person); expect(context.getName('Person')).toEqual(person); expect(context.getConstructor(Person)).toEqual(person); expect(context.register({ [$CONSTRUCTOR]: Person, [$NAME]: 'Person', [$ID]: 2, [$SALARY]: 10000 })).toEqual(renewedPerson); expect(context.getName('Person')).toEqual(renewedPerson); expect(context.getConstructor(Person)).toEqual(renewedPerson); expect(context.names()).toEqual(['Person']); expect(context.constructors()).toEqual([Person]); }); it('should throw an error when registring properties with existing name and new constructor', () => { const context = new MetaContext(); context.register({ [$CONSTRUCTOR]: Person, [$NAME]: 'Person', [$DESCRIPTION]: 'Person class', }); expect(() => { context.register({ [$NAME]: 'Person', [$CONSTRUCTOR]: class Person { }, [$ID]: 1 }); }).toThrowError(TypeError); expect(context.names()).toEqual(['Person']); expect(context.constructors()).toEqual([Person]); }); it('should throw an error when registring properties with existing constructor and new name', () => { const context = new MetaContext(); context.register({ [$CONSTRUCTOR]: Person, [$NAME]: 'Person', [$DESCRIPTION]: 'Person class', }); expect(() => { context.register({ [$CONSTRUCTOR]: Person, [$NAME]: 'Manager', [$ID]: 1 }); }).toThrowError(TypeError); expect(context.names()).toEqual(['Person']); expect(context.constructors()).toEqual([Person]); }); // it(`should support '$oncreate' callback`, () => { // const context = new MetaContext(); // context.register({ // [$CONSTRUCTOR]: Person, // [$NAME]: 'Person', // [$EQUAL](lhs: Person, rhs: Person) { // return lhs.name === rhs.name; // } // }); // const $Employee = context.register({ // [$CONSTRUCTOR]: Employee, // [$NAME]: 'Employee', // [$ONCREATE](this: MetaObject, context: MetaContext) { // const $Person = context.$constructor(Person); // return { // ...this, // [$EQUAL](lhs: Employee, rhs: Employee) { // return lhs.position === rhs.position // && $Person[$EQUAL](lhs, rhs); // } // }; // } // }) as MetaObject & { [$EQUAL](lhs: Employee, rhs: Employee): boolean }; // expect(typeof $Employee[$EQUAL]).toBe('function'); // expect($Employee[$EQUAL](new Employee('Manager', 'John Smith'), new Employee('Manager', 'John Smith'))).toBeTruthy(); // expect($Employee[$EQUAL](new Employee('Manager', 'Michael Jones'), new Employee('Manager', 'John Smith'))).toBeFalsy(); // }); }); describe('registerAll', () => { it('should allow register multiple types', () => { const all = [ { [$CONSTRUCTOR]: Person, [$NAME]: 'Person', [$DESCRIPTION]: 'Person class', }, { [$CONSTRUCTOR]: Point, [$NAME]: 'Point', [$DESCRIPTION]: 'Point class', } ]; const context = new MetaContext(); expect(context.registerAll(all)).toBe(context); expect(context.names()).toEqual(['Person', 'Point']); expect(context.constructors()).toEqual([Person, Point]); }); }); describe('getValue', () => { const context = new MetaContext(); const $null = context.register({ [$NAME]: 'null' }); const $undefined = context.register({ [$NAME]: 'undefined' }); const $string = context.register({ [$NAME]: 'string' }); const $String = context.register({ [$CONSTRUCTOR]: String, [$NAME]: 'String' }); const $number = context.register({ [$NAME]: 'number' }); const $Number = context.register({ [$CONSTRUCTOR]: Number, [$NAME]: 'Number' }); const $Date = context.register({ [$CONSTRUCTOR]: Date, [$NAME]: 'Date' }); const $Array = context.register({ [$CONSTRUCTOR]: Array, [$NAME]: 'Array' }); const $Object = context.register({ [$CONSTRUCTOR]: Object, [$NAME]: 'Object' }); const $object = context.register({ [$NAME]: 'object' }); const $function = context.register({ [$NAME]: 'function' }); const $Point = context.register({ [$CONSTRUCTOR]: Point, [$NAME]: 'Point' }); test.each([ [null, $null], [undefined, $undefined], ['abc', $string], [String('abc'), $string], [new String('abc'), $String], [123, $number], [Number(123), $number], [new Number(123), $Number], [new Date(), $Date], [[1, 2, 3], $Array], [new Array(3), $Array], [{ a: 1, b: 2, c: 3 }, $Object], [Object.create(null), $object], [function () { }, $function], [new Function('return this'), $function], [Function('return this'), $function], [Function, $function], [Number, $function], [Object, $function], [new Point(1, 2), $Point], [new Person('John Smith'), undefined], [false, undefined] ])('getValue#%#', (value: any, metaType: MetaObject) => { expect(context.getValue(value)).toBe(metaType); }); }); describe('resolveValue/resolveName/resolveConstructor', () => { it('should return properties on registred types', () => { const context = new MetaContext(); const $Point = context.register({ [$CONSTRUCTOR]: Point, [$NAME]: 'Point' }); expect(context.resolveValue(new Point(1, 2))).toBe($Point); expect(context.resolveName('Point')).toBe($Point); expect(context.resolveConstructor(Point)).toBe($Point); }); it('should throw an type error on unregistred types', () => { const context = new MetaContext(); expect(() => { context.resolveValue(new Point(1, 2)); }).toThrowError(TypeError); expect(() => { context.resolveName('Point'); }).toThrowError(TypeError); expect(() => { context.resolveConstructor(Point); }).toThrowError(TypeError); }); }); });