import { describe, it, expect } from "vitest"; import { mount } from "@vue/test-utils"; import NodeCard from "../NodeCard.vue"; import type { BcNodeDetail } from "@/lib/bc-graph"; const detail: BcNodeDetail = { id: "action:orders.place", kind: "action", name: "orders.place", app: "orders", description: "Place an order.", public: true, source: { file: "/app/actions.ts", line: 12, column: 4 }, schema: { type: "object", properties: { id: { type: "string" } } }, emits: ["orders.placed"], extra: { retry: 3 }, }; describe("NodeCard", () => { it("renders the empty state with no detail", () => { const w = mount(NodeCard, { props: { detail: null } }); expect(w.find('[data-testid="node-card-empty"]').exists()).toBe(true); expect(w.find('[data-testid="node-card"]').exists()).toBe(false); }); it("renders kind, name, app and description", () => { const w = mount(NodeCard, { props: { detail } }); expect(w.text()).toContain("action"); expect(w.text()).toContain("orders.place"); expect(w.text()).toContain("orders"); expect(w.text()).toContain("Place an order."); }); it("shows the public marker for public nodes", () => { const w = mount(NodeCard, { props: { detail } }); expect(w.find('[data-testid="node-public"]').exists()).toBe(true); const priv = mount(NodeCard, { props: { detail: { ...detail, public: false } } }); expect(priv.find('[data-testid="node-private"]').exists()).toBe(true); }); it("emits select-event when an emitted event chip is clicked", async () => { const w = mount(NodeCard, { props: { detail } }); await w.get('[data-testid="emits-orders.placed"]').trigger("click"); expect(w.emitted("select-event")?.[0]).toEqual(["orders.placed"]); }); it("emits open-source when the source pill is clicked", async () => { const w = mount(NodeCard, { props: { detail } }); await w.get('[data-testid="source-pill"]').trigger("click"); expect(w.emitted("open-source")?.[0]?.[0]).toMatchObject({ line: 12 }); }); it("renders a Business rules section listing each invariant message + rule", () => { const withRules: BcNodeDetail = { ...detail, invariants: [ { rule: '(i) => i.by.length > 0 || "Must name the moderator"', message: "Must name the moderator", }, { rule: "(i) => i.ok" }, ], }; const w = mount(NodeCard, { props: { detail: withRules } }); const panel = w.find('[data-testid="business-rules"]'); expect(panel.exists()).toBe(true); expect(panel.text()).toContain("Business rules"); expect(panel.text()).toContain("Must name the moderator"); expect(panel.text()).toContain('(i) => i.by.length > 0 || "Must name the moderator"'); // A rule with no message still shows its source plus the placeholder. expect(panel.text()).toContain("(i) => i.ok"); expect(panel.text()).toContain("no stated message"); }); it("omits the Business rules section when there are no invariants", () => { const w = mount(NodeCard, { props: { detail } }); expect(w.find('[data-testid="business-rules"]').exists()).toBe(false); }); const appDetail: BcNodeDetail = { id: "app:moderation", kind: "app", name: "moderation", description: "Moderation BC.", env: ["MODERATION_AUTO_THRESHOLD", "PORT"], config: [{ file: "/abs/app/config/moderation.ts", keys: ["moderation", "spamMarker"] }], extra: { plugins: ["forge.actions"] }, }; it("renders an Environment section listing each env var the app reads", () => { const w = mount(NodeCard, { props: { detail: appDetail } }); const panel = w.find('[data-testid="environment"]'); expect(panel.exists()).toBe(true); expect(panel.text()).toContain("Environment"); expect(w.find('[data-testid="env-PORT"]').exists()).toBe(true); expect(w.find('[data-testid="env-MODERATION_AUTO_THRESHOLD"]').exists()).toBe(true); }); it("renders a Config section with each module's file (config-relative) + keys", () => { const w = mount(NodeCard, { props: { detail: appDetail } }); const panel = w.find('[data-testid="config"]'); expect(panel.exists()).toBe(true); expect(panel.text()).toContain("Config"); // Path is shown from the config/ segment, not the absolute prefix. expect(panel.text()).toContain("config/moderation.ts"); expect(panel.text()).not.toContain("/abs/app"); expect(panel.text()).toContain("moderation"); expect(panel.text()).toContain("spamMarker"); }); it("omits Environment + Config sections when absent", () => { const w = mount(NodeCard, { props: { detail } }); expect(w.find('[data-testid="environment"]').exists()).toBe(false); expect(w.find('[data-testid="config"]').exists()).toBe(false); }); });