summaryrefslogtreecommitdiffhomepage
path: root/src/features/plugins/logic/view-model.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/plugins/logic/view-model.test.ts')
-rw-r--r--src/features/plugins/logic/view-model.test.ts71
1 files changed, 70 insertions, 1 deletions
diff --git a/src/features/plugins/logic/view-model.test.ts b/src/features/plugins/logic/view-model.test.ts
index 680ba94..661cade 100644
--- a/src/features/plugins/logic/view-model.test.ts
+++ b/src/features/plugins/logic/view-model.test.ts
@@ -1,7 +1,7 @@
import type { PluginListResponse, PluginTier } from "@dispatch/transport-contract";
import { describe, expect, it } from "vitest";
import { emptyListResponse, node } from "./test-fixtures";
-import { groupByTier, pluginNodes, splitSections } from "./view-model";
+import { groupByTier, pluginNodes, splitSections, withFrontendStates } from "./view-model";
function listResponse(overrides: Partial<PluginListResponse> = {}): PluginListResponse {
return {
@@ -225,3 +225,72 @@ describe("splitSections", () => {
expect(sections.missing.map((n) => n.id)).toEqual(["gone"]);
});
});
+
+// ── withFrontendStates (plan 03) — the manager rows' FE-half issues ─────────
+
+describe("withFrontendStates", () => {
+ function listWith(...ids: readonly string[]): PluginListResponse {
+ return {
+ loaded: ids.map((id) => ({
+ id,
+ name: id,
+ version: "1.0.0",
+ tier: "standard" as const,
+ trust: "local" as const,
+ locked: false,
+ dependsOn: [],
+ })),
+ disabledWithReason: [],
+ discovered: [],
+ missing: [],
+ };
+ }
+
+ it("renders the plan-mandated grammar for an incompatible FE half", () => {
+ const nodes = pluginNodes(listWith("bad"));
+ const states = new Map([["bad", { kind: "incompatible", apiVersion: "^2" } as const]]);
+ const out = withFrontendStates(nodes, states);
+ expect(out[0]?.frontendIssue).toBe("incompatible frontend plugin");
+ });
+
+ it("renders the activation error for a failed FE half", () => {
+ const nodes = pluginNodes(listWith("broken"));
+ const states = new Map([["broken", { kind: "failed", error: "boom" } as const]]);
+ const out = withFrontendStates(nodes, states);
+ expect(out[0]?.frontendIssue).toBe("frontend failed: boom");
+ });
+
+ it("a loaded FE half adds no issue line; absent states add none", () => {
+ const nodes = pluginNodes(listWith("ok", "no-fe"));
+ const states = new Map([["ok", { kind: "loaded" } as const]]);
+ const out = withFrontendStates(nodes, states);
+ expect(out.find((n) => n.id === "ok")?.frontendIssue).toBeUndefined();
+ expect(out.find((n) => n.id === "no-fe")?.frontendIssue).toBeUndefined();
+ });
+
+ it("never annotates non-loaded sections (discovered / missing rows)", () => {
+ const response: PluginListResponse = {
+ loaded: [],
+ disabledWithReason: [],
+ discovered: [
+ {
+ id: "dis",
+ name: "dis",
+ version: "1.0.0",
+ tier: "standard",
+ trust: "local",
+ dir: "/plugins/dis",
+ },
+ ],
+ missing: [],
+ };
+ const states = new Map([["dis", { kind: "failed", error: "boom" } as const]]);
+ const out = withFrontendStates(pluginNodes(response), states);
+ expect(out[0]?.frontendIssue).toBeUndefined();
+ });
+
+ it("an empty state map is the identity (no allocation on the hot path)", () => {
+ const nodes = pluginNodes(listWith("a"));
+ expect(withFrontendStates(nodes, new Map())).toBe(nodes);
+ });
+});