| import { describe, expect, test } from "vitest"; |
| import { |
| BRANCH_X_GAP, |
| BRANCH_Y_GAP, |
| NODE_WIDTH, |
| buildCanvasEdge, |
| createCanvasNodeFromGeneration, |
| getBranchPosition, |
| normalizeStoredCanvas |
| } from "./canvas-model"; |
|
|
| const generation = { |
| id: "node_123", |
| title: "Learning AMD GPUs visually", |
| imageUrl: "http://localhost:8000/outputs/node_123.png", |
| imagePrompt: "A luminous visual research board about AMD GPUs", |
| summary: "A one-paragraph public summary grounded in the retrieved sources.", |
| query: "teach me AMD GPUs", |
| visualFacts: [ |
| { |
| label: "ROCm stack", |
| detail: "AMD's software stack for accelerated AI workloads." |
| } |
| ], |
| sources: [ |
| { |
| title: "AMD Developer Cloud", |
| url: "https://www.amd.com/en/developer-cloud.html", |
| snippet: "Cloud access to AMD GPUs." |
| } |
| ], |
| suggestedBranches: ["compare clouds", "show model options"] |
| }; |
|
|
| describe("canvas model", () => { |
| test("creates a root image node at the canvas center", () => { |
| const node = createCanvasNodeFromGeneration(generation, { x: 0, y: 0 }); |
|
|
| expect(node.id).toBe("node_123"); |
| expect(node.type).toBe("veniceImage"); |
| expect(node.position).toEqual({ x: 0, y: 0 }); |
| expect(node.data.summary).toBe("A one-paragraph public summary grounded in the retrieved sources."); |
| expect(node.data.visualFacts).toEqual([ |
| { |
| label: "ROCm stack", |
| detail: "AMD's software stack for accelerated AI workloads." |
| } |
| ]); |
| expect(node.data.sources).toHaveLength(1); |
| expect(node.data.parentId).toBeUndefined(); |
| }); |
|
|
| test("uses a readable visual-page node size instead of a thumbnail", () => { |
| expect(NODE_WIDTH).toBeGreaterThanOrEqual(700); |
| }); |
|
|
| test("places branch nodes to the right and staggered by sibling index", () => { |
| expect(getBranchPosition({ x: 40, y: 80 }, 0)).toEqual({ x: 40 + BRANCH_X_GAP, y: 80 }); |
| expect(getBranchPosition({ x: 40, y: 80 }, 2)).toEqual({ |
| x: 40 + BRANCH_X_GAP, |
| y: 80 + BRANCH_Y_GAP * 2, |
| }); |
| }); |
|
|
| test("creates an animated edge from parent to child", () => { |
| const edge = buildCanvasEdge("parent", "child"); |
|
|
| expect(edge).toMatchObject({ |
| id: "parent-child", |
| source: "parent", |
| target: "child", |
| animated: true |
| }); |
| }); |
|
|
| test("normalizes legacy stored nodes that do not have summary or visual facts", () => { |
| const legacyCanvas = { |
| nodes: [ |
| { |
| id: "legacy", |
| type: "veniceImage", |
| position: { x: 12, y: 34 }, |
| data: { |
| title: "Legacy node", |
| imageUrl: "/api/outputs/legacy.png", |
| imagePrompt: "old prompt", |
| query: "old query", |
| sources: [], |
| suggestedBranches: [] |
| } |
| } |
| ], |
| edges: [] |
| }; |
|
|
| const normalized = normalizeStoredCanvas(legacyCanvas); |
|
|
| expect(normalized.nodes[0].data.summary).toBe("Generated visual search node for old query."); |
| expect(normalized.nodes[0].data.visualFacts).toEqual([]); |
| expect(normalized.nodes[0].data.sources).toEqual([]); |
| expect(normalized.nodes[0].data.suggestedBranches).toEqual([]); |
| }); |
| }); |
|
|