From 8a3c5cb227d35e18491e52747fd91c28497de4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Oliva?= Date: Wed, 28 Jun 2023 23:56:30 +0200 Subject: [PATCH] combineStates 100% covfefe --- .../context-state2/src/combineStates.test.ts | 156 ++++++++++++++++++ packages/context-state2/src/combineStates.ts | 11 +- .../src/test-utils/instance-node.ts | 28 ++++ 3 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 packages/context-state2/src/test-utils/instance-node.ts diff --git a/packages/context-state2/src/combineStates.test.ts b/packages/context-state2/src/combineStates.test.ts index 4b262c8..d9962f6 100644 --- a/packages/context-state2/src/combineStates.test.ts +++ b/packages/context-state2/src/combineStates.test.ts @@ -4,6 +4,7 @@ import { substate } from "./substate" import { combineStates } from "./combineStates" import { StateNode } from "./types" import { routeState } from "./route-state" +import { instanceNode } from "./test-utils/instance-node" describe("combineStates", () => { it("combines state nodes into one", () => { @@ -125,4 +126,159 @@ describe("combineStates", () => { expect(combined.getValue({ root: "" })).toEqual({ nodeA: "a2", nodeB: "b" }) }) + + it("combines states with keys of different lengths", () => { + const root = createRoot() + /** I want 3 nodes with key length 1,2,3 and pass it down in the order 1,3,2 + * And to make it more realistic, combine nodes that don't have a context relationship + * sA1 + * r-iA1< sB2 + * iB2< + * iC3 + * -> Combine [sA1,iC3,sB2] + */ + const iA1 = instanceNode(root, "a") + const iB2 = instanceNode(iA1, "b") + const iC3 = instanceNode(iB2, "c") + + const sA1 = substate(iA1, (_ctx, _obs, key) => of("sA" + key.a)) + const sB2 = substate(iB2, (_ctx, _obs, key) => of("sB" + key.b)) + + const combined = combineStates({ a: sA1, c: iC3, b: sB2 }) + root.run() + + iA1.addInstance({ + a: 1, + }) + iB2.addInstance({ + a: 1, + b: 1, + }) + iC3.addInstance({ + a: 1, + b: 1, + c: 1, + }) + + // Adding this one but it should not exist on the combined state + iB2.addInstance({ + a: 1, + b: 2, + }) + + iA1.addInstance({ + a: 2, + }) + iB2.addInstance({ + a: 2, + b: 1, + }) + iC3.addInstance({ + a: 2, + b: 1, + c: 1, + }) + iC3.addInstance({ + a: 2, + b: 1, + c: 3, + }) + + expect(combined.getValue({ a: 1, b: 1, c: 1 })).toEqual({ + a: "sA1", + b: "sB1", + c: 1, + }) + expect(combined.getValue({ a: 2, b: 1, c: 1 })).toEqual({ + a: "sA2", + b: "sB1", + c: 1, + }) + expect(combined.getValue({ a: 2, b: 1, c: 3 })).toEqual({ + a: "sA2", + b: "sB1", + c: 3, + }) + expect(() => combined.getValue({ a: 1, b: 2, c: 1 })).toThrow() + expect(() => combined.getValue({ a: 2, b: 2, c: 1 })).toThrow() + expect(() => combined.getValue({ a: 2, b: 2, c: 3 })).toThrow() + }) + + it("removes instances when they are removed", () => { + const root = createRoot() + const iA1 = instanceNode(root, "a") + const iB2 = instanceNode(iA1, "b") + + const combined = combineStates({ a: iA1, b: iB2 }) + root.run() + + iA1.addInstance({ + a: 1, + }) + iB2.addInstance({ + a: 1, + b: 1, + }) + iB2.addInstance({ + a: 1, + b: 2, + }) + + iA1.addInstance({ + a: 2, + }) + iB2.addInstance({ + a: 2, + b: 1, + }) + iB2.addInstance({ + a: 2, + b: 2, + }) + + expect(combined.getValue({ a: 1, b: 1 })).toEqual({ + a: 1, + b: 1, + }) + expect(combined.getValue({ a: 1, b: 2 })).toEqual({ + a: 1, + b: 2, + }) + + iA1.removeInstance({ + a: 1, + }) + + expect(() => combined.getValue({ a: 1, b: 1 })).toThrow() + expect(() => combined.getValue({ a: 1, b: 2 })).toThrow() + expect(combined.getValue({ a: 2, b: 1 })).toEqual({ + a: 2, + b: 1, + }) + expect(combined.getValue({ a: 2, b: 2 })).toEqual({ + a: 2, + b: 2, + }) + }) + + it("combines states when it's declared after having instances already in running", () => { + const root = createRoot() + const iA1 = instanceNode(root, "a") + const iB2 = instanceNode(iA1, "b") + root.run() + + iA1.addInstance({ + a: 1, + }) + iB2.addInstance({ + a: 1, + b: 1, + }) + const combined = combineStates({ a: iA1, b: iB2 }) + + expect(combined.getValue({ a: 1, b: 1 })).toEqual({ + a: 1, + b: 1, + }) + }) }) diff --git a/packages/context-state2/src/combineStates.ts b/packages/context-state2/src/combineStates.ts index d3c9425..2013890 100644 --- a/packages/context-state2/src/combineStates.ts +++ b/packages/context-state2/src/combineStates.ts @@ -50,9 +50,8 @@ export const combineStates = < ), ) ) { - const key = keysOrder.map((k) => instance.key[k]) - result.addInstance(key) - toActivate.push(key) + result.addInstance(instance.key) + toActivate.push(instance.key) } } for (let key of toActivate) { @@ -65,7 +64,7 @@ export const combineStates = < keysToRemove.push(instance.key) } for (let key of keysToRemove) { - result.removeInstance(keysOrder.map((k) => key[k])) + result.removeInstance(key) } } @@ -85,7 +84,7 @@ export const combineStates = < ) addInstances( keysOrder.map((k) => - k in node.keysOrder ? change.key[k] : Wildcard, + node.keysOrder.includes(k) ? change.key[k] : Wildcard, ), ) } else if (change.type === "removed") { @@ -95,7 +94,7 @@ export const combineStates = < ) removeInstances( keysOrder.map((k) => - k in node.keysOrder ? change.key[k] : Wildcard, + node.keysOrder.includes(k) ? change.key[k] : Wildcard, ), ) } diff --git a/packages/context-state2/src/test-utils/instance-node.ts b/packages/context-state2/src/test-utils/instance-node.ts new file mode 100644 index 0000000..746c824 --- /dev/null +++ b/packages/context-state2/src/test-utils/instance-node.ts @@ -0,0 +1,28 @@ +import { of } from "rxjs" +import { InternalStateNode, createStateNode, getInternals } from "../internal" +import { KeysBaseType, StateNode } from "../types" + +export function instanceNode( + parent: StateNode, + keyName: KN, +) { + type MergedKey = { + [key in keyof (K & Record)]: (K & Record)[key] + } + const parentInternals = getInternals(parent) + const result: InternalStateNode = createStateNode( + [...parentInternals.keysOrder, keyName], + [parentInternals], + (_ctx, _obs, key) => of(key[keyName]), + ) + + return Object.assign(result.public, { + addInstance(key: MergedKey) { + result.addInstance(key) + result.activateInstance(key) + }, + removeInstance(key: MergedKey) { + result.removeInstance(key) + }, + }) +}