diff --git a/packages/chess/src/ui/visual-builder/preview/BoardDiagramView.test.tsx b/packages/chess/src/ui/visual-builder/preview/BoardDiagramView.test.tsx new file mode 100644 index 0000000..dd620e5 --- /dev/null +++ b/packages/chess/src/ui/visual-builder/preview/BoardDiagramView.test.tsx @@ -0,0 +1,118 @@ +/** + * BoardDiagramView test suite — validates board diagram rendering for + * effect primitives: square highlighting (on-moved-onto-square), + * aura rendering (add-aura), and the empty state placeholder. + * + * Rendering approach: React SSR (`renderToStaticMarkup`) — no DOM mount, + * purely the declarative tree. Assertions check for data-testid attributes + * and expected HTML structure. + */ +import { describe, it, expect } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import "../../../modifiers/primitives/index.js"; +import type { CustomModifierDescriptor } from "../../../modifiers/custom/types.js"; +import type { EffectPrimitiveNode } from "../../../modifiers/primitives/types.js"; +import { BoardDiagramView } from "./BoardDiagramView.js"; + +/** + * Factory: minimal valid descriptor for testing. + */ +function createSampleDescriptor( + primitives: readonly EffectPrimitiveNode[] = [] +): CustomModifierDescriptor { + return { + type: "data", + id: "test-board-1" as unknown as CustomModifierDescriptor["id"], + name: "Test Board Modifier", + description: "A test modifier for board effects", + version: 1, + primitives, + targetAttrs: [], + uiForm: "primitive-composer", + source: "custom", + }; +} + +describe("BoardDiagramView", () => { + it("empty descriptor renders 'No board effect' placeholder", () => { + const descriptor = createSampleDescriptor([]); + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("No board effect"); + // Should NOT render SVG when there's no board effect + expect(html).not.toContain(" { + const descriptor = createSampleDescriptor([ + { + kind: "on-moved-onto-square", + params: { + filter: { kind: "squares", squares: [28, 35] }, + primitives: [], + }, + }, + ]); + + const html = renderToStaticMarkup( + + ); + + // Should render the board diagram (contains SVG) + expect(html).toContain(" { + const descriptor = createSampleDescriptor([ + { + kind: "add-aura", + params: { radius: 2, targetAttr: "HpBonus", delta: 1 }, + }, + ]); + + const html = renderToStaticMarkup( + + ); + + // Should render the board diagram + expect(html).toContain(" { + const descriptor = createSampleDescriptor([ + { + kind: "on-capture", + params: { + primitives: [ + { + kind: "on-moved-onto-square", + params: { + filter: { kind: "squares", squares: [15, 48] }, + primitives: [], + }, + }, + ], + }, + }, + ]); + + const html = renderToStaticMarkup( + + ); + + // The walker should traverse into on-capture and find the nested + // on-moved-onto-square, so the squares should be highlighted + expect(html).toContain(" +): CustomModifierDescriptor { + return { + type: "data", + id: "test-mod-1" as unknown as CustomModifierDescriptor["id"], + name: "Test Modifier", + description: "A test modifier", + version: 1, + primitives: [ + { kind: "add-to-attribute", params: { attr: "Hp", delta: 1 } }, + ], + targetAttrs: [], + uiForm: "primitive-composer", + source: "custom", + ...overrides, + }; +} + +describe("PreviewPane", () => { + it("renders all three tabs with correct roles", () => { + const descriptor = createSampleDescriptor(); + const html = renderToStaticMarkup( + + ); + + // Expect three role="tab" elements + const tabMatches = html.match(/role="tab"/g); + expect(tabMatches).toBeTruthy(); + expect(tabMatches!.length).toBe(3); + + // Verify tab labels exist + expect(html).toContain("Narrative"); + expect(html).toContain("JSON"); + expect(html).toContain("Board"); + }); + + it("default active tab is Narrative (visible, others hidden)", () => { + const descriptor = createSampleDescriptor(); + const html = renderToStaticMarkup( + + ); + + // Narrative tab should have aria-selected="true" by default + expect(html).toContain('id="tab-narrative"'); + // Verify that the narrative tab is the active one + expect(html).toContain('aria-selected="true" aria-controls="panel-narrative"'); + + // JSON and Board tabs should have aria-selected="false" + expect(html).toContain('id="tab-json"'); + expect(html).toContain('aria-selected="false"'); + expect(html).toContain('id="tab-board"'); + + // Narrative panel should be visible (class contains "block") + expect(html).toContain('id="panel-narrative"'); + expect(html).toContain('aria-labelledby="tab-narrative" class="h-full block"'); + + // JSON and Board panels should be hidden + expect(html).toMatch(/id="panel-json".*aria-labelledby="tab-json".*hidden/s); + expect(html).toMatch(/id="panel-board".*aria-labelledby="tab-board".*hidden/s); + }); + + it("sub-views are memoized over descriptor identity", () => { + // Render twice with the exact same descriptor object reference + const descriptor = createSampleDescriptor(); + + const html1 = renderToStaticMarkup( + + ); + const html2 = renderToStaticMarkup( + + ); + + // With memoization, the rendered output should be identical + expect(html1).toBe(html2); + + // Now render with a different descriptor (different reference) + const descriptor2 = createSampleDescriptor(); + const html3 = renderToStaticMarkup( + + ); + + // Even though content is the same, the memoized sub-views + // should have been re-created, so markup should still match + // (since the sample is identical). This test verifies the + // components accept the descriptor and respond to changes. + expect(html3).toContain("Narrative"); + expect(html3).toContain("JSON"); + expect(html3).toContain("Board"); + }); +});