import { describe, it, expect } from "vitest"; import { projectHealth, healthBadge, healthLabel, projectPort, sortProjects, projectStats, statsArePending, quickStats, countErrors, activityLabel, recentActivity, } from "../home"; import { manifestView } from "../manifest"; import type { Manifest } from "../manifest"; import type { DiscoveredProject } from "../../composables/useDiscovery"; import type { TelemetryRecord } from "../telemetry"; import type { ProjectSnapshot } from "../../lib/project-catalog"; function snapshot(over: Partial = {}): ProjectSnapshot { return { cwd: "/repo/app", name: "app", lastVisited: "2026-01-01T00:00:00.000Z", ...over, }; } function project(over: Partial = {}): DiscoveredProject { return { cwd: over.snapshot?.cwd ?? over.cwd ?? "/repo/app", snapshot: snapshot(over.snapshot), status: over.status, active: over.active ?? false, ...over, }; } describe("projectHealth", () => { it("is running when a live process is registered", () => { expect(projectHealth(project({ status: { processes: [{ port: 4000 }] } }))).toBe("running"); }); it("is running when the legacy `running` flag is set", () => { expect(projectHealth(project({ status: { running: true } }))).toBe("running"); }); it("is on-disk when a manifest exists but no process is up", () => { expect(projectHealth(project({ status: { hasManifest: true, processes: [] } }))).toBe( "on-disk", ); }); it("is unreachable with neither a process nor a manifest", () => { expect(projectHealth(project({ status: { hasManifest: false, processes: [] } }))).toBe( "unreachable", ); expect(projectHealth(project({ status: undefined }))).toBe("unreachable"); }); }); describe("health presentation", () => { it("maps health to a badge token", () => { expect(healthBadge("running")).toBe("live"); expect(healthBadge("on-disk")).toBe("idle"); expect(healthBadge("unreachable")).toBe("error"); }); it("labels each health", () => { expect(healthLabel("running")).toBe("Running"); expect(healthLabel("on-disk")).toBe("On disk"); expect(healthLabel("unreachable")).toBe("Unreachable"); }); }); describe("projectPort", () => { it("returns the first process port", () => { expect(projectPort(project({ status: { processes: [{}, { port: 4111 }] } }))).toBe(4111); }); it("is undefined when no port is known", () => { expect(projectPort(project({ status: { processes: [] } }))).toBeUndefined(); }); }); describe("sortProjects", () => { it("orders active first, then by health, then by name", () => { const running = project({ snapshot: snapshot({ cwd: "/r", name: "running" }), status: { processes: [{}] }, }); const onDisk = project({ snapshot: snapshot({ cwd: "/d", name: "ondisk" }), status: { hasManifest: true }, }); const dead = project({ snapshot: snapshot({ cwd: "/x", name: "dead" }), status: {} }); const active = project({ snapshot: snapshot({ cwd: "/a", name: "zeta-active" }), active: true, status: {}, }); const out = sortProjects([dead, onDisk, running, active]); expect(out.map((p) => p.snapshot.name)).toEqual(["zeta-active", "running", "ondisk", "dead"]); }); it("does not mutate the input", () => { const list = [ project({ snapshot: snapshot({ name: "b" }) }), project({ snapshot: snapshot({ name: "a" }) }), ]; const copy = [...list]; sortProjects(list); expect(list).toEqual(copy); }); }); describe("projectStats", () => { it("reads composition counts from the snapshot", () => { const p = project({ snapshot: snapshot({ composition: { apps: 1, plugins: 2, actions: 3, events: 4, resolvers: 5, workflows: 6 }, }), }); expect(projectStats(p)).toEqual({ apps: 1, plugins: 2, actions: 3, events: 4, resolvers: 5, workflows: 6, }); }); it("falls back to zeros when no composition is recorded", () => { expect(projectStats(project())).toEqual({ apps: 0, plugins: 0, actions: 0, events: 0, resolvers: 0, workflows: 0, }); }); it("flags a pending (not-yet-scanned) snapshot", () => { expect(statsArePending(project())).toBe(true); expect( statsArePending( project({ snapshot: snapshot({ composition: { apps: 0, plugins: 0, actions: 0, events: 0 } }), }), ), ).toBe(false); }); }); function manifest(): Manifest { return { version: 3, model: { nodes: [ { id: "action:a", kind: "action", name: "a" }, { id: "action:b", kind: "action", name: "b" }, { id: "event:E", kind: "event", name: "E" }, { id: "projection:P", kind: "projection", name: "P" }, { id: "app:app", kind: "app", name: "app" }, ], edges: [], }, } as unknown as Manifest; } describe("quickStats", () => { it("derives KPI counts from the manifest view + error count", () => { const stats = quickStats(manifestView(manifest()), 7); const byLabel = Object.fromEntries(stats.map((s) => [s.label, s.value])); expect(byLabel.Nodes).toBe(5); expect(byLabel.Actions).toBe(2); expect(byLabel.Events).toBe(1); expect(byLabel.Projections).toBe(1); expect(byLabel.Errors).toBe(7); }); it("reports zeros for a null view (manifest not loaded)", () => { const stats = quickStats(null, 0); expect(stats.every((s) => s.value === 0)).toBe(true); }); }); describe("countErrors", () => { it("counts failure records", () => { const recs: TelemetryRecord[] = [ { kind: "action.dispatched" }, { kind: "action.failed" }, { kind: "event.published", error: { name: "X", message: "boom" } }, ]; expect(countErrors(recs)).toBe(2); }); }); describe("activityLabel", () => { it("prefers the named subject", () => { expect(activityLabel({ kind: "action.dispatched", action: "orders.place" })).toBe( "orders.place", ); expect(activityLabel({ kind: "event.published", event: "OrderPlaced" })).toBe("OrderPlaced"); }); it("falls back to the kind", () => { expect(activityLabel({ kind: "timer.fired" })).toBe("timer.fired"); }); }); describe("recentActivity", () => { const recs: TelemetryRecord[] = [ { kind: "action.dispatched", action: "a", ts: "1", envelope: { messageId: "m1", correlationId: "c1", causationId: "m1" }, }, { kind: "action.failed", action: "b", ts: "2" }, { kind: "event.published", event: "E", ts: "3" }, ]; it("returns newest-first, capped at the limit", () => { const items = recentActivity(recs, 2); expect(items.map((i) => i.label)).toEqual(["E", "b"]); }); it("marks failures and carries the correlation id + colour family", () => { const items = recentActivity(recs, 3); const failed = items.find((i) => i.label === "b"); const dispatched = items.find((i) => i.label === "a"); expect(failed?.failed).toBe(true); expect(dispatched?.failed).toBe(false); expect(dispatched?.correlationId).toBe("c1"); expect(dispatched?.colorKey).toBe("action"); }); });