combineStates 100% covfefe

This commit is contained in:
Víctor Oliva 2023-06-28 23:56:30 +02:00
parent c9abc0bb6a
commit 8a3c5cb227
3 changed files with 189 additions and 6 deletions

View File

@ -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,
})
})
})

View File

@ -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,
),
)
}

View File

@ -0,0 +1,28 @@
import { of } from "rxjs"
import { InternalStateNode, createStateNode, getInternals } from "../internal"
import { KeysBaseType, StateNode } from "../types"
export function instanceNode<T, K extends KeysBaseType, KN extends string>(
parent: StateNode<T, K>,
keyName: KN,
) {
type MergedKey = {
[key in keyof (K & Record<KN, any>)]: (K & Record<KN, any>)[key]
}
const parentInternals = getInternals(parent)
const result: InternalStateNode<any, MergedKey> = 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)
},
})
}