diff options
| author | Adam Malczewski <[email protected]> | 2026-09-03 18:05:29 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-09-03 18:05:29 +0900 |
| commit | 3a4922a05930b0dc22740bf5621910803e8f275e (patch) | |
| tree | d5f831c3d3838cfff346e378f5370a9c58bc2ed3 /src/features/plugins/logic/cascade.test.ts | |
| parent | 33c7245f18346553f660434bc3ac589cff3e80c2 (diff) | |
| download | dispatch-web-3a4922a05930b0dc22740bf5621910803e8f275e.tar.gz dispatch-web-3a4922a05930b0dc22740bf5621910803e8f275e.zip | |
feat(plugin-system): plan 07 frontend plugin manager ui
Diffstat (limited to 'src/features/plugins/logic/cascade.test.ts')
| -rw-r--r-- | src/features/plugins/logic/cascade.test.ts | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/src/features/plugins/logic/cascade.test.ts b/src/features/plugins/logic/cascade.test.ts new file mode 100644 index 0000000..109c08b --- /dev/null +++ b/src/features/plugins/logic/cascade.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from "vitest"; +import { computeCascade } from "./cascade"; +import { node } from "./test-fixtures"; + +describe("computeCascade", () => { + it("returns [] when the target has no dependents", () => { + const nodes = [node({ id: "a" }), node({ id: "b" })]; + expect(computeCascade("a", nodes)).toEqual([]); + }); + + it("returns the direct loaded dependents", () => { + const nodes = [ + node({ id: "heartbeat" }), + node({ id: "transport-http", dependsOn: ["heartbeat"] }), + ]; + expect(computeCascade("heartbeat", nodes)).toEqual(["transport-http"]); + }); + + it("is transitive (dependent of dependent)", () => { + const nodes = [ + node({ id: "a" }), + node({ id: "b", dependsOn: ["a"] }), + node({ id: "c", dependsOn: ["b"] }), + ]; + expect(computeCascade("a", nodes)).toEqual(["b", "c"]); + }); + + it("does NOT traverse through an UNLOADED intermediate (loaded-subgraph closure — server mirror)", () => { + // c depends on b, b is unloaded: unloading a cascades to b only (b is not + // collected — unloaded) and does NOT reach c (its dep chain is already unmet). + const nodes = [ + node({ id: "a" }), + node({ id: "b", loaded: false, section: "discovered", dependsOn: ["a"] }), + node({ id: "c", dependsOn: ["b"] }), + ]; + expect(computeCascade("a", nodes)).toEqual([]); + }); + + it("ignores plugins that merely share a dependency (direction matters)", () => { + const nodes = [node({ id: "dep" }), node({ id: "x", dependsOn: ["dep"] })]; + expect(computeCascade("x", nodes)).toEqual([]); + }); + + it("handles diamond dependencies without duplicates", () => { + const nodes = [ + node({ id: "root" }), + node({ id: "left", dependsOn: ["root"] }), + node({ id: "right", dependsOn: ["root"] }), + node({ id: "bottom", dependsOn: ["left", "right"] }), + ]; + expect(computeCascade("root", nodes)).toEqual(["bottom", "left", "right"]); + }); + + it("returns [] for a target that is not in the list", () => { + expect(computeCascade("ghost", [node({ id: "a" })])).toEqual([]); + }); + + it("is deterministic (alphabetical order)", () => { + const nodes = [ + node({ id: "root" }), + node({ id: "zeta", dependsOn: ["root"] }), + node({ id: "alpha", dependsOn: ["root"] }), + ]; + expect(computeCascade("root", nodes)).toEqual(["alpha", "zeta"]); + }); +}); |
