UNPKG

@traxjs/trax

Version:

Reactive state management

938 lines (846 loc) 60.4 kB
import { beforeEach, describe, expect, it } from 'vitest'; import { createTraxEnv } from '../core'; import { Store, Trax } from '../index'; import { ArrayFamilyStore, printEvents } from './utils'; describe('Arrays', () => { let trax: Trax; beforeEach(() => { trax = createTraxEnv(); }); function printLogs(minCycleId = 0, ignoreCycleEvents = true): string[] { return printEvents(trax.log, ignoreCycleEvents, minCycleId); } /** * 2 kind of arrays * - !Computed: its items are added/sorted/filterd/removed by a processor * - primary: its items are manually managed outside processors (primary values) */ describe('Primary', () => { function createFamilyStore(empty: boolean) { return trax.createStore("FStore", (store: Store<ArrayFamilyStore>) => { if (empty) { store.init({ familyName: "Simpson", members: [] }); } else { store.init({ familyName: "Simpson", members: [{ firstName: "Homer", lastName: "Simpson" }, { firstName: "Marge", lastName: "Simpson" }] }); } let family = store.data; store.compute("Size", () => { family.size = family.members.length; }); store.compute("Names", () => { family.names = family.members.map((m) => m.firstName).join(", "); }) }); } it('should support creation as JSON + push updates (empty)', async () => { const fs = createFamilyStore(true); const f = fs.data; expect(f.members.length).toBe(0); expect(f.size).toBe(0); expect(f.names).toBe(""); await trax.reconciliation(); expect(printLogs(0)).toMatchObject([ "0:1 !PCS - !StoreInit (FStore)", "0:2 !NEW - S: FStore", "0:3 !NEW - O: FStore/data", "0:4 !NEW - P: FStore#Size", "0:5 !PCS - !Compute #1 (FStore#Size) P1 Init - parentId=0:1", "0:6 !NEW - A: FStore/data*members", "0:7 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "0:8 !GET - FStore/data*members.length -> 0", "0:9 !SET - FStore/data.size = 0 (prev: undefined)", "0:10 !PCE - 0:5", "0:11 !NEW - P: FStore#Names", "0:12 !PCS - !Compute #1 (FStore#Names) P2 Init - parentId=0:1", "0:13 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "0:14 !GET - FStore/data*members.map -> '[Function]'", "0:15 !GET - FStore/data*members.length -> 0", "0:16 !SET - FStore/data.names = '' (prev: undefined)", "0:17 !PCE - 0:12", "0:18 !PCE - 0:1", "0:19 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "0:20 !GET - FStore/data*members.length -> 0", "0:21 !GET - FStore/data.size -> 0", "0:22 !GET - FStore/data.names -> ''", ]); // add item trax.log.info("A"); f.members.push({ firstName: "Homer", lastName: "Simpson" }); await trax.reconciliation(); expect(printLogs(1)).toMatchObject([ "1:1 !LOG - A", "1:2 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:3 !GET - FStore/data*members.push -> '[Function]'", "1:4 !GET - FStore/data*members.length -> 0", "1:5 !NEW - O: FStore/data*members*0", "1:6 !SET - FStore/data*members.0 = '[TRAX FStore/data*members*0]' (prev: undefined)", "1:7 !DRT - FStore#Size <- FStore/data*members.length", "1:8 !DRT - FStore#Names <- FStore/data*members.length", "1:9 !PCS - !Reconciliation #1 - 2 processors", "1:10 !PCS - !Compute #2 (FStore#Size) P1 Reconciliation - parentId=1:9", "1:11 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:12 !GET - FStore/data*members.length -> 1", "1:13 !SET - FStore/data.size = 1 (prev: 0)", "1:14 !PCE - 1:10", "1:15 !PCS - !Compute #2 (FStore#Names) P2 Reconciliation - parentId=1:9", "1:16 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:17 !GET - FStore/data*members.map -> '[Function]'", "1:18 !GET - FStore/data*members.length -> 1", "1:19 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "1:20 !GET - FStore/data*members*0.firstName -> 'Homer'", "1:21 !SET - FStore/data.names = 'Homer' (prev: '')", "1:22 !PCE - 1:15", "1:23 !PCE - 1:9", ]); await trax.reconciliation(); trax.log.info("B"); expect(f.members.length).toBe(1); expect(f.size).toBe(1); expect(f.names).toBe("Homer"); expect(printLogs(2)).toMatchObject([ "2:1 !LOG - B", "2:2 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "2:3 !GET - FStore/data*members.length -> 1", "2:4 !GET - FStore/data.size -> 1", "2:5 !GET - FStore/data.names -> 'Homer'", ]); f.members.push({ firstName: "Marge", lastName: "Simpson" }); f.members.push({ firstName: "Bart", lastName: "Simpson" }); f.members.push({ firstName: "Lisa", lastName: "Simpson" }); f.members.push({ firstName: "Maggie", lastName: "Simpson" }); await trax.reconciliation(); expect(f.size).toBe(5); expect(f.names).toBe("Homer, Marge, Bart, Lisa, Maggie"); }); it('should support creation as JSON + push updates (non empty)', async () => { const fs = createFamilyStore(false); const f = fs.data; expect(f.size).toBe(2); expect(f.names).toBe("Homer, Marge"); await trax.reconciliation(); expect(printLogs(0)).toMatchObject([ "0:1 !PCS - !StoreInit (FStore)", "0:2 !NEW - S: FStore", "0:3 !NEW - O: FStore/data", "0:4 !NEW - P: FStore#Size", "0:5 !PCS - !Compute #1 (FStore#Size) P1 Init - parentId=0:1", "0:6 !NEW - A: FStore/data*members", "0:7 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "0:8 !GET - FStore/data*members.length -> 2", "0:9 !SET - FStore/data.size = 2 (prev: undefined)", "0:10 !PCE - 0:5", "0:11 !NEW - P: FStore#Names", "0:12 !PCS - !Compute #1 (FStore#Names) P2 Init - parentId=0:1", "0:13 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "0:14 !GET - FStore/data*members.map -> '[Function]'", "0:15 !GET - FStore/data*members.length -> 2", "0:16 !NEW - O: FStore/data*members*0", "0:17 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "0:18 !GET - FStore/data*members*0.firstName -> 'Homer'", "0:19 !NEW - O: FStore/data*members*1", "0:20 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*1]'", "0:21 !GET - FStore/data*members*1.firstName -> 'Marge'", "0:22 !SET - FStore/data.names = 'Homer, Marge' (prev: undefined)", "0:23 !PCE - 0:12", "0:24 !PCE - 0:1", "0:25 !GET - FStore/data.size -> 2", "0:26 !GET - FStore/data.names -> 'Homer, Marge'", ]); // add item trax.log.info("A"); f.members.push({ firstName: "Bart", lastName: "Simpson" }); f.members.push({ firstName: "Lisa", lastName: "Simpson" }); f.members.push({ firstName: "Maggie", lastName: "Simpson" }); await trax.reconciliation(); expect(f.size).toBe(5); expect(f.names).toBe("Homer, Marge, Bart, Lisa, Maggie"); expect(printLogs(1)).toMatchObject([ "1:1 !LOG - A", "1:2 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:3 !GET - FStore/data*members.push -> '[Function]'", "1:4 !GET - FStore/data*members.length -> 2", "1:5 !NEW - O: FStore/data*members*2", "1:6 !SET - FStore/data*members.2 = '[TRAX FStore/data*members*2]' (prev: undefined)", "1:7 !DRT - FStore#Size <- FStore/data*members.length", "1:8 !DRT - FStore#Names <- FStore/data*members.length", "1:9 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:10 !GET - FStore/data*members.push -> '[Function]'", "1:11 !GET - FStore/data*members.length -> 3", "1:12 !NEW - O: FStore/data*members*3", "1:13 !SET - FStore/data*members.3 = '[TRAX FStore/data*members*3]' (prev: undefined)", "1:14 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:15 !GET - FStore/data*members.push -> '[Function]'", "1:16 !GET - FStore/data*members.length -> 4", "1:17 !NEW - O: FStore/data*members*4", "1:18 !SET - FStore/data*members.4 = '[TRAX FStore/data*members*4]' (prev: undefined)", "1:19 !PCS - !Reconciliation #1 - 2 processors", "1:20 !PCS - !Compute #2 (FStore#Size) P1 Reconciliation - parentId=1:19", "1:21 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:22 !GET - FStore/data*members.length -> 5", "1:23 !SET - FStore/data.size = 5 (prev: 2)", "1:24 !PCE - 1:20", "1:25 !PCS - !Compute #2 (FStore#Names) P2 Reconciliation - parentId=1:19", "1:26 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:27 !GET - FStore/data*members.map -> '[Function]'", "1:28 !GET - FStore/data*members.length -> 5", "1:29 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "1:30 !GET - FStore/data*members*0.firstName -> 'Homer'", "1:31 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*1]'", "1:32 !GET - FStore/data*members*1.firstName -> 'Marge'", "1:33 !GET - FStore/data*members.2 -> '[TRAX FStore/data*members*2]'", "1:34 !GET - FStore/data*members*2.firstName -> 'Bart'", "1:35 !GET - FStore/data*members.3 -> '[TRAX FStore/data*members*3]'", "1:36 !GET - FStore/data*members*3.firstName -> 'Lisa'", "1:37 !GET - FStore/data*members.4 -> '[TRAX FStore/data*members*4]'", "1:38 !GET - FStore/data*members*4.firstName -> 'Maggie'", "1:39 !SET - FStore/data.names = 'Homer, Marge, Bart, Lisa, Maggie' (prev: 'Homer, Marge')", "1:40 !PCE - 1:25", "1:41 !PCE - 1:19", "2:1 !GET - FStore/data.size -> 5", "2:2 !GET - FStore/data.names -> 'Homer, Marge, Bart, Lisa, Maggie'", ]); }); it('should support creation as JSON + manual updates (empty)', async () => { const fs = createFamilyStore(true); const f = fs.data; expect(f.members.length).toBe(0); expect(f.size).toBe(0); expect(f.names).toBe(""); await trax.reconciliation(); // add item trax.log.info("A"); f.members[0] = { firstName: "Homer", lastName: "Simpson" }; // keep 1 empty f.members[2] = { firstName: "Marge", lastName: "Simpson" }; await trax.reconciliation(); expect(f.size).toBe(3); expect(f.names).toBe("Homer, , Marge"); expect(printLogs(1)).toMatchObject([ "1:1 !LOG - A", "1:2 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:3 !NEW - O: FStore/data*members*0", "1:4 !SET - FStore/data*members.0 = '[TRAX FStore/data*members*0]' (prev: undefined)", "1:5 !DRT - FStore#Size <- FStore/data*members.length", "1:6 !DRT - FStore#Names <- FStore/data*members.length", "1:7 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:8 !NEW - O: FStore/data*members*2", "1:9 !SET - FStore/data*members.2 = '[TRAX FStore/data*members*2]' (prev: undefined)", "1:10 !PCS - !Reconciliation #1 - 2 processors", "1:11 !PCS - !Compute #2 (FStore#Size) P1 Reconciliation - parentId=1:10", "1:12 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:13 !GET - FStore/data*members.length -> 3", "1:14 !SET - FStore/data.size = 3 (prev: 0)", "1:15 !PCE - 1:11", "1:16 !PCS - !Compute #2 (FStore#Names) P2 Reconciliation - parentId=1:10", "1:17 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:18 !GET - FStore/data*members.map -> '[Function]'", "1:19 !GET - FStore/data*members.length -> 3", "1:20 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "1:21 !GET - FStore/data*members*0.firstName -> 'Homer'", "1:22 !GET - FStore/data*members.2 -> '[TRAX FStore/data*members*2]'", "1:23 !GET - FStore/data*members*2.firstName -> 'Marge'", "1:24 !SET - FStore/data.names = 'Homer, , Marge' (prev: '')", "1:25 !PCE - 1:16", "1:26 !PCE - 1:10", "2:1 !GET - FStore/data.size -> 3", "2:2 !GET - FStore/data.names -> 'Homer, , Marge'", ]); }); it('should support splice', async () => { const fs = createFamilyStore(false); const f = fs.data; const members = f.members; members.push({ firstName: "Bart", lastName: "Simpson" }); members.push({ firstName: "Lisa", lastName: "Simpson" }); members.push({ firstName: "Maggie", lastName: "Simpson" }); await trax.reconciliation(); expect(f.size).toBe(5); expect(f.names).toBe("Homer, Marge, Bart, Lisa, Maggie"); await trax.reconciliation(); trax.log.info("A"); members.splice(1, 2); // removal of 2 elements await trax.reconciliation(); expect(f.size).toBe(3); expect(f.names).toBe("Homer, Lisa, Maggie"); trax.log.info("B"); members.splice(1, 0, { firstName: "MARGE", lastName: "S" }, { firstName: "BART", lastName: "S" }); // removal of 0 and add of 2 elements await trax.reconciliation(); expect(f.size).toBe(5); expect(f.names).toBe("Homer, MARGE, BART, Lisa, Maggie"); }); it('should support creation through add() - empty array', async () => { const fs = createFamilyStore(false); const f = fs.data; let output = ""; const pr = fs.compute("Summary", () => { if (f.infos) { output = f.infos.map(info => info.desc).join("; "); } else { output = ""; } }); expect(pr.computeCount).toBe(1); await trax.reconciliation(); trax.log.info("A"); const infos = fs.add<{ desc: string }[]>("Infos", []); f.infos = infos; await trax.reconciliation(); expect(output).toBe(""); expect(pr.computeCount).toBe(2); expect(printLogs(1)).toMatchObject([ "1:1 !LOG - A", "1:2 !NEW - A: FStore/Infos", "1:3 !SET - FStore/data.infos = '[TRAX FStore/Infos]' (prev: undefined)", "1:4 !DRT - FStore#Summary <- FStore/data.infos", "1:5 !PCS - !Reconciliation #1 - 3 processors", "1:6 !PCS - !Compute #2 (FStore#Summary) P3 Reconciliation - parentId=1:5", "1:7 !GET - FStore/data.infos -> '[TRAX FStore/Infos]'", "1:8 !GET - FStore/data.infos -> '[TRAX FStore/Infos]'", "1:9 !GET - FStore/Infos.map -> '[Function]'", "1:10 !GET - FStore/Infos.length -> 0", "1:11 !PCE - 1:6", "1:12 !PCE - 1:5", ]); trax.log.info("B"); infos.push({ desc: "AAA" }, { desc: "BBB" }); await trax.reconciliation(); expect(output).toBe('AAA; BBB'); expect(pr.computeCount).toBe(3); expect(printLogs(2)).toMatchObject([ "2:1 !LOG - B", "2:2 !GET - FStore/Infos.push -> '[Function]'", "2:3 !GET - FStore/Infos.length -> 0", "2:4 !NEW - O: FStore/Infos*0", "2:5 !SET - FStore/Infos.0 = '[TRAX FStore/Infos*0]' (prev: undefined)", "2:6 !DRT - FStore#Summary <- FStore/Infos.length", "2:7 !NEW - O: FStore/Infos*1", "2:8 !SET - FStore/Infos.1 = '[TRAX FStore/Infos*1]' (prev: undefined)", "2:9 !PCS - !Reconciliation #2 - 3 processors", "2:10 !PCS - !Compute #3 (FStore#Summary) P3 Reconciliation - parentId=2:9", "2:11 !GET - FStore/data.infos -> '[TRAX FStore/Infos]'", "2:12 !GET - FStore/data.infos -> '[TRAX FStore/Infos]'", "2:13 !GET - FStore/Infos.map -> '[Function]'", "2:14 !GET - FStore/Infos.length -> 2", "2:15 !GET - FStore/Infos.0 -> '[TRAX FStore/Infos*0]'", "2:16 !GET - FStore/Infos*0.desc -> 'AAA'", "2:17 !GET - FStore/Infos.1 -> '[TRAX FStore/Infos*1]'", "2:18 !GET - FStore/Infos*1.desc -> 'BBB'", "2:19 !PCE - 2:10", "2:20 !PCE - 2:9", ]); }); it('should support creation through add() - non empty array', async () => { const fs = createFamilyStore(false); const f = fs.data; let output = ""; const pr = fs.compute("Summary", () => { if (f.infos) { output = f.infos.map(info => info.desc).join("; "); } else { output = ""; } }); expect(pr.computeCount).toBe(1); await trax.reconciliation(); trax.log.info("A"); const infos = fs.add<{ desc: string }[]>("Infos", [{ desc: "AAA" }]); f.infos = infos; await trax.reconciliation(); expect(output).toBe("AAA"); expect(pr.computeCount).toBe(2); expect(printLogs(1)).toMatchObject([ "1:1 !LOG - A", "1:2 !NEW - A: FStore/Infos", "1:3 !SET - FStore/data.infos = '[TRAX FStore/Infos]' (prev: undefined)", "1:4 !DRT - FStore#Summary <- FStore/data.infos", "1:5 !PCS - !Reconciliation #1 - 3 processors", "1:6 !PCS - !Compute #2 (FStore#Summary) P3 Reconciliation - parentId=1:5", "1:7 !GET - FStore/data.infos -> '[TRAX FStore/Infos]'", "1:8 !GET - FStore/data.infos -> '[TRAX FStore/Infos]'", "1:9 !GET - FStore/Infos.map -> '[Function]'", "1:10 !GET - FStore/Infos.length -> 1", "1:11 !NEW - O: FStore/Infos*0", "1:12 !GET - FStore/Infos.0 -> '[TRAX FStore/Infos*0]'", "1:13 !GET - FStore/Infos*0.desc -> 'AAA'", "1:14 !PCE - 1:6", "1:15 !PCE - 1:5", ]); }); it('should not autowrap values when accessing an out-of-bound value', async () => { const fs = createFamilyStore(false); const f = fs.data; expect(f.members.length).toBe(2); f.members.push({ firstName: "Bart", lastName: "Simpson" }); expect(f.members.length).toBe(3); expect(f.members[5]).toBe(undefined); expect(f.members.length).toBe(3); }); describe('Arrays of Arrays', () => { it('should support creation as JSON (empty)', async () => { let output = ""; const fs = trax.createStore("FStore", (store: Store<ArrayFamilyStore>) => { const f = store.init({ familyName: "Simpson", members: [], misc: [] }); store.compute("MiscRender", () => { if (!f.misc) { output = "--"; } else { output = f.misc.map(arr => arr.map(item => item.desc).join(";")).join("/") || "-"; } }); }); const f = fs.data; await trax.reconciliation(); expect(output).toBe("-"); trax.log.info("A"); f.misc!.push([{ desc: "AAA" }, { desc: "BBB" }]); f.misc!.push([{ desc: "CCC" }, { desc: "DDD" }]); await trax.reconciliation(); expect(output).toBe("AAA;BBB/CCC;DDD") expect(printLogs(0)).toMatchObject([ "0:1 !PCS - !StoreInit (FStore)", "0:2 !NEW - S: FStore", "0:3 !NEW - O: FStore/data", "0:4 !NEW - P: FStore#MiscRender", "0:5 !PCS - !Compute #1 (FStore#MiscRender) P1 Init - parentId=0:1", "0:6 !NEW - A: FStore/data*misc", "0:7 !GET - FStore/data.misc -> '[TRAX FStore/data*misc]'", "0:8 !GET - FStore/data.misc -> '[TRAX FStore/data*misc]'", "0:9 !GET - FStore/data*misc.map -> '[Function]'", "0:10 !GET - FStore/data*misc.length -> 0", "0:11 !PCE - 0:5", "0:12 !PCE - 0:1", "1:1 !LOG - A", "1:2 !GET - FStore/data.misc -> '[TRAX FStore/data*misc]'", "1:3 !GET - FStore/data*misc.push -> '[Function]'", "1:4 !GET - FStore/data*misc.length -> 0", "1:5 !NEW - A: FStore/data*misc*0", "1:6 !SET - FStore/data*misc.0 = '[TRAX FStore/data*misc*0]' (prev: undefined)", "1:7 !DRT - FStore#MiscRender <- FStore/data*misc.length", "1:8 !GET - FStore/data.misc -> '[TRAX FStore/data*misc]'", "1:9 !GET - FStore/data*misc.push -> '[Function]'", "1:10 !GET - FStore/data*misc.length -> 1", "1:11 !NEW - A: FStore/data*misc*1", "1:12 !SET - FStore/data*misc.1 = '[TRAX FStore/data*misc*1]' (prev: undefined)", "1:13 !PCS - !Reconciliation #1 - 1 processor", "1:14 !PCS - !Compute #2 (FStore#MiscRender) P1 Reconciliation - parentId=1:13", "1:15 !GET - FStore/data.misc -> '[TRAX FStore/data*misc]'", "1:16 !GET - FStore/data.misc -> '[TRAX FStore/data*misc]'", "1:17 !GET - FStore/data*misc.map -> '[Function]'", "1:18 !GET - FStore/data*misc.length -> 2", "1:19 !GET - FStore/data*misc.0 -> '[TRAX FStore/data*misc*0]'", "1:20 !GET - FStore/data*misc*0.map -> '[Function]'", "1:21 !GET - FStore/data*misc*0.length -> 2", "1:22 !NEW - O: FStore/data*misc*0*0", "1:23 !GET - FStore/data*misc*0.0 -> '[TRAX FStore/data*misc*0*0]'", "1:24 !GET - FStore/data*misc*0*0.desc -> 'AAA'", "1:25 !NEW - O: FStore/data*misc*0*1", "1:26 !GET - FStore/data*misc*0.1 -> '[TRAX FStore/data*misc*0*1]'", "1:27 !GET - FStore/data*misc*0*1.desc -> 'BBB'", "1:28 !GET - FStore/data*misc.1 -> '[TRAX FStore/data*misc*1]'", "1:29 !GET - FStore/data*misc*1.map -> '[Function]'", "1:30 !GET - FStore/data*misc*1.length -> 2", "1:31 !NEW - O: FStore/data*misc*1*0", "1:32 !GET - FStore/data*misc*1.0 -> '[TRAX FStore/data*misc*1*0]'", "1:33 !GET - FStore/data*misc*1*0.desc -> 'CCC'", "1:34 !NEW - O: FStore/data*misc*1*1", "1:35 !GET - FStore/data*misc*1.1 -> '[TRAX FStore/data*misc*1*1]'", "1:36 !GET - FStore/data*misc*1*1.desc -> 'DDD'", "1:37 !PCE - 1:14", "1:38 !PCE - 1:13" ]); f.misc!.push([{ desc: "EEE" }]); await trax.reconciliation(); expect(output).toBe("AAA;BBB/CCC;DDD/EEE") }); it('should support creation as JSON (non empty)', async () => { let output = ""; const fs = trax.createStore("FStore", (store: Store<ArrayFamilyStore>) => { const f = store.init({ familyName: "Simpson", members: [], misc: [ [{ desc: "AAA" }, { desc: "BBB" }], [{ desc: "CCC" }, { desc: "DDD" }] ] }); store.compute("MiscRender", () => { if (!f.misc) { output = "--"; } else { output = f.misc.map(arr => arr.map(item => item.desc).join(";")).join("/") || "-"; } }); }); const f = fs.data; expect(output).toBe("AAA;BBB/CCC;DDD") f.misc!.push([{ desc: "EEE" }]); expect(trax.getTraxId(f.misc![0][1])).toBe("FStore/data*misc*0*1"); await trax.reconciliation(); expect(output).toBe("AAA;BBB/CCC;DDD/EEE") }); it('should support creation through add()', async () => { let output = ""; const fs = trax.createStore("FStore", (store: Store<ArrayFamilyStore>) => { const f = store.init({ familyName: "Simpson", members: [], misc: [] }); store.compute("MiscRender", () => { if (!f.misc) { output = "--"; } else { output = f.misc.map(arr => arr.map(item => item.desc).join(";")).join("/") || "-"; } }); }); const f = fs.data; await trax.reconciliation(); expect(output).toBe("-"); trax.log.info("A"); const line0 = fs.add("Line0", [{ desc: "AAA" }, { desc: "BBB" }]); const line1 = fs.add("Line1", [{ desc: "CCC" }, { desc: "DDD" }]); f.misc!.push(line0); f.misc!.push(line1); await trax.reconciliation(); expect(output).toBe("AAA;BBB/CCC;DDD") expect(trax.getTraxId(f.misc![0][1])).toBe("FStore/Line0*1"); expect(trax.getTraxId(f.misc![1][0])).toBe("FStore/Line1*0"); }); }); }); describe('Computed', () => { function createFamilyStore(empty: boolean) { return trax.createStore("FStore", (store: Store<ArrayFamilyStore>) => { if (empty) { store.init({ familyName: "Simpson", members: [] }); } else { store.init({ familyName: "Simpson", members: [{ firstName: "Homer", lastName: "Simpson" }, { firstName: "Marge", lastName: "Simpson" }] }); } let family = store.data; store.compute("Desc", () => { // Compute Misc content from members let infos = family.infos; if (!infos) { // create the array infos = family.infos = []; } let content = family.members.map((m) => { const info = store.add(["Info", m], { desc: "" }); info.desc = m.firstName + " " + m.lastName; return info; }); // sort by desc content = content.sort((c1, c2) => c1.desc === c2.desc ? 0 : c1.desc > c2.desc ? 1 : -1); trax.updateArray(infos, content); }); }); } function printInfos(infos: ArrayFamilyStore["infos"]) { return infos?.map(info => info.desc).join(";"); } it('should compute array from another one (empty start)', async () => { const fs = createFamilyStore(true); const family = fs.data; const members = family.members; expect(family.infos?.length).toBe(0); await trax.reconciliation(); trax.log.info("Size Increase"); members.push({ firstName: "Homer", lastName: "Simpson" }); members.push({ firstName: "Bart", lastName: "Simpson" }); await trax.reconciliation(); expect(printLogs(1)).toMatchObject([ "1:1 !LOG - Size Increase", "1:2 !GET - FStore/data*members.push -> '[Function]'", "1:3 !GET - FStore/data*members.length -> 0", "1:4 !NEW - O: FStore/data*members*0", "1:5 !SET - FStore/data*members.0 = '[TRAX FStore/data*members*0]' (prev: undefined)", "1:6 !DRT - FStore#Desc <- FStore/data*members.length", "1:7 !GET - FStore/data*members.push -> '[Function]'", "1:8 !GET - FStore/data*members.length -> 1", "1:9 !NEW - O: FStore/data*members*1", "1:10 !SET - FStore/data*members.1 = '[TRAX FStore/data*members*1]' (prev: undefined)", "1:11 !PCS - !Reconciliation #1 - 1 processor", "1:12 !PCS - !Compute #2 (FStore#Desc) P1 Reconciliation - parentId=1:11", "1:13 !GET - FStore/data.infos -> '[TRAX FStore/data*infos]'", "1:14 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:15 !GET - FStore/data*members.map -> '[Function]'", "1:16 !GET - FStore/data*members.length -> 2", "1:17 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "1:18 !NEW - O: FStore/Info:data*members*0", "1:19 !GET - FStore/data*members*0.firstName -> 'Homer'", "1:20 !GET - FStore/data*members*0.lastName -> 'Simpson'", "1:21 !SET - FStore/Info:data*members*0.desc = 'Homer Simpson' (prev: '')", "1:22 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*1]'", "1:23 !NEW - O: FStore/Info:data*members*1", "1:24 !GET - FStore/data*members*1.firstName -> 'Bart'", "1:25 !GET - FStore/data*members*1.lastName -> 'Simpson'", "1:26 !SET - FStore/Info:data*members*1.desc = 'Bart Simpson' (prev: '')", "1:27 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "1:28 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "1:29 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "1:30 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "1:31 !PCS - !ArrayUpdate (FStore/data*infos) - parentId=1:12", "1:32 !GET - FStore/data*infos.length -> 0", "1:33 !SET - FStore/data*infos.0 = '[TRAX FStore/Info:data*members*1]' (prev: undefined)", "1:34 !SET - FStore/data*infos.1 = '[TRAX FStore/Info:data*members*0]' (prev: undefined)", "1:35 !PCE - 1:31", "1:36 !PCE - 1:12", "1:37 !PCE - 1:11", ]); const infos = family.infos; expect(infos?.length).toBe(2); expect(printInfos(infos)).toBe("Bart Simpson;Homer Simpson"); // Bart first (cf. sort) await trax.reconciliation(); // to get a new log cycle trax.log.info("Size Increase 2"); members.push({ firstName: "Marge", lastName: "Simpson" }); await trax.reconciliation(); expect(printLogs(3)).toMatchObject([ "3:1 !LOG - Size Increase 2", "3:2 !GET - FStore/data*members.push -> '[Function]'", "3:3 !GET - FStore/data*members.length -> 2", "3:4 !NEW - O: FStore/data*members*2", "3:5 !SET - FStore/data*members.2 = '[TRAX FStore/data*members*2]' (prev: undefined)", "3:6 !DRT - FStore#Desc <- FStore/data*members.length", "3:7 !PCS - !Reconciliation #2 - 1 processor", "3:8 !PCS - !Compute #3 (FStore#Desc) P1 Reconciliation - parentId=3:7", "3:9 !GET - FStore/data.infos -> '[TRAX FStore/data*infos]'", "3:10 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "3:11 !GET - FStore/data*members.map -> '[Function]'", "3:12 !GET - FStore/data*members.length -> 3", "3:13 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", // already exists: no new "3:14 !GET - FStore/data*members*0.firstName -> 'Homer'", "3:15 !GET - FStore/data*members*0.lastName -> 'Simpson'", "3:16 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*1]'", "3:17 !GET - FStore/data*members*1.firstName -> 'Bart'", "3:18 !GET - FStore/data*members*1.lastName -> 'Simpson'", "3:19 !GET - FStore/data*members.2 -> '[TRAX FStore/data*members*2]'", "3:20 !NEW - O: FStore/Info:data*members*2", // new info item "3:21 !GET - FStore/data*members*2.firstName -> 'Marge'", "3:22 !GET - FStore/data*members*2.lastName -> 'Simpson'", "3:23 !SET - FStore/Info:data*members*2.desc = 'Marge Simpson' (prev: '')", "3:24 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "3:25 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "3:26 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "3:27 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "3:28 !GET - FStore/Info:data*members*2.desc -> 'Marge Simpson'", "3:29 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "3:30 !GET - FStore/Info:data*members*2.desc -> 'Marge Simpson'", "3:31 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "3:32 !GET - FStore/Info:data*members*2.desc -> 'Marge Simpson'", "3:33 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "3:34 !GET - FStore/Info:data*members*2.desc -> 'Marge Simpson'", "3:35 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "3:36 !PCS - !ArrayUpdate (FStore/data*infos) - parentId=3:8", "3:37 !GET - FStore/data*infos.length -> 2", "3:38 !SET - FStore/data*infos.2 = '[TRAX FStore/Info:data*members*2]' (prev: undefined)", "3:39 !PCE - 3:36", "3:40 !PCE - 3:8", "3:41 !PCE - 3:7", ]); expect(printInfos(infos)).toBe("Bart Simpson;Homer Simpson;Marge Simpson"); await trax.reconciliation(); // to get a new log cycle trax.log.info("Size Decrease"); members.splice(0, 1); await trax.reconciliation(); expect(printLogs(5)).toMatchObject([ "5:1 !LOG - Size Decrease", "5:2 !GET - FStore/data*members.splice -> '[Function]'", "5:3 !GET - FStore/data*members.length -> 3", "5:4 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "5:5 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*1]'", "5:6 !SET - FStore/data*members.0 = '[TRAX FStore/data*members*1]' (prev: '[TRAX FStore/data*members*0]')", "5:7 !DRT - FStore#Desc <- FStore/data*members.0", "5:8 !GET - FStore/data*members.2 -> '[TRAX FStore/data*members*2]'", "5:9 !SET - FStore/data*members.1 = '[TRAX FStore/data*members*2]' (prev: '[TRAX FStore/data*members*1]')", "5:10 !SET - FStore/data*members.length = 2 (prev: 3)", "5:11 !PCS - !Reconciliation #3 - 1 processor", "5:12 !PCS - !Compute #4 (FStore#Desc) P1 Reconciliation - parentId=5:11", "5:13 !GET - FStore/data.infos -> '[TRAX FStore/data*infos]'", "5:14 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "5:15 !GET - FStore/data*members.map -> '[Function]'", "5:16 !GET - FStore/data*members.length -> 2", "5:17 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*1]'", "5:18 !GET - FStore/data*members*1.firstName -> 'Bart'", "5:19 !GET - FStore/data*members*1.lastName -> 'Simpson'", "5:20 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*2]'", "5:21 !GET - FStore/data*members*2.firstName -> 'Marge'", "5:22 !GET - FStore/data*members*2.lastName -> 'Simpson'", "5:23 !GET - FStore/Info:data*members*2.desc -> 'Marge Simpson'", "5:24 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "5:25 !GET - FStore/Info:data*members*2.desc -> 'Marge Simpson'", "5:26 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "5:27 !PCS - !ArrayUpdate (FStore/data*infos) - parentId=5:12", "5:28 !GET - FStore/data*infos.length -> 3", "5:29 !SET - FStore/data*infos.1 = '[TRAX FStore/Info:data*members*2]' (prev: '[TRAX FStore/Info:data*members*0]')", "5:30 !SET - FStore/data*infos.2 = undefined (prev: '[TRAX FStore/Info:data*members*2]')", "5:31 !GET - FStore/data*infos.splice -> '[Function]'", "5:32 !GET - FStore/data*infos.length -> 3", "5:33 !GET - FStore/data*infos.2 -> undefined", "5:34 !SET - FStore/data*infos.length = 2 (prev: 3)", "5:35 !PCE - 5:27", "5:36 !PCE - 5:12", "5:37 !PCE - 5:11", ]); expect(printInfos(infos)).toBe("Bart Simpson;Marge Simpson"); await trax.reconciliation(); // to get a new log cycle trax.log.info("Update with no size changes"); members[1].firstName = "Alex"; await trax.reconciliation(); expect(printLogs(7)).toMatchObject([ "7:1 !LOG - Update with no size changes", "7:2 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*2]'", "7:3 !SET - FStore/data*members*2.firstName = 'Alex' (prev: 'Marge')", "7:4 !DRT - FStore#Desc <- FStore/data*members*2.firstName", "7:5 !PCS - !Reconciliation #4 - 1 processor", "7:6 !PCS - !Compute #5 (FStore#Desc) P1 Reconciliation - parentId=7:5", "7:7 !GET - FStore/data.infos -> '[TRAX FStore/data*infos]'", "7:8 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "7:9 !GET - FStore/data*members.map -> '[Function]'", "7:10 !GET - FStore/data*members.length -> 2", "7:11 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*1]'", "7:12 !GET - FStore/data*members*1.firstName -> 'Bart'", "7:13 !GET - FStore/data*members*1.lastName -> 'Simpson'", "7:14 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*2]'", "7:15 !GET - FStore/data*members*2.firstName -> 'Alex'", "7:16 !GET - FStore/data*members*2.lastName -> 'Simpson'", "7:17 !SET - FStore/Info:data*members*2.desc = 'Alex Simpson' (prev: 'Marge Simpson')", "7:18 !GET - FStore/Info:data*members*2.desc -> 'Alex Simpson'", "7:19 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "7:20 !GET - FStore/Info:data*members*2.desc -> 'Alex Simpson'", "7:21 !GET - FStore/Info:data*members*1.desc -> 'Bart Simpson'", "7:22 !PCS - !ArrayUpdate (FStore/data*infos) - parentId=7:6", "7:23 !GET - FStore/data*infos.length -> 2", "7:24 !SET - FStore/data*infos.0 = '[TRAX FStore/Info:data*members*2]' (prev: '[TRAX FStore/Info:data*members*1]')", "7:25 !SET - FStore/data*infos.1 = '[TRAX FStore/Info:data*members*1]' (prev: '[TRAX FStore/Info:data*members*2]')", "7:26 !PCE - 7:22", "7:27 !PCE - 7:6", "7:28 !PCE - 7:5", ]); expect(printInfos(infos)).toBe("Alex Simpson;Bart Simpson"); }); it('should compute array from another one (non empty start)', async () => { const fs = createFamilyStore(false); const family = fs.data; const members = family.members; const infos = family.infos; expect(family.infos?.length).toBe(2); await trax.reconciliation(); expect(printInfos(infos)).toBe("Homer Simpson;Marge Simpson"); trax.log.info("Size Increase"); members.push({ firstName: "Bart", lastName: "Simpson" }); await trax.reconciliation(); expect(printLogs(1)).toMatchObject([ "1:1 !GET - FStore/data*infos.map -> '[Function]'", "1:2 !GET - FStore/data*infos.length -> 2", "1:3 !GET - FStore/data*infos.0 -> '[TRAX FStore/Info:data*members*0]'", "1:4 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "1:5 !GET - FStore/data*infos.1 -> '[TRAX FStore/Info:data*members*1]'", "1:6 !GET - FStore/Info:data*members*1.desc -> 'Marge Simpson'", "1:7 !LOG - Size Increase", "1:8 !GET - FStore/data*members.push -> '[Function]'", "1:9 !GET - FStore/data*members.length -> 2", "1:10 !NEW - O: FStore/data*members*2", "1:11 !SET - FStore/data*members.2 = '[TRAX FStore/data*members*2]' (prev: undefined)", "1:12 !DRT - FStore#Desc <- FStore/data*members.length", "1:13 !PCS - !Reconciliation #1 - 1 processor", "1:14 !PCS - !Compute #2 (FStore#Desc) P1 Reconciliation - parentId=1:13", "1:15 !GET - FStore/data.infos -> '[TRAX FStore/data*infos]'", "1:16 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "1:17 !GET - FStore/data*members.map -> '[Function]'", "1:18 !GET - FStore/data*members.length -> 3", "1:19 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "1:20 !GET - FStore/data*members*0.firstName -> 'Homer'", "1:21 !GET - FStore/data*members*0.lastName -> 'Simpson'", "1:22 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*1]'", "1:23 !GET - FStore/data*members*1.firstName -> 'Marge'", "1:24 !GET - FStore/data*members*1.lastName -> 'Simpson'", "1:25 !GET - FStore/data*members.2 -> '[TRAX FStore/data*members*2]'", "1:26 !NEW - O: FStore/Info:data*members*2", "1:27 !GET - FStore/data*members*2.firstName -> 'Bart'", "1:28 !GET - FStore/data*members*2.lastName -> 'Simpson'", "1:29 !SET - FStore/Info:data*members*2.desc = 'Bart Simpson' (prev: '')", "1:30 !GET - FStore/Info:data*members*1.desc -> 'Marge Simpson'", "1:31 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "1:32 !GET - FStore/Info:data*members*1.desc -> 'Marge Simpson'", "1:33 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "1:34 !GET - FStore/Info:data*members*2.desc -> 'Bart Simpson'", "1:35 !GET - FStore/Info:data*members*1.desc -> 'Marge Simpson'", "1:36 !GET - FStore/Info:data*members*2.desc -> 'Bart Simpson'", "1:37 !GET - FStore/Info:data*members*1.desc -> 'Marge Simpson'", "1:38 !GET - FStore/Info:data*members*2.desc -> 'Bart Simpson'", "1:39 !GET - FStore/Info:data*members*1.desc -> 'Marge Simpson'", "1:40 !GET - FStore/Info:data*members*2.desc -> 'Bart Simpson'", "1:41 !GET - FStore/Info:data*members*1.desc -> 'Marge Simpson'", "1:42 !GET - FStore/Info:data*members*2.desc -> 'Bart Simpson'", "1:43 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "1:44 !GET - FStore/Info:data*members*2.desc -> 'Bart Simpson'", "1:45 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "1:46 !PCS - !ArrayUpdate (FStore/data*infos) - parentId=1:14", "1:47 !GET - FStore/data*infos.length -> 2", "1:48 !SET - FStore/data*infos.0 = '[TRAX FStore/Info:data*members*2]' (prev: '[TRAX FStore/Info:data*members*0]')", "1:49 !SET - FStore/data*infos.1 = '[TRAX FStore/Info:data*members*0]' (prev: '[TRAX FStore/Info:data*members*1]')", "1:50 !SET - FStore/data*infos.2 = '[TRAX FStore/Info:data*members*1]' (prev: undefined)", "1:51 !PCE - 1:46", "1:52 !PCE - 1:14", "1:53 !PCE - 1:13", ]); expect(printInfos(infos)).toBe("Bart Simpson;Homer Simpson;Marge Simpson"); await trax.reconciliation(); trax.log.info("No changes"); members[0].firstName = "HOMER"; members[0].firstName = "Homer"; // revert await trax.reconciliation(); expect(printLogs(3)).toMatchObject([ "3:1 !LOG - No changes", "3:2 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "3:3 !SET - FStore/data*members*0.firstName = 'HOMER' (prev: 'Homer')", "3:4 !DRT - FStore#Desc <- FStore/data*members*0.firstName", "3:5 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "3:6 !SET - FStore/data*members*0.firstName = 'Homer' (prev: 'HOMER')", "3:7 !PCS - !Reconciliation #2 - 1 processor", "3:8 !PCS - !Compute #3 (FStore#Desc) P1 Reconciliation - parentId=3:7", "3:9 !GET - FStore/data.infos -> '[TRAX FStore/data*infos]'", "3:10 !GET - FStore/data.members -> '[TRAX FStore/data*members]'", "3:11 !GET - FStore/data*members.map -> '[Function]'", "3:12 !GET - FStore/data*members.length -> 3", "3:13 !GET - FStore/data*members.0 -> '[TRAX FStore/data*members*0]'", "3:14 !GET - FStore/data*members*0.firstName -> 'Homer'", "3:15 !GET - FStore/data*members*0.lastName -> 'Simpson'", "3:16 !GET - FStore/data*members.1 -> '[TRAX FStore/data*members*1]'", "3:17 !GET - FStore/data*members*1.firstName -> 'Marge'", "3:18 !GET - FStore/data*members*1.lastName -> 'Simpson'", "3:19 !GET - FStore/data*members.2 -> '[TRAX FStore/data*members*2]'", "3:20 !GET - FStore/data*members*2.firstName -> 'Bart'", "3:21 !GET - FStore/data*members*2.lastName -> 'Simpson'", "3:22 !GET - FStore/Info:data*members*1.desc -> 'Marge Simpson'", "3:23 !GET - FStore/Info:data*members*0.desc -> 'Homer Simpson'", "3:24 !GET - FStore/Info:data*members*