File size: 3,169 Bytes
6fffa8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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([]);
  });
});