UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

455 lines (454 loc) 23.2 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { describe, it, expect, beforeEach } from 'vitest'; import Brain from './brain'; import { BrainIgnoreClone } from './decorators'; let state; let brain; describe('State: base principles', () => { beforeEach(() => { state = { objectValue: {}, otherObjectValue: {} }; }); it('Base principles: check virtual properties on root state', () => { brain = new Brain(state, () => { /* do nothing */ }); expect(state.__brainIsProxy).toBeFalsy(); expect(brain.getState().__brainIsProxy).toBeTruthy(); expect(brain.getState().__brainTarget).toBe(state); expect(brain.getState().__brainChildren).toBeInstanceOf(Map); expect(brain.getState().__brainChildren.size).toBe(0); expect(brain.getState().__brainParents).toBeUndefined(); expect(brain.getState().__brainFullPaths).toBeInstanceOf(Array); expect(brain.getState().__brainFullPaths.length).toBe(1); expect(brain.getState().__brainFullPaths[0]).toBe(''); }); it('Base principles: check virtual properties on child', () => { brain = new Brain(state, () => { /* do nothing */ }); const pizza = { key: 'pizza' }; brain.getState().objectValue = pizza; expect(pizza.__brainIsProxy).toBeFalsy(); expect(brain.getState().objectValue.__brainIsProxy).toBeTruthy(); expect(brain.getState().objectValue.__brainTarget).toBe(pizza); expect(brain.getState().objectValue.__brainChildren).toBeInstanceOf(Map); expect(brain.getState().objectValue.__brainChildren.size).toBe(0); expect(brain.getState().objectValue.__brainParents).toBeInstanceOf(Array); expect(brain.getState().objectValue.__brainParents[0]).toBe(brain.getState()); expect(brain.getState().objectValue.__brainChildren.size).toBe(0); expect(brain.getState().objectValue.__brainFullPaths).toBeInstanceOf(Array); expect(brain.getState().objectValue.__brainFullPaths.length).toBe(1); expect(brain.getState().objectValue.__brainFullPaths[0]).toBe('objectValue'); expect(brain.getState().__brainChildren.size).toBe(1); expect(brain.getState().__brainChildren.get('objectValue')).toBe(brain.getState().objectValue); }); it('Base principles: should create different proxies for different objects', () => { brain = new Brain(state, () => { /* do nothing */ }); const pizza1 = { key: 'pizza1' }; const pizza2 = { key: 'pizza2' }; brain.getState().objectValue = pizza1; brain.getState().otherObjectValue = pizza2; expect(brain.getState().__brainChildren.size).toBe(2); expect(brain.getState().__brainChildren.get('objectValue')).toBe(brain.getState().objectValue); expect(brain.getState().__brainChildren.get('otherObjectValue')).toBe(brain.getState().otherObjectValue); expect(pizza1.__brainIsProxy).toBeFalsy(); expect(brain.getState().objectValue.__brainIsProxy).toBeTruthy(); expect(brain.getState().objectValue.__brainTarget).toBe(pizza1); expect(brain.getState().objectValue.__brainChildren.size).toBe(0); expect(brain.getState().objectValue.__brainParents[0]).toBe(brain.getState()); expect(brain.getState().objectValue.__brainFullPaths).toBeInstanceOf(Array); expect(brain.getState().objectValue.__brainFullPaths.length).toBe(1); expect(brain.getState().objectValue.__brainFullPaths[0]).toBe('objectValue'); expect(pizza2.__brainIsProxy).toBeFalsy(); expect(brain.getState().otherObjectValue.__brainIsProxy).toBeTruthy(); expect(brain.getState().otherObjectValue.__brainTarget).toBe(pizza2); expect(brain.getState().otherObjectValue.__brainChildren.size).toBe(0); expect(brain.getState().otherObjectValue.__brainParents[0]).toBe(brain.getState()); expect(brain.getState().otherObjectValue.__brainFullPaths).toBeInstanceOf(Array); expect(brain.getState().otherObjectValue.__brainFullPaths.length).toBe(1); expect(brain.getState().otherObjectValue.__brainFullPaths[0]).toBe('otherObjectValue'); }); it('Base principles: should create different proxies for different objects with same values', () => { brain = new Brain(state, () => { /* do nothing */ }); const pizza1 = { key: 'pizza1' }; const pizza2 = { key: 'pizza1' }; brain.getState().objectValue = pizza1; brain.getState().otherObjectValue = pizza2; expect(brain.getState().__brainChildren.size).toBe(2); expect(brain.getState().__brainChildren.get('objectValue')).toBe(brain.getState().objectValue); expect(brain.getState().__brainChildren.get('otherObjectValue')).toBe(brain.getState().otherObjectValue); expect(pizza1.__brainIsProxy).toBeFalsy(); expect(brain.getState().objectValue.__brainIsProxy).toBeTruthy(); expect(brain.getState().objectValue.__brainTarget).toBe(pizza1); expect(brain.getState().objectValue.__brainChildren.size).toBe(0); expect(brain.getState().objectValue.__brainParents[0]).toBe(brain.getState()); expect(brain.getState().objectValue.__brainFullPaths).toBeInstanceOf(Array); expect(brain.getState().objectValue.__brainFullPaths.length).toBe(1); expect(brain.getState().objectValue.__brainFullPaths[0]).toBe('objectValue'); expect(pizza2.__brainIsProxy).toBeFalsy(); expect(brain.getState().otherObjectValue.__brainIsProxy).toBeTruthy(); expect(brain.getState().otherObjectValue.__brainTarget).toBe(pizza2); expect(brain.getState().otherObjectValue.__brainChildren.size).toBe(0); expect(brain.getState().otherObjectValue.__brainParents[0]).toBe(brain.getState()); expect(brain.getState().otherObjectValue.__brainFullPaths).toBeInstanceOf(Array); expect(brain.getState().otherObjectValue.__brainFullPaths.length).toBe(1); expect(brain.getState().otherObjectValue.__brainFullPaths[0]).toBe('otherObjectValue'); }); it('Base principles: should reuse same proxy for same object', () => { brain = new Brain(state, () => { /* do nothing */ }); const pizza = { key: 'pizza' }; brain.getState().objectValue = pizza; brain.getState().otherObjectValue = pizza; expect(brain.getState().__brainChildren.size).toBe(2); expect(brain.getState().__brainChildren.get('objectValue')).toBe(brain.getState().objectValue); expect(brain.getState().__brainChildren.get('otherObjectValue')).toBe(brain.getState().objectValue); expect(brain.getState().__brainChildren.get('objectValue')).toBe(brain.getState().otherObjectValue); expect(brain.getState().__brainChildren.get('otherObjectValue')).toBe(brain.getState().otherObjectValue); expect(pizza.__brainIsProxy).toBeFalsy(); expect(brain.getState().objectValue.__brainIsProxy).toBeTruthy(); expect(brain.getState().objectValue.__brainTarget).toBe(pizza); expect(brain.getState().objectValue.__brainChildren.size).toBe(0); expect(brain.getState().objectValue.__brainParents[0]).toBe(brain.getState()); expect(brain.getState().objectValue.__brainFullPaths).toBeInstanceOf(Array); expect(brain.getState().objectValue.__brainFullPaths.length).toBe(2); expect(brain.getState().objectValue.__brainFullPaths[0]).toBe('objectValue'); expect(brain.getState().objectValue.__brainFullPaths[1]).toBe('otherObjectValue'); }); it('Base principles: should add correctly parents for same proxy', () => { brain = new Brain(state, () => { /* do nothing */ }); const pizza = { key: 'pizza' }; const pizzeria1 = { name: 'pizzeria1', specialPizza: pizza }; const pizzeria2 = { name: 'pizzeria2', specialPizza: pizza }; brain.getState().objectValue = pizzeria1; brain.getState().otherObjectValue = pizzeria2; expect(brain.getState().__brainChildren.size).toBe(2); expect(brain.getState().objectValue.specialPizza.__brainIsProxy).toBeTruthy(); expect(brain.getState().otherObjectValue.specialPizza.__brainIsProxy).toBeTruthy(); expect(brain.getState().objectValue.specialPizza).toBe(brain.getState().otherObjectValue.specialPizza); expect(brain.getState().objectValue.specialPizza.__brainParents).toBeInstanceOf(Array); expect(brain.getState().objectValue.specialPizza.__brainParents.length).toBe(2); expect(brain.getState().objectValue.specialPizza.__brainParents[0]).toBe(brain.getState().objectValue); expect(brain.getState().objectValue.specialPizza.__brainParents[1]).toBe(brain.getState().otherObjectValue); }); it('Base principles: should remove correctly parents for same proxy, but only if no more child has the same parent', () => { brain = new Brain(state, () => { /* do nothing */ }); const pizza = { key: 'pizza' }; brain.getState().objectValue = pizza; expect(brain.getState().__brainChildren.size).toBe(1); expect(brain.getState().objectValue.__brainParents).toBeInstanceOf(Array); expect(brain.getState().objectValue.__brainParents.length).toBe(1); expect(brain.getState().objectValue.__brainParents[0]).toBe(brain.getState()); brain.getState().otherObjectValue = pizza; expect(brain.getState().__brainChildren.size).toBe(2); expect(brain.getState().objectValue).toBe(brain.getState().otherObjectValue); expect(brain.getState().objectValue.__brainParents).toBeInstanceOf(Array); expect(brain.getState().objectValue.__brainParents.length).toBe(1); // Still 1 : same parent expect(brain.getState().objectValue.__brainParents[0]).toBe(brain.getState()); // Remove the dupplicate // @ts-ignore brain.getState().objectValue = undefined; expect(brain.getState().__brainChildren.size).toBe(1); expect(brain.getState().otherObjectValue.__brainParents).toBeInstanceOf(Array); expect(brain.getState().otherObjectValue.__brainParents.length).toBe(1); // Still 1 : parent remains expect(brain.getState().otherObjectValue.__brainParents[0]).toBe(brain.getState()); }); it('Base principles: should remove correctly parents for same proxy', () => { brain = new Brain(state, () => { /* do nothing */ }); const pizza = { key: 'pizza' }; const pizzeria1 = { name: 'pizzeria1', specialPizza: pizza }; const pizzeria2 = { name: 'pizzeria2', specialPizza: pizza }; brain.getState().objectValue = pizzeria1; brain.getState().otherObjectValue = pizzeria2; expect(brain.getState().__brainChildren.size).toBe(2); expect(brain.getState().objectValue.specialPizza.__brainIsProxy).toBeTruthy(); expect(brain.getState().otherObjectValue.specialPizza.__brainIsProxy).toBeTruthy(); expect(brain.getState().objectValue.specialPizza).toBe(brain.getState().otherObjectValue.specialPizza); // Remove the dupplicate brain.getState().objectValue.specialPizza = undefined; expect(brain.getState().objectValue.specialPizza?.__brainIsProxy).toBeFalsy(); expect(brain.getState().otherObjectValue.specialPizza?.__brainIsProxy).toBeTruthy(); expect(brain.getState().otherObjectValue.specialPizza.__brainParents).toBeInstanceOf(Array); expect(brain.getState().otherObjectValue.specialPizza.__brainParents.length).toBe(1); expect(brain.getState().otherObjectValue.specialPizza.__brainParents[0]).toBe(brain.getState().otherObjectValue); expect(brain.getState().otherObjectValue.specialPizza.__brainParents[0].__brainTarget).toBe(pizzeria2); }); it('Base principles: should be able to affect proxy as new value', () => { brain = new Brain(state, () => { /* do nothing */ }); const pizza = { key: 'pizza' }; brain.getState().objectValue = pizza; brain.getState().otherObjectValue = brain.getState().objectValue; expect(brain.getState().__brainChildren.size).toBe(2); expect(brain.getState().__brainChildren.get('objectValue')).toBe(brain.getState().objectValue); expect(brain.getState().__brainChildren.get('otherObjectValue')).toBe(brain.getState().objectValue); expect(brain.getState().__brainChildren.get('objectValue')).toBe(brain.getState().otherObjectValue); expect(brain.getState().__brainChildren.get('otherObjectValue')).toBe(brain.getState().otherObjectValue); expect(pizza.__brainIsProxy).toBeFalsy(); expect(brain.getState().objectValue.__brainIsProxy).toBeTruthy(); expect(brain.getState().objectValue.__brainTarget).toBe(pizza); expect(brain.getState().objectValue.__brainChildren.size).toBe(0); expect(brain.getState().objectValue.__brainParents[0]).toBe(brain.getState()); expect(brain.getState().objectValue.__brainFullPaths).toBeInstanceOf(Array); expect(brain.getState().objectValue.__brainFullPaths.length).toBe(2); expect(brain.getState().objectValue.__brainFullPaths[0]).toBe('objectValue'); expect(brain.getState().objectValue.__brainFullPaths[1]).toBe('otherObjectValue'); }); it('Base principles: old value should not be a proxy', () => { let controlValue = 1; brain = new Brain(state, (property, oldValue) => { controlValue++; expect(property).toBe('objectValue'); expect(oldValue.__brainIsProxy).toBeFalsy(); }); const pizza1 = { key: 'pizza1' }; const pizza2 = { key: 'pizza2' }; expect(controlValue).toBe(1); brain.getState().objectValue = pizza1; expect(controlValue).toBe(2); brain.getState().objectValue = pizza2; expect(controlValue).toBe(3); }); it('Base principles: old value should be frozen (not extensible)', () => { let controlValue = 1; brain = new Brain(state, (_property, oldValue) => { controlValue++; if (oldValue) { expect(() => { oldValue.key = 'pizza2'; }).toThrowError(); } }); const pizza1 = { key: 'pizza1' }; expect(controlValue).toBe(1); brain.getState().objectValue = pizza1; expect(controlValue).toBe(2); }); it('Base principles: old value should be frozen (readonly properties)', () => { state = { objectValue: { key: 'pizza' }, otherObjectValue: {} }; let controlValue = 1; brain = new Brain(state, (_property, oldValue) => { controlValue++; if (oldValue) { expect(() => { oldValue.key = 'pizza2'; }).toThrowError(); } }); const pizza1 = { key: 'pizza1' }; expect(controlValue).toBe(1); brain.getState().objectValue = pizza1; expect(controlValue).toBe(2); }); it('Base principles: old value should be frozen (array)', () => { state = { objectValue: [], otherObjectValue: {} }; let controlValue = 1; brain = new Brain(state, (_property, oldValue) => { controlValue++; if (oldValue) { expect(() => { oldValue.push('pizza2'); }).toThrowError(); } }); const pizza1 = ['pizza1']; expect(controlValue).toBe(1); brain.getState().objectValue = pizza1; expect(controlValue).toBe(2); }); it('Base principles: old value should be frozen (circular references)', () => { const pizza1 = { ref: {} }; const pizza2 = { ref: pizza1 }; pizza1.ref = pizza2; state = { objectValue: pizza1, otherObjectValue: pizza2 }; let controlValue = 1; brain = new Brain(state, (_property, oldValue) => { controlValue++; if (oldValue) { expect(() => { oldValue.ref = {}; }).toThrowError(); } }); brain.getState().objectValue = {}; expect(controlValue).toBe(2); }); it('Base principles: should only use main path for circular references', () => { const group = { type: 'group', children: [] }; const layer = { type: 'layer', parent: group, active: false }; group.children.push(layer); state = { objectValue: group, otherObjectValue: {} }; brain = new Brain(state, () => { }); // Access the same object under recursive paths brain.getState().objectValue.children[0].active = true; brain.getState().objectValue.children[0].parent.children[0].active = false; brain.getState().objectValue.children[0].parent.children[0].parent.children[0].active = true; expect(brain.getState().objectValue.children[0].__brainFullPaths.length).toBe(1); }); it('Base principles: should use multiple paths for different places in the state (no circular references)', () => { const group1 = { type: 'group', children: [] }; const group2 = { type: 'group', children: [] }; const group3 = { type: 'group', children: [] }; const layer = { type: 'layer', parent: group2, active: false }; group1.children.push(group2); group3.children.push(group2); group2.children.push(layer); state = { objectValue: [group1, group3], otherObjectValue: {} }; brain = new Brain(state, () => { }); // Access the same object under recursive paths const g1 = brain.getState().objectValue[0]; const g3 = brain.getState().objectValue[1]; expect(g1.children[0].__brainIsProxy).toBeTruthy(); expect(g1.children[0]).toBe(g3.children[0]); g1.children[0].children[0].active = true; g1.children[0].children[0].parent.children[0].active = true; g1.children[0].children[0].parent.children[0].parent.children[0].active = true; expect(g1.children[0].__brainFullPaths.length).toBe(2); expect(g1.children[0].__brainFullPaths[0]).toBe('objectValue.0.children.0'); expect(g1.children[0].__brainFullPaths[1]).toBe('objectValue.1.children.0'); expect(g1.children[0].children[0].__brainFullPaths.length).toBe(2); expect(g1.children[0].children[0].__brainFullPaths[0]).toBe('objectValue.0.children.0.children.0'); expect(g1.children[0].children[0].__brainFullPaths[1]).toBe('objectValue.1.children.0.children.0'); }); it('Base principles: should delay callbacks', () => { const pizza1 = { key: 'pizza1' }; state.objectValue = pizza1; let counter = 1; brain = new Brain(state, (property, oldValue, newValue, parents) => { counter++; expect(property).toBe('objectValue.key'); expect(oldValue).toBe('pizza1'); expect(newValue).toBe('pizza4'); expect(parents).toBeInstanceOf(Array); expect(parents[0]).toBe(brain.getState().objectValue); }); brain.delay((proxy) => { proxy.objectValue.key = 'pizza2'; expect(counter).toBe(1); proxy.objectValue.key = 'pizza3'; expect(counter).toBe(1); proxy.objectValue.key = 'pizza4'; expect(counter).toBe(1); }); expect(counter).toBe(2); }); it('Base principles: Do not clone objects with attribute BrainIgnoreClone', () => { class Pizza { constructor(key, owner) { Object.defineProperty(this, "key", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "owner", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.key = key; this.owner = owner; } } __decorate([ BrainIgnoreClone ], Pizza.prototype, "owner", void 0); const pizzeria = { name: 'Noninna', place: 'Aspach' }; const pizza1 = new Pizza('pizza1', pizzeria); const pizza2 = new Pizza('pizza2', pizzeria); state.objectValue = pizza1; let counter = 1; brain = new Brain(state, (property, oldValue, newValue) => { counter++; expect(property).toBe('objectValue'); expect(oldValue.owner).toBe(pizzeria); expect(newValue.owner.__brainTarget).toBe(pizzeria); }); brain.getState().objectValue = pizza2; expect(counter).toBe(2); }); it('Base principles: Do not clone objects with attribute BrainIgnoreClone, but listen to changes', () => { class Pizza { constructor(key, owner) { Object.defineProperty(this, "key", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "owner", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.key = key; this.owner = owner; } } __decorate([ BrainIgnoreClone ], Pizza.prototype, "owner", void 0); const pizzeria = { name: 'Noninna', place: 'Aspach' }; const pizza1 = new Pizza('pizza1', pizzeria); state.objectValue = pizza1; let counter = 1; brain = new Brain(state, (property, oldValue, newValue) => { counter++; expect(property).toBe('objectValue.owner.place'); expect(oldValue).toBe('Aspach'); expect(newValue).toBe('Bernwiller'); }); brain.getState().objectValue.owner.place = 'Bernwiller'; expect(counter).toBe(2); }); });