UNPKG

@traxjs/trax

Version:

Reactive state management

317 lines (249 loc) 10.3 kB
import { beforeEach, describe, expect, it } from 'vitest'; import { createTraxEnv } from '../core'; import { Trax } from '../types'; import { printEvents } from './utils'; /** * Ref Props are props that should be considered as references * and should not be auto-wrapped * This behaviour is controlled through a naming convention: the property name must start * with 1 or more $ signs */ describe('Ref Props', () => { let trax: Trax; beforeEach(() => { trax = createTraxEnv(); }); function printLogs(minCycleId = 0, ignoreCycleEvents = true): string[] { return printEvents(trax.log, ignoreCycleEvents, minCycleId); } interface Person { name: string; $parents?: { father: string; mother: string; } } interface Person1 { name: string; $family: Person[]; } interface Person21 { name: string; $$family: Person[]; } interface Person22 { name: string; $$family: Person[][]; } interface Person3 { name: string; $$$family: Person[][]; } it('should be supported for sub-objects (level 1)', async () => { const ps = trax.createStore<Person>("PStore", { name: "Bart", $parents: { father: "Homer", mother: "Marge" } }); const p = ps.data; let output = ""; ps.compute("Render", () => { const parents = p.$parents; output = `${p.name} / ${parents!.father} & ${parents!.mother}`; }); expect(output).toBe("Bart / Homer & Marge"); await trax.reconciliation(); trax.log.info("A"); p.name = "BART"; await trax.reconciliation(); expect(output).toBe("BART / Homer & Marge"); trax.log.info("B"); p.$parents!.father = "HOMER"; await trax.reconciliation(); trax.log.info("C"); expect(output).toBe("BART / Homer & Marge"); // no changes p.name = "Bart"; await trax.reconciliation(); expect(output).toBe("Bart / HOMER & Marge"); // re-processed because of Bart change expect(printLogs(1)).toMatchObject([ "1:1 !LOG - A", "1:2 !SET - PStore/data.name = 'BART' (prev: 'Bart')", "1:3 !DRT - PStore#Render <- PStore/data.name", "1:4 !PCS - !Reconciliation #1 - 1 processor", "1:5 !PCS - !Compute #2 (PStore#Render) P1 Reconciliation - parentId=1:4", "1:6 !GET - PStore/data.$parents -> {\"father\":\"Homer\",\"mother\":\"Marge\"}", "1:7 !GET - PStore/data.name -> 'BART'", "1:8 !PCE - 1:5", "1:9 !PCE - 1:4", "2:1 !LOG - B", "2:2 !GET - PStore/data.$parents -> {\"father\":\"Homer\",\"mother\":\"Marge\"}", "3:1 !LOG - C", // no re-processing "3:2 !SET - PStore/data.name = 'Bart' (prev: 'BART')", "3:3 !DRT - PStore#Render <- PStore/data.name", "3:4 !PCS - !Reconciliation #2 - 1 processor", "3:5 !PCS - !Compute #3 (PStore#Render) P1 Reconciliation - parentId=3:4", "3:6 !GET - PStore/data.$parents -> {\"father\":\"HOMER\",\"mother\":\"Marge\"}", "3:7 !GET - PStore/data.name -> 'Bart'", "3:8 !PCE - 3:5", "3:9 !PCE - 3:4", ]); }); it('should be supported for arrays (level 1)', async () => { const ps = trax.createStore<Person1>("PStore", { name: "Bart", $family: [ { name: "Homer" }, { name: "Marge" }, ] }); const p = ps.data; let output = ""; ps.compute("Render", () => { const f = p.$family; output = `${p.name} / ${f.map(p => p.name).join(" & ")}`; }); expect(output).toBe("Bart / Homer & Marge"); await trax.reconciliation(); trax.log.info("A"); p.name = "BART"; await trax.reconciliation(); expect(output).toBe("BART / Homer & Marge"); await trax.reconciliation(); trax.log.info("B"); p.$family.push({ name: "Lisa" }); // no changes expected await trax.reconciliation(); expect(output).toBe("BART / Homer & Marge"); // no changes trax.log.info("C"); p.name = "Bart"; await trax.reconciliation(); expect(output).toBe("Bart / Homer & Marge & Lisa"); // reprocessed through Bart change expect(printLogs(1)).toMatchObject([ "1:1 !LOG - A", "1:2 !SET - PStore/data.name = 'BART' (prev: 'Bart')", "1:3 !DRT - PStore#Render <- PStore/data.name", "1:4 !PCS - !Reconciliation #1 - 1 processor", "1:5 !PCS - !Compute #2 (PStore#Render) P1 Reconciliation - parentId=1:4", "1:6 !GET - PStore/data.$family -> [{\"name\":\"Homer\"},{\"name\":\"Marge\"}]", "1:7 !GET - PStore/data.name -> 'BART'", "1:8 !PCE - 1:5", "1:9 !PCE - 1:4", "2:1 !LOG - B", "2:2 !GET - PStore/data.$family -> [{\"name\":\"Homer\"},{\"name\":\"Marge\"}]", "3:1 !LOG - C", // no changes "3:2 !SET - PStore/data.name = 'Bart' (prev: 'BART')", "3:3 !DRT - PStore#Render <- PStore/data.name", // change "3:4 !PCS - !Reconciliation #2 - 1 processor", "3:5 !PCS - !Compute #3 (PStore#Render) P1 Reconciliation - parentId=3:4", "3:6 !GET - PStore/data.$family -> [{\"name\":\"Homer\"},{\"name\":\"Marge\"},{\"name\":\"Lisa\"}]", "3:7 !GET - PStore/data.name -> 'Bart'", "3:8 !PCE - 3:5", "3:9 !PCE - 3:4", ]); }); it('should be supported for arrays (level 2 - array)', async () => { const ps = trax.createStore<Person21>("PStore", { name: "Bart", $$family: [ { name: "Homer" }, { name: "Marge" }, ] }); const p = ps.data; let output = ""; ps.compute("Render", () => { const f = p.$$family; output = `${p.name} / ${f.map(p => p.name).join(" & ")}`; }); expect(output).toBe("Bart / Homer & Marge"); await trax.reconciliation(); trax.log.info("A"); p.$$family.push({ name: "Lisa" }); // expect change await trax.reconciliation(); expect(output).toBe("Bart / Homer & Marge & Lisa"); trax.log.info("B"); p.$$family[2].name = "LISA"; // no changes in this case await trax.reconciliation(); expect(output).toBe("Bart / Homer & Marge & Lisa"); trax.log.info("C"); p.$$family.splice(0, 1, { name: "Maggie" }); // change await trax.reconciliation(); expect(output).toBe("Bart / Maggie & Marge & LISA"); }); it('should be supported for arrays (level 2 - array in array)', async () => { const ps = trax.createStore<Person22>("PStore", { name: "Bart", $$family: [ // first level is wrapped, but not the 2nd level [{ name: "Homer" }, { name: "Marge" }], ] }); const p = ps.data; let output = ""; ps.compute("Render", () => { const f = p.$$family; output = `${p.name} / ${f.map(a => "[" + a.map(p => p.name).join("+") + "]").join(" & ")}`; }); expect(output).toBe("Bart / [Homer+Marge]"); await trax.reconciliation(); trax.log.info("A"); p.name = "BART"; // change expected await trax.reconciliation(); expect(output).toBe("BART / [Homer+Marge]"); trax.log.info("B"); p.$$family.push([{ name: "Lisa" }]); // level1: change expected await trax.reconciliation(); expect(output).toBe("BART / [Homer+Marge] & [Lisa]"); trax.log.info("C"); p.$$family[1].push({ name: "Maggie" }); // level2 : no changes expected await trax.reconciliation(); expect(output).toBe("BART / [Homer+Marge] & [Lisa]"); trax.log.info("D"); p.$$family[1][0].name = "LISA"; // level2 : no changes expected await trax.reconciliation(); expect(output).toBe("BART / [Homer+Marge] & [Lisa]"); trax.log.info("E"); p.name = "Bart"; // change expected await trax.reconciliation(); expect(output).toBe("Bart / [Homer+Marge] & [LISA+Maggie]"); }); it('should be supported for arrays (level 3 - array in array)', async () => { const ps = trax.createStore<Person3>("PStore", { name: "Bart", $$$family: [ // first level is wrapped, but not the 2nd level [{ name: "Homer" }, { name: "Marge" }], ] }); const p = ps.data; let output = ""; ps.compute("Render", () => { const f = p.$$$family; output = `${p.name} / ${f.map(a => "[" + a.map(p => p.name).join("+") + "]").join(" & ")}`; }); expect(output).toBe("Bart / [Homer+Marge]"); await trax.reconciliation(); trax.log.info("A"); p.name = "BART"; // change expected await trax.reconciliation(); expect(output).toBe("BART / [Homer+Marge]"); trax.log.info("B"); p.$$$family.push([{ name: "Lisa" }]); // level1: change expected await trax.reconciliation(); expect(output).toBe("BART / [Homer+Marge] & [Lisa]"); trax.log.info("C"); p.$$$family[1].push({ name: "Maggie" }); // level2 : change expected await trax.reconciliation(); expect(output).toBe("BART / [Homer+Marge] & [Lisa+Maggie]"); trax.log.info("D"); p.$$$family[1][0].name = "LISA"; // level3 : no changes expected await trax.reconciliation(); expect(output).toBe("BART / [Homer+Marge] & [Lisa+Maggie]"); trax.log.info("E"); p.name = "Bart"; // change expected await trax.reconciliation(); expect(output).toBe("Bart / [Homer+Marge] & [LISA+Maggie]"); }); // error if $$ prefix used outside of arrays? });