diff --git a/packages/context-state2/src/create-signal.test.ts b/packages/context-state2/src/create-signal.test.ts index d48e837..a71638d 100644 --- a/packages/context-state2/src/create-signal.test.ts +++ b/packages/context-state2/src/create-signal.test.ts @@ -80,9 +80,32 @@ describe("createSignal", () => { expect(() => signal.getSignal$({ gameId: "b" })).toThrowError( "Inactive Context", ) + expect(() => signal.push({ gameId: "b" }, 3)).toThrowError( + "Inactive Context", + ) stop() expect(() => signal.getSignal$({ gameId: "a" })).toThrowError( "Inactive Context", ) + expect(() => signal.push({ gameId: "a" }, 3)).toThrowError( + "Inactive Context", + ) + }) + + it("activates even if it's declared after the parent was already active", () => { + const root = createRoot() + + root.run() + + const signal = createSignal(root) + const next = jest.fn() + signal.getSignal$().subscribe(next) + expect(next).not.toBeCalled() + + signal.push(4) + signal.push(5) + expect(next).toBeCalledTimes(2) + expect(next).toBeCalledWith(4) + expect(next).toBeCalledWith(5) }) }) diff --git a/packages/context-state2/src/internal/state-node.ts b/packages/context-state2/src/internal/state-node.ts index e0f7b3c..1025470 100644 --- a/packages/context-state2/src/internal/state-node.ts +++ b/packages/context-state2/src/internal/state-node.ts @@ -164,8 +164,8 @@ export function createStateNode( getInstance(key).activate() } const removeInstance = (key: K) => { - // TODO already deleted - const instance = instances.get(nestedMapKey(key))! + const instance = instances.get(nestedMapKey(key)) + if (!instance) return instance.kill() instances.delete(nestedMapKey(key)) instanceChange$.next({ diff --git a/packages/context-state2/src/route-state.test.ts b/packages/context-state2/src/route-state.test.ts index edb0862..c84d61a 100644 --- a/packages/context-state2/src/route-state.test.ts +++ b/packages/context-state2/src/route-state.test.ts @@ -124,6 +124,25 @@ describe("routeState", () => { expect(() => routes.b.getValue()).toThrowError("Inactive Context") }) + it("activates the route even if it's declared after the parent was running", () => { + const root = createRoot() + const parentSource = new Subject<"a" | "b">() + const parent = substate(root, () => parentSource) + root.run() + parentSource.next("a") + + const [, routes] = routeState( + parent, + { + a: null, + b: null, + }, + (v) => v, + ) + + expect(routes.a.getValue()).toEqual("a") + }) + it("deactivates the previous route before activating the new one", () => { const root = createRoot() const parentSource = new Subject<"a" | "b">() @@ -186,5 +205,28 @@ describe("routeState", () => { expect(a.getValue()).toEqual("a mapped a") }) + + it("deactivates the nodes when the parent is deactivated", () => { + const root = createRoot() + const parentSource = new Subject<"a" | "b">() + const parent = substate(root, () => parentSource) + // Now I'd like to have routes first, then the key on the second place :'D + const [, routes] = routeState( + parent, + { + a: null, + b: null, + }, + (v) => v, + ) + const stahp = root.run() + + parentSource.next("a") + expect(routes.a.getValue()).toEqual("a") + + stahp() + + expect(() => routes.a.getValue()).toThrowError("Inactive Context") + }) }) }) diff --git a/packages/context-state2/src/route-state.ts b/packages/context-state2/src/route-state.ts index f06e35b..f2b3930 100644 --- a/packages/context-state2/src/route-state.ts +++ b/packages/context-state2/src/route-state.ts @@ -69,6 +69,9 @@ export const routeState = < const key = internalParent.keysOrder.map((k) => instanceKey[k]) const sub = subscriptions.get(key) subscriptions.delete(key) + Object.values(routedState).forEach((node) => { + node.removeInstance(instanceKey) + }) sub?.unsubscribe() } diff --git a/packages/context-state2/src/substate.test.ts b/packages/context-state2/src/substate.test.ts index 3772b6e..a1ad2df 100644 --- a/packages/context-state2/src/substate.test.ts +++ b/packages/context-state2/src/substate.test.ts @@ -322,6 +322,25 @@ describe("subState", () => { expect(emissions).toEqual([1, 2]) }) + it("can be declared on a node already running", () => { + const root = createRoot() + const source$ = new Subject() + root.run() + const subNode = substate(root, () => source$) + + const emissions: number[] = [] + subNode.getState$({ root: "" }).subscribe({ + next: (v) => emissions.push(v), + }) + expect(emissions).toEqual([]) + + source$.next(1) + expect(emissions).toEqual([1]) + + source$.next(2) + expect(emissions).toEqual([1, 2]) + }) + it("replays the latest value on late subscription", () => { const root = createRoot() const source$ = new Subject()