@runfusion/fusion 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/bin.js +2254 -1349
  2. package/dist/client/assets/AgentDetailView-ZbHEbYRT.js +18 -0
  3. package/dist/client/assets/{AgentsView-BkB9FiMT.js → AgentsView-B3jYk8Kt.js} +3 -3
  4. package/dist/client/assets/ChatView-DhPkiEGs.js +1 -0
  5. package/dist/client/assets/{DevServerView-BkvtjZBa.js → DevServerView-DyGDEiBP.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-BK-KbnhP.js → DirectoryPicker-D5UIeIl6.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-BEg1CQAk.js → DocumentsView-DNHu1T8K.js} +1 -1
  8. package/dist/client/assets/{EvalsView-Berf9bQm.js → EvalsView-CpRobtDi.js} +1 -1
  9. package/dist/client/assets/{ExperimentalAgentOnboardingModal-jcInE50G.js → ExperimentalAgentOnboardingModal-DOY_oZi7.js} +1 -1
  10. package/dist/client/assets/{InsightsView-BX5bSF1J.js → InsightsView-vp0RE8Mg.js} +1 -1
  11. package/dist/client/assets/MemoryView-PSc5lGJt.js +2 -0
  12. package/dist/client/assets/MemoryView-zaXewZzi.css +1 -0
  13. package/dist/client/assets/{NodesView-DLUOBLf6.js → NodesView-DMj6HGeC.js} +1 -1
  14. package/dist/client/assets/{PiExtensionsManager-COlJf0Kx.js → PiExtensionsManager-DL_QcN56.js} +2 -2
  15. package/dist/client/assets/PluginManager-BtYKm8IT.js +1 -0
  16. package/dist/client/assets/{ResearchView-B256Lr8I.js → ResearchView-BhWqfdV0.js} +1 -1
  17. package/dist/client/assets/{SettingsModal-BeA_nQtW.js → SettingsModal-BAgB4_AR.js} +4 -4
  18. package/dist/client/assets/{SettingsModal-yRqM4DV8.js → SettingsModal-CUCyaAyE.js} +1 -1
  19. package/dist/client/assets/{SetupWizardModal-uUZk3TKT.js → SetupWizardModal-BKscasuh.js} +1 -1
  20. package/dist/client/assets/{SkillsView-CP8JX0P_.js → SkillsView-BdELqTy7.js} +1 -1
  21. package/dist/client/assets/{TodoView-DCRIkDZ-.js → TodoView-DFNGBDNV.js} +1 -1
  22. package/dist/client/assets/{folder-open-DHjELt8-.js → folder-open-k1xmUMyr.js} +1 -1
  23. package/dist/client/assets/index-Qq2JOOWx.css +1 -0
  24. package/dist/client/assets/{index-CQyVRLOb.js → index-TFYXEVpn.js} +160 -160
  25. package/dist/client/assets/{star-DYesq1AV.js → star-ne32r3Y4.js} +1 -1
  26. package/dist/client/assets/{upload-DTWF3Db5.js → upload-MS-2Gx53.js} +1 -1
  27. package/dist/client/assets/{users--syrel4l.js → users-C519GSjH.js} +1 -1
  28. package/dist/client/index.html +2 -2
  29. package/dist/client/version.json +1 -1
  30. package/dist/droid-cli/package.json +1 -1
  31. package/dist/extension.js +1370 -629
  32. package/dist/pi-claude-cli/package.json +1 -1
  33. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +9 -11
  34. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  35. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +30 -0
  36. package/dist/plugins/fusion-plugin-dependency-graph/package.json +3 -28
  37. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +899 -895
  38. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  39. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +68 -71
  40. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  41. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +47 -50
  42. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  43. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +155 -109
  44. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  45. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  46. package/dist/plugins/fusion-plugin-reports/src/index.ts +49 -3
  47. package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +38 -0
  48. package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-schema.test.ts +66 -0
  49. package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-store.test.ts +177 -0
  50. package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +341 -0
  51. package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +77 -0
  52. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  53. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  54. package/package.json +1 -1
  55. package/dist/client/assets/AgentDetailView-gy_5SUj2.js +0 -18
  56. package/dist/client/assets/ChatView-B_-B8fqu.js +0 -1
  57. package/dist/client/assets/MemoryView-CKElJY_3.js +0 -2
  58. package/dist/client/assets/MemoryView-DiajLXby.css +0 -1
  59. package/dist/client/assets/PluginManager-CfW55BF4.js +0 -1
  60. package/dist/client/assets/createLucideIcon-BazL2hk5.js +0 -21
  61. package/dist/client/assets/dashboard-view-BkTMSZYn.css +0 -1
  62. package/dist/client/assets/dashboard-view-CyWN-d02.js +0 -63
  63. package/dist/client/assets/dashboard-view-DdGlfuu-.css +0 -1
  64. package/dist/client/assets/dashboard-view-lR7YYmSC.js +0 -21
  65. package/dist/client/assets/index-CxA2Nn0_.css +0 -1
  66. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css +0 -58
  67. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.tsx +0 -301
  68. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphHighlight.css +0 -27
  69. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.css +0 -157
  70. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx +0 -126
  71. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.css +0 -35
  72. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.tsx +0 -36
  73. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx +0 -112
  74. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.persistence.test.tsx +0 -115
  75. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx +0 -128
  76. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx +0 -82
  77. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx +0 -307
  78. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphToolbar.test.tsx +0 -60
  79. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx +0 -75
  80. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filtering.test.tsx +0 -62
  81. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filters.test.ts +0 -78
  82. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/graphPositionStorage.test.ts +0 -95
  83. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/host-integration.test.ts +0 -74
  84. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/index.test.ts +0 -58
  85. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/interactions.test.tsx +0 -121
  86. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/layout.test.ts +0 -70
  87. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/persistence.test.tsx +0 -89
  88. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphData.test.ts +0 -86
  89. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphInteraction.test.ts +0 -167
  90. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphPositions.test.ts +0 -66
  91. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useNodeDrag.test.ts +0 -81
  92. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-interop.d.ts +0 -35
  93. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx +0 -19
  94. package/dist/plugins/fusion-plugin-dependency-graph/src/edges.tsx +0 -70
  95. package/dist/plugins/fusion-plugin-dependency-graph/src/filters.ts +0 -8
  96. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/__tests__/useDependencyChain.test.ts +0 -53
  97. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useDependencyChain.ts +0 -60
  98. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useGraphPositions.ts +0 -45
  99. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useNodeDrag.ts +0 -114
  100. package/dist/plugins/fusion-plugin-dependency-graph/src/index.ts +0 -24
  101. package/dist/plugins/fusion-plugin-dependency-graph/src/layout.ts +0 -91
  102. package/dist/plugins/fusion-plugin-dependency-graph/src/styles/drag.css +0 -15
  103. package/dist/plugins/fusion-plugin-dependency-graph/src/types.ts +0 -21
  104. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphData.ts +0 -17
  105. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphInteraction.ts +0 -292
  106. package/dist/plugins/fusion-plugin-dependency-graph/src/utils/graphPositionStorage.ts +0 -65
@@ -1,35 +0,0 @@
1
- .graph-toolbar {
2
- position: absolute;
3
- right: var(--space-md);
4
- bottom: var(--space-md);
5
- display: flex;
6
- flex-direction: column;
7
- gap: var(--space-xs);
8
- padding: var(--space-sm);
9
- background: var(--surface);
10
- border: var(--btn-border-width) solid var(--border);
11
- border-radius: var(--radius-md);
12
- box-shadow: var(--shadow-md);
13
- z-index: 10;
14
- }
15
-
16
- .graph-toolbar__zoom-label {
17
- min-width: 3.5ch;
18
- color: var(--text-muted);
19
- font-family: var(--font-mono);
20
- font-size: 0.75rem;
21
- text-align: center;
22
- }
23
-
24
- @media (max-width: 768px) {
25
- .graph-toolbar {
26
- right: var(--space-sm);
27
- bottom: var(--space-sm);
28
- padding: var(--space-md);
29
- }
30
-
31
- .graph-toolbar .btn-icon {
32
- min-width: calc(var(--space-xs) * 11);
33
- min-height: calc(var(--space-xs) * 11);
34
- }
35
- }
@@ -1,36 +0,0 @@
1
- import { Maximize, RotateCcw, ZoomIn, ZoomOut } from "lucide-react";
2
- import "./GraphToolbar.css";
3
-
4
- export interface GraphToolbarProps {
5
- zoom: number;
6
- onZoomIn: () => void;
7
- onZoomOut: () => void;
8
- onFitToGraph: () => void;
9
- onResetView: () => void;
10
- }
11
-
12
- export function GraphToolbar({
13
- zoom,
14
- onZoomIn,
15
- onZoomOut,
16
- onFitToGraph,
17
- onResetView,
18
- }: GraphToolbarProps) {
19
- return (
20
- <div className="graph-toolbar" data-testid="graph-toolbar">
21
- <button className="btn btn-icon" title="Zoom in (Ctrl+=)" aria-label="Zoom in" onClick={onZoomIn}>
22
- <ZoomIn size={16} />
23
- </button>
24
- <button className="btn btn-icon" title="Zoom out (Ctrl+-)" aria-label="Zoom out" onClick={onZoomOut}>
25
- <ZoomOut size={16} />
26
- </button>
27
- <div className="graph-toolbar__zoom-label" aria-live="polite">{Math.round(zoom * 100)}%</div>
28
- <button className="btn btn-icon" title="Fit to graph (Ctrl+Shift+F)" aria-label="Fit to graph" onClick={onFitToGraph}>
29
- <Maximize size={16} />
30
- </button>
31
- <button className="btn btn-icon" title="Reset view (Ctrl+0)" aria-label="Reset view" onClick={onResetView}>
32
- <RotateCcw size={16} />
33
- </button>
34
- </div>
35
- );
36
- }
@@ -1,112 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import type { Task } from "@fusion/core";
4
- import { DependencyGraph } from "../DependencyGraph";
5
-
6
- vi.mock("@fusion/dashboard/app/components/TaskCard", () => ({
7
- TaskCard: ({ task, onOpenDetail }: { task: Task; onOpenDetail: (task: Task) => void }) => (
8
- <button data-testid={`task-${task.id}`} onClick={() => onOpenDetail(task)}>{task.id}</button>
9
- ),
10
- }));
11
-
12
- vi.mock("../useGraphInteraction", () => ({
13
- useGraphInteraction: () => ({
14
- transform: "translate(0px, 0px) scale(1)",
15
- zoom: 1,
16
- transitioning: false,
17
- zoomIn: vi.fn(),
18
- zoomOut: vi.fn(),
19
- resetView: vi.fn(),
20
- fitToGraph: vi.fn(),
21
- onPointerDown: vi.fn(),
22
- onPointerMove: vi.fn(),
23
- onPointerUp: vi.fn(),
24
- onWheelZoom: vi.fn(),
25
- handleKeyDown: vi.fn(),
26
- }),
27
- }));
28
-
29
- function createTask(id: string, dependencies: string[] = []): Task {
30
- return {
31
- id,
32
- description: id,
33
- column: "todo",
34
- dependencies,
35
- steps: [],
36
- currentStep: 0,
37
- log: [],
38
- createdAt: new Date().toISOString(),
39
- updatedAt: new Date().toISOString(),
40
- } as Task;
41
- }
42
-
43
- afterEach(() => {
44
- cleanup();
45
- });
46
-
47
- describe("DependencyGraph highlighting", () => {
48
- const tasks = [createTask("A"), createTask("B", ["A"]), createTask("C", ["B"]), createTask("D")];
49
-
50
- it("highlights chain on hover and returns to neutral on mouse leave", () => {
51
- render(<DependencyGraph tasks={tasks} onOpenDetail={vi.fn()} />);
52
-
53
- fireEvent.mouseEnter(screen.getByTestId("graph-task-node-C"));
54
- expect(screen.getByTestId("graph-task-node-A").className).toContain("graph-task-node--highlighted");
55
- expect(screen.getByTestId("graph-task-node-B").className).toContain("graph-task-node--highlighted");
56
- expect(screen.getByTestId("graph-task-node-C").className).toContain("graph-task-node--highlighted");
57
- expect(screen.getByTestId("graph-task-node-D").className).toContain("graph-task-node--dimmed");
58
-
59
- fireEvent.mouseLeave(screen.getByTestId("graph-task-node-C"));
60
- expect(screen.getByTestId("graph-task-node-A").className).not.toContain("graph-task-node--highlighted");
61
- expect(screen.getByTestId("graph-task-node-D").className).not.toContain("graph-task-node--dimmed");
62
- });
63
-
64
- it("keeps selection until toggled or pane clicked", () => {
65
- render(<DependencyGraph tasks={tasks} onOpenDetail={vi.fn()} />);
66
-
67
- fireEvent.click(screen.getByTestId("graph-task-node-B"));
68
- expect(screen.getByTestId("graph-task-node-A").className).toContain("graph-task-node--highlighted");
69
-
70
- fireEvent.click(screen.getByTestId("graph-task-node-B"));
71
- expect(screen.getByTestId("graph-task-node-A").className).not.toContain("graph-task-node--highlighted");
72
-
73
- fireEvent.click(screen.getByTestId("graph-task-node-B"));
74
- fireEvent.click(document.querySelector(".dependency-graph__viewport") as Element);
75
- expect(screen.getByTestId("graph-task-node-B").className).not.toContain("graph-task-node--highlighted");
76
- });
77
-
78
- it("hover overrides selection and reverts when hover leaves", () => {
79
- render(<DependencyGraph tasks={tasks} onOpenDetail={vi.fn()} />);
80
-
81
- fireEvent.click(screen.getByTestId("graph-task-node-B"));
82
- fireEvent.mouseEnter(screen.getByTestId("graph-task-node-D"));
83
- expect(screen.getByTestId("graph-task-node-D").className).toContain("graph-task-node--highlighted");
84
- expect(screen.getByTestId("graph-task-node-A").className).toContain("graph-task-node--dimmed");
85
-
86
- fireEvent.mouseLeave(screen.getByTestId("graph-task-node-D"));
87
- expect(screen.getByTestId("graph-task-node-A").className).toContain("graph-task-node--highlighted");
88
- });
89
-
90
- it("applies edge dimming/highlighting and preserves click-to-detail", () => {
91
- const onOpenDetail = vi.fn();
92
- render(<DependencyGraph tasks={tasks} onOpenDetail={onOpenDetail} />);
93
-
94
- fireEvent.mouseEnter(screen.getByTestId("graph-task-node-C"));
95
- const edges = screen.getAllByTestId("dependency-edge");
96
- const edgeAB = edges.find((edge) => edge.getAttribute("data-edge-id") === "B->A");
97
- const edgeCB = edges.find((edge) => edge.getAttribute("data-edge-id") === "C->B");
98
-
99
- expect(edgeAB?.className.baseVal || edgeAB?.className).toContain("graph-edge--highlighted");
100
- expect(edgeCB?.className.baseVal || edgeCB?.className).toContain("graph-edge--highlighted");
101
-
102
- fireEvent.click(screen.getByTestId("task-C"));
103
- expect(onOpenDetail).toHaveBeenCalledTimes(1);
104
- expect(onOpenDetail).toHaveBeenCalledWith(expect.objectContaining({ id: "C" }));
105
- });
106
-
107
- it("highlights only isolated node with no dependencies", () => {
108
- render(<DependencyGraph tasks={[createTask("X")]} onOpenDetail={vi.fn()} />);
109
- fireEvent.mouseEnter(screen.getByTestId("graph-task-node-X"));
110
- expect(screen.getByTestId("graph-task-node-X").className).toContain("graph-task-node--highlighted");
111
- });
112
- });
@@ -1,115 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import type { Task } from "@fusion/core";
4
- import { DependencyGraph } from "../DependencyGraph";
5
-
6
- const fitToGraph = vi.fn();
7
-
8
- vi.mock("@fusion/dashboard/app/components/TaskCard", () => ({
9
- TaskCard: ({ task, onOpenDetail }: { task: Task; onOpenDetail: (task: Task) => void }) => (
10
- <button data-testid={`task-${task.id}`} onClick={() => onOpenDetail(task)}>{task.id}</button>
11
- ),
12
- }));
13
-
14
- vi.mock("../useGraphInteraction", () => ({
15
- useGraphInteraction: () => ({
16
- transform: "translate(0px, 0px) scale(1)",
17
- zoom: 1,
18
- transitioning: false,
19
- zoomIn: vi.fn(),
20
- zoomOut: vi.fn(),
21
- resetView: vi.fn(),
22
- fitToGraph,
23
- onPointerDown: vi.fn(),
24
- onPointerMove: vi.fn(),
25
- onPointerUp: vi.fn(),
26
- onWheelZoom: vi.fn(),
27
- handleKeyDown: vi.fn(),
28
- }),
29
- }));
30
-
31
- vi.mock("../layout", () => ({
32
- computeAutoLayout: ({ nodes }: { nodes: Array<{ task: { id: string } }> }) => {
33
- const map = new Map<string, { x: number; y: number }>();
34
- for (const node of nodes) {
35
- if (node.task.id === "A") map.set("A", { x: 0, y: 0 });
36
- if (node.task.id === "B") map.set("B", { x: 200, y: 0 });
37
- }
38
- return map;
39
- },
40
- }));
41
-
42
- function createStorage() {
43
- const store = new Map<string, string>();
44
- return {
45
- getItem: (key: string) => store.get(key) ?? null,
46
- setItem: (key: string, value: string) => {
47
- store.set(key, value);
48
- },
49
- removeItem: (key: string) => {
50
- store.delete(key);
51
- },
52
- };
53
- }
54
-
55
- function createTask(id: string, column: Task["column"] = "todo"): Task {
56
- return { id, description: id, column, dependencies: [], steps: [], currentStep: 0, log: [] } as Task;
57
- }
58
-
59
- describe("DependencyGraph persistence", () => {
60
- afterEach(() => {
61
- cleanup();
62
- });
63
-
64
- beforeEach(() => {
65
- Object.defineProperty(window, "localStorage", { value: createStorage(), configurable: true });
66
- fitToGraph.mockReset();
67
- });
68
-
69
- it("persists dragged node position across remount", () => {
70
- const { unmount } = render(<DependencyGraph tasks={[createTask("A")]} projectId="p1" />);
71
-
72
- const node = screen.getByTestId("graph-task-node-A");
73
- fireEvent.pointerDown(node, { pointerId: 1, isPrimary: true, clientX: 10, clientY: 10 });
74
- fireEvent.pointerMove(node, { pointerId: 1, isPrimary: true, clientX: 30, clientY: 40 });
75
- fireEvent.pointerUp(node, { pointerId: 1, isPrimary: true, clientX: 30, clientY: 40 });
76
-
77
- expect(window.localStorage.getItem("kb:p1:fusion-plugin-dependency-graph:positions")).toContain('"A":{"x":20,"y":30}');
78
-
79
- unmount();
80
- render(<DependencyGraph tasks={[createTask("A")]} projectId="p1" />);
81
-
82
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 20px");
83
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("top: 30px");
84
- });
85
-
86
- it("merges saved positions with auto-layout for new tasks", () => {
87
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", JSON.stringify({ A: { x: 25, y: 35 } }));
88
-
89
- render(<DependencyGraph tasks={[createTask("A"), createTask("B")]} projectId="p1" />);
90
-
91
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 25px");
92
- expect(screen.getByTestId("graph-task-node-B").getAttribute("style")).toContain("left: 200px");
93
- });
94
-
95
- it("fit to graph clears saved positions and reapplies auto-layout", () => {
96
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", JSON.stringify({ A: { x: 25, y: 35 } }));
97
-
98
- render(<DependencyGraph tasks={[createTask("A")]} projectId="p1" />);
99
- fireEvent.click(screen.getByRole("button", { name: "Fit to graph" }));
100
-
101
- expect(window.localStorage.getItem("kb:p1:fusion-plugin-dependency-graph:positions")).toBeNull();
102
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 0px");
103
- });
104
-
105
- it("switching projects loads project-scoped positions", () => {
106
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", JSON.stringify({ A: { x: 11, y: 22 } }));
107
- window.localStorage.setItem("kb:p2:fusion-plugin-dependency-graph:positions", JSON.stringify({ A: { x: 33, y: 44 } }));
108
-
109
- const { rerender } = render(<DependencyGraph tasks={[createTask("A")]} projectId="p1" />);
110
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 11px");
111
-
112
- rerender(<DependencyGraph tasks={[createTask("A")]} projectId="p2" />);
113
- expect(screen.getByTestId("graph-task-node-A").getAttribute("style")).toContain("left: 33px");
114
- });
115
- });
@@ -1,128 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import type { Task } from "@fusion/core";
4
- import { DependencyGraph } from "../DependencyGraph";
5
-
6
- const fitToGraph = vi.fn();
7
- const zoomIn = vi.fn();
8
- const zoomOut = vi.fn();
9
- const resetView = vi.fn();
10
- const handleKeyDown = vi.fn();
11
-
12
- vi.mock("@fusion/dashboard/app/components/TaskCard", () => ({
13
- TaskCard: ({ task, onOpenDetail, disableDrag }: { task: Task; onOpenDetail: (task: Task) => void; disableDrag?: boolean }) => (
14
- <button data-testid={`task-${task.id}`} draggable={!disableDrag} onClick={() => onOpenDetail(task)}>{task.id}</button>
15
- ),
16
- }));
17
-
18
- vi.mock("../useGraphInteraction", () => ({
19
- useGraphInteraction: () => ({
20
- transform: "translate(0px, 0px) scale(1)",
21
- zoom: 1,
22
- transitioning: false,
23
- zoomIn,
24
- zoomOut,
25
- resetView,
26
- fitToGraph,
27
- onPointerDown: vi.fn(),
28
- onPointerMove: vi.fn(),
29
- onPointerUp: vi.fn(),
30
- onWheelZoom: vi.fn(),
31
- handleKeyDown,
32
- }),
33
- }));
34
-
35
- function createTask(id: string, column: Task["column"], dependencies: string[] = []): Task {
36
- return { id, description: id, column, dependencies, steps: [], currentStep: 0, log: [] } as Task;
37
- }
38
-
39
- describe("DependencyGraph", () => {
40
- beforeEach(() => {
41
- fitToGraph.mockReset();
42
- zoomIn.mockReset();
43
- zoomOut.mockReset();
44
- resetView.mockReset();
45
- handleKeyDown.mockReset();
46
- });
47
-
48
- afterEach(() => {
49
- cleanup();
50
- });
51
-
52
- it("renders empty state for empty list", () => {
53
- render(<DependencyGraph tasks={[]} onOpenTaskDetail={vi.fn()} />);
54
- expect(screen.getByText(/No active tasks/i)).toBeTruthy();
55
- });
56
-
57
- it("renders only triage/todo/in-progress/in-review nodes from mixed columns", () => {
58
- render(
59
- <DependencyGraph
60
- tasks={[
61
- createTask("A", "triage"),
62
- createTask("B", "todo"),
63
- createTask("C", "in-progress"),
64
- createTask("D", "in-review"),
65
- createTask("E", "done"),
66
- createTask("F", "archived"),
67
- ]}
68
- onOpenTaskDetail={vi.fn()}
69
- />,
70
- );
71
-
72
- expect(screen.getByTestId("graph-task-node-A")).toBeTruthy();
73
- expect(screen.getByTestId("graph-task-node-B")).toBeTruthy();
74
- expect(screen.getByTestId("graph-task-node-C")).toBeTruthy();
75
- expect(screen.getByTestId("graph-task-node-D")).toBeTruthy();
76
- expect(screen.queryByTestId("graph-task-node-E")).toBeNull();
77
- expect(screen.queryByTestId("graph-task-node-F")).toBeNull();
78
- });
79
-
80
- it("auto-fits on initial load with active tasks", () => {
81
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
82
- expect(fitToGraph).toHaveBeenCalled();
83
- });
84
-
85
- it("forwards keyboard events to interaction hook", () => {
86
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
87
- const viewport = document.querySelector(".dependency-graph__viewport");
88
- if (!viewport) throw new Error("missing viewport");
89
- fireEvent.keyDown(viewport, { key: "=", ctrlKey: true });
90
- expect(handleKeyDown).toHaveBeenCalled();
91
- });
92
-
93
- it("sets viewport tabIndex for keyboard focus", () => {
94
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
95
- const viewport = document.querySelector(".dependency-graph__viewport");
96
- expect(viewport?.getAttribute("tabindex")).toBe("0");
97
- });
98
-
99
- it("renders toolbar controls", () => {
100
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
101
- expect(screen.getByRole("button", { name: "Zoom in" })).toBeTruthy();
102
- expect(screen.getByRole("button", { name: "Zoom out" })).toBeTruthy();
103
- expect(screen.getByRole("button", { name: "Fit to graph" })).toBeTruthy();
104
- expect(screen.getByRole("button", { name: "Reset view" })).toBeTruthy();
105
- expect(screen.getByText("100%")).toBeTruthy();
106
- });
107
-
108
- it("fit-to-graph button triggers fitToGraph", () => {
109
- render(<DependencyGraph tasks={[createTask("A", "todo")]} onOpenTaskDetail={vi.fn()} />);
110
- fireEvent.click(screen.getByRole("button", { name: "Fit to graph" }));
111
- expect(fitToGraph).toHaveBeenCalled();
112
- });
113
-
114
- it("clicking a card triggers onOpenDetail exactly once", () => {
115
- const onOpenDetail = vi.fn();
116
- render(<DependencyGraph tasks={[createTask("A", "in-progress")]} onOpenDetail={onOpenDetail} />);
117
- fireEvent.click(screen.getByTestId("task-A"));
118
- expect(onOpenDetail).toHaveBeenCalledTimes(1);
119
- expect(onOpenDetail).toHaveBeenCalledWith(expect.objectContaining({ id: "A" }));
120
- });
121
-
122
- it("falls back to onOpenTaskDetail when onOpenDetail is not provided", () => {
123
- const onOpenTaskDetail = vi.fn();
124
- render(<DependencyGraph tasks={[createTask("A", "in-progress")]} onOpenTaskDetail={onOpenTaskDetail} />);
125
- fireEvent.click(screen.getByTestId("task-A"));
126
- expect(onOpenTaskDetail).toHaveBeenCalledWith("A");
127
- });
128
- });
@@ -1,82 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import type React from "react";
4
- import type { Task } from "@fusion/core";
5
- import { GraphTaskNode } from "../GraphTaskNode";
6
-
7
- function task(id = "FN-1"): Task {
8
- return { id, description: id, column: "todo", dependencies: [], steps: [], currentStep: 0, log: [] } as Task;
9
- }
10
-
11
- function props(overrides: Partial<React.ComponentProps<typeof GraphTaskNode>> = {}): React.ComponentProps<typeof GraphTaskNode> {
12
- return {
13
- task: task(),
14
- projectId: "p1",
15
- position: { x: 0, y: 0 },
16
- scale: 1,
17
- isHighlighted: false,
18
- isDimmed: false,
19
- onNodePositionChange: vi.fn(),
20
- onNodeDragStateChange: vi.fn(),
21
- onOpenDetail: vi.fn(),
22
- addToast: vi.fn(),
23
- onUpdateTask: vi.fn(),
24
- onArchiveTask: vi.fn(),
25
- onUnarchiveTask: vi.fn(),
26
- onDeleteTask: vi.fn(),
27
- onRetryTask: vi.fn(),
28
- onOpenDetailWithTab: vi.fn(),
29
- onMoveTask: vi.fn(),
30
- onOpenMission: vi.fn(),
31
- taskStuckTimeoutMs: 1000,
32
- lastFetchTimeMs: Date.now(),
33
- workflowStepNameLookup: new Map<string, string>(),
34
- ...overrides,
35
- };
36
- }
37
-
38
- afterEach(() => {
39
- cleanup();
40
- });
41
-
42
- describe("GraphTaskNode drag", () => {
43
- it("does not open detail after drag threshold is exceeded", () => {
44
- const onOpenDetail = vi.fn();
45
- render(<GraphTaskNode {...props({ onOpenDetail })} />);
46
- const node = screen.getByTestId("graph-task-node-FN-1");
47
-
48
- fireEvent.pointerDown(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
49
- fireEvent.pointerMove(node, { pointerId: 1, clientX: 25, clientY: 25, isPrimary: true });
50
- fireEvent.pointerUp(node, { pointerId: 1, clientX: 25, clientY: 25, isPrimary: true });
51
- fireEvent.click(node);
52
-
53
- expect(onOpenDetail).not.toHaveBeenCalled();
54
- });
55
-
56
- it("applies dragging class only after threshold move", () => {
57
- const onNodePositionChange = vi.fn();
58
- render(<GraphTaskNode {...props({ onNodePositionChange })} />);
59
- const node = screen.getByTestId("graph-task-node-FN-1");
60
-
61
- fireEvent.pointerDown(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
62
- fireEvent.pointerMove(node, { pointerId: 1, clientX: 12, clientY: 12, isPrimary: true });
63
- expect(node.className).not.toContain("graph-node--dragging");
64
-
65
- fireEvent.pointerMove(node, { pointerId: 1, clientX: 20, clientY: 20, isPrimary: true });
66
- expect(node.className).toContain("graph-node--dragging");
67
- expect(onNodePositionChange).toHaveBeenCalled();
68
-
69
- fireEvent.pointerUp(node, { pointerId: 1, clientX: 20, clientY: 20, isPrimary: true });
70
- expect(node.className).not.toContain("graph-node--dragging");
71
- });
72
-
73
- it("composes highlight and dragging classes", () => {
74
- render(<GraphTaskNode {...props({ isHighlighted: true })} />);
75
- const node = screen.getByTestId("graph-task-node-FN-1");
76
- fireEvent.pointerDown(node, { pointerId: 1, clientX: 0, clientY: 0, isPrimary: true });
77
- fireEvent.pointerMove(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
78
-
79
- expect(node.className).toContain("graph-task-node--highlighted");
80
- expect(node.className).toContain("graph-node--dragging");
81
- });
82
- });