@runfusion/fusion 0.24.0 → 0.26.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 (167) hide show
  1. package/README.md +6 -0
  2. package/dist/bin.js +18646 -15669
  3. package/dist/client/assets/AgentDetailView-Cv-vgOj3.js +18 -0
  4. package/dist/client/assets/{AgentsView-BkB9FiMT.js → AgentsView-D6Zi5zfP.js} +4 -4
  5. package/dist/client/assets/ChatView-CAHjY9uO.js +1 -0
  6. package/dist/client/assets/{DevServerView-BkvtjZBa.js → DevServerView--_WBvIDQ.js} +1 -1
  7. package/dist/client/assets/{DirectoryPicker-BK-KbnhP.js → DirectoryPicker-xedtR-Rd.js} +1 -1
  8. package/dist/client/assets/{DocumentsView-BEg1CQAk.js → DocumentsView-Bg2oaZks.js} +1 -1
  9. package/dist/client/assets/{EvalsView-Berf9bQm.js → EvalsView-B3uOCXfr.js} +1 -1
  10. package/dist/client/assets/{ExperimentalAgentOnboardingModal-jcInE50G.js → ExperimentalAgentOnboardingModal-Bx6yXVS5.js} +1 -1
  11. package/dist/client/assets/{InsightsView-BX5bSF1J.js → InsightsView-Q1zvtF4F.js} +1 -1
  12. package/dist/client/assets/MemoryView-xcN_eouf.js +2 -0
  13. package/dist/client/assets/MemoryView-zaXewZzi.css +1 -0
  14. package/dist/client/assets/{NodesView-DLUOBLf6.js → NodesView-RxXg58_Q.js} +1 -1
  15. package/dist/client/assets/{PiExtensionsManager-COlJf0Kx.js → PiExtensionsManager-Cc8aAZXg.js} +2 -2
  16. package/dist/client/assets/PluginManager-BEkyBajl.js +1 -0
  17. package/dist/client/assets/{ResearchView-BzCcDAS4.css → ResearchView-BEI4ZSGs.css} +1 -1
  18. package/dist/client/assets/ResearchView-CERNf7sJ.js +1 -0
  19. package/dist/client/assets/{SettingsModal-yRqM4DV8.js → SettingsModal-B1r0yASu.js} +1 -1
  20. package/dist/client/assets/SettingsModal-BLsac7CJ.js +31 -0
  21. package/dist/client/assets/SettingsModal-Cis-4Lot.css +1 -0
  22. package/dist/client/assets/{SetupWizardModal-uUZk3TKT.js → SetupWizardModal-D1q548_L.js} +1 -1
  23. package/dist/client/assets/{SkillsView-CP8JX0P_.js → SkillsView-ClLM6u6p.js} +1 -1
  24. package/dist/client/assets/StashRecoveryView-B_8WIQEo.css +1 -0
  25. package/dist/client/assets/StashRecoveryView-ze0pEZ5U.js +1 -0
  26. package/dist/client/assets/{TodoView-DCRIkDZ-.js → TodoView-CTmIfy2M.js} +1 -1
  27. package/dist/client/assets/{dashboard-view-lR7YYmSC.js → dashboard-view-4xAN3yO5.js} +2 -2
  28. package/dist/client/assets/{folder-open-DHjELt8-.js → folder-open-BZuKESeq.js} +1 -1
  29. package/dist/client/assets/index-Bdw6llW6.js +692 -0
  30. package/dist/client/assets/index-CZGlyJuS.css +1 -0
  31. package/dist/client/assets/{star-DYesq1AV.js → star-D75YKEq-.js} +1 -1
  32. package/dist/client/assets/{upload-DTWF3Db5.js → upload-BYYTgWFj.js} +1 -1
  33. package/dist/client/assets/{users--syrel4l.js → users-RS90Aii3.js} +1 -1
  34. package/dist/client/index.html +2 -2
  35. package/dist/client/version.json +1 -1
  36. package/dist/droid-cli/package.json +1 -1
  37. package/dist/extension.js +5640 -3618
  38. package/dist/pi-claude-cli/package.json +1 -1
  39. package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +6 -0
  40. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +26 -0
  41. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +20 -0
  42. package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +14 -0
  43. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +9 -11
  44. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  45. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +30 -0
  46. package/dist/plugins/fusion-plugin-dependency-graph/package.json +3 -28
  47. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +899 -895
  48. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  49. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +68 -71
  50. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  51. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +47 -50
  52. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  53. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +155 -109
  54. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  55. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  56. package/dist/plugins/fusion-plugin-reports/src/index.ts +49 -3
  57. package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +38 -0
  58. package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-schema.test.ts +66 -0
  59. package/dist/plugins/fusion-plugin-reports/src/store/__tests__/report-store.test.ts +177 -0
  60. package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +341 -0
  61. package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +77 -0
  62. package/dist/plugins/fusion-plugin-roadmap/{src/dashboard/RoadmapsView.css → bundled.css} +13 -219
  63. package/dist/plugins/fusion-plugin-roadmap/bundled.js +29535 -0
  64. package/dist/plugins/fusion-plugin-roadmap/package.json +4 -41
  65. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  66. package/package.json +2 -3
  67. package/dist/client/assets/AgentDetailView-gy_5SUj2.js +0 -18
  68. package/dist/client/assets/ChatView-B_-B8fqu.js +0 -1
  69. package/dist/client/assets/MemoryView-CKElJY_3.js +0 -2
  70. package/dist/client/assets/MemoryView-DiajLXby.css +0 -1
  71. package/dist/client/assets/PluginManager-CfW55BF4.js +0 -1
  72. package/dist/client/assets/ResearchView-B256Lr8I.js +0 -1
  73. package/dist/client/assets/SettingsModal-BeA_nQtW.js +0 -31
  74. package/dist/client/assets/SettingsModal-DzsLquBu.css +0 -1
  75. package/dist/client/assets/index-CQyVRLOb.js +0 -692
  76. package/dist/client/assets/index-CxA2Nn0_.css +0 -1
  77. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.css +0 -58
  78. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraph.tsx +0 -301
  79. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphHighlight.css +0 -27
  80. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.css +0 -157
  81. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphTaskNode.tsx +0 -126
  82. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.css +0 -35
  83. package/dist/plugins/fusion-plugin-dependency-graph/src/GraphToolbar.tsx +0 -36
  84. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.highlighting.test.tsx +0 -112
  85. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.persistence.test.tsx +0 -115
  86. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/DependencyGraph.test.tsx +0 -128
  87. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx +0 -82
  88. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx +0 -307
  89. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphToolbar.test.tsx +0 -60
  90. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/edges.test.tsx +0 -75
  91. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filtering.test.tsx +0 -62
  92. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/filters.test.ts +0 -78
  93. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/graphPositionStorage.test.ts +0 -95
  94. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/host-integration.test.ts +0 -74
  95. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/index.test.ts +0 -58
  96. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/interactions.test.tsx +0 -121
  97. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/layout.test.ts +0 -70
  98. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/persistence.test.tsx +0 -89
  99. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphData.test.ts +0 -86
  100. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphInteraction.test.ts +0 -167
  101. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useGraphPositions.test.ts +0 -66
  102. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/useNodeDrag.test.ts +0 -81
  103. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-interop.d.ts +0 -35
  104. package/dist/plugins/fusion-plugin-dependency-graph/src/dashboard-view.tsx +0 -19
  105. package/dist/plugins/fusion-plugin-dependency-graph/src/edges.tsx +0 -70
  106. package/dist/plugins/fusion-plugin-dependency-graph/src/filters.ts +0 -8
  107. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/__tests__/useDependencyChain.test.ts +0 -53
  108. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useDependencyChain.ts +0 -60
  109. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useGraphPositions.ts +0 -45
  110. package/dist/plugins/fusion-plugin-dependency-graph/src/hooks/useNodeDrag.ts +0 -114
  111. package/dist/plugins/fusion-plugin-dependency-graph/src/index.ts +0 -24
  112. package/dist/plugins/fusion-plugin-dependency-graph/src/layout.ts +0 -91
  113. package/dist/plugins/fusion-plugin-dependency-graph/src/styles/drag.css +0 -15
  114. package/dist/plugins/fusion-plugin-dependency-graph/src/types.ts +0 -21
  115. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphData.ts +0 -17
  116. package/dist/plugins/fusion-plugin-dependency-graph/src/useGraphInteraction.ts +0 -292
  117. package/dist/plugins/fusion-plugin-dependency-graph/src/utils/graphPositionStorage.ts +0 -65
  118. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/api-client.test.ts +0 -101
  119. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/index.test.ts +0 -92
  120. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-routes.test.ts +0 -48
  121. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-suggestions.test.ts +0 -31
  122. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx +0 -2559
  123. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx +0 -1144
  124. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/useRoadmaps.test.ts +0 -1756
  125. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/api.ts +0 -70
  126. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/test-setup.ts +0 -7
  127. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/types.ts +0 -1
  128. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useConfirm.ts +0 -8
  129. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts +0 -1188
  130. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useViewportMode.ts +0 -20
  131. package/dist/plugins/fusion-plugin-roadmap/src/dashboard-view.tsx +0 -6
  132. package/dist/plugins/fusion-plugin-roadmap/src/index.ts +0 -74
  133. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-routes.ts +0 -1
  134. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-schema.ts +0 -41
  135. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.d.ts +0 -15
  136. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.ts +0 -15
  137. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts +0 -283
  138. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts.map +0 -1
  139. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js +0 -21
  140. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js.map +0 -1
  141. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.ts +0 -310
  142. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts +0 -5
  143. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts.map +0 -1
  144. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js +0 -361
  145. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js.map +0 -1
  146. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.ts +0 -408
  147. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts +0 -68
  148. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts.map +0 -1
  149. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js +0 -300
  150. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js.map +0 -1
  151. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.ts +0 -381
  152. package/dist/plugins/fusion-plugin-roadmap/src/server/index.d.ts +0 -3
  153. package/dist/plugins/fusion-plugin-roadmap/src/server/index.ts +0 -1
  154. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-handoff.test.ts +0 -445
  155. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-ordering.test.ts +0 -334
  156. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-store.test.ts +0 -1318
  157. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-handoff.ts +0 -163
  158. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts +0 -37
  159. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts.map +0 -1
  160. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js +0 -188
  161. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js.map +0 -1
  162. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.ts +0 -311
  163. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts +0 -299
  164. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts.map +0 -1
  165. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js +0 -765
  166. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js.map +0 -1
  167. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.ts +0 -1001
@@ -1,307 +0,0 @@
1
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
2
- import type { Task } from "@fusion/core";
3
- import { afterEach, describe, expect, it, vi } from "vitest";
4
- import { TaskCard } from "@fusion/dashboard/app/components/TaskCard";
5
- import { GraphTaskNode } from "../GraphTaskNode";
6
-
7
- function createTask(overrides: Partial<Task> = {}): Task {
8
- return {
9
- id: "FN-TEST",
10
- description: "Task description",
11
- column: "todo",
12
- dependencies: [],
13
- steps: [],
14
- currentStep: 0,
15
- log: [],
16
- ...overrides,
17
- } as Task;
18
- }
19
-
20
- function createProps(task: Task) {
21
- return {
22
- task,
23
- position: { x: 0, y: 0 },
24
- scale: 1,
25
- onNodePositionChange: vi.fn(),
26
- onNodeDragStateChange: vi.fn(),
27
- projectId: "proj-1",
28
- onOpenDetail: vi.fn(),
29
- addToast: vi.fn(),
30
- onUpdateTask: vi.fn(),
31
- onArchiveTask: vi.fn(),
32
- onUnarchiveTask: vi.fn(),
33
- onDeleteTask: vi.fn(),
34
- onRetryTask: vi.fn(),
35
- onOpenDetailWithTab: vi.fn(),
36
- onMoveTask: vi.fn(),
37
- onOpenMission: vi.fn(),
38
- taskStuckTimeoutMs: 60_000,
39
- lastFetchTimeMs: Date.now(),
40
- workflowStepNameLookup: new Map<string, string>(),
41
- };
42
- }
43
-
44
- afterEach(() => {
45
- cleanup();
46
- });
47
-
48
- describe("GraphTaskNode", () => {
49
- it("renders a TaskCard and passes core props through", () => {
50
- const props = createProps(createTask());
51
- const { container } = render(<GraphTaskNode {...props} style={{ left: 10, top: 20 }} />);
52
-
53
- const node = screen.getByTestId("graph-task-node-FN-TEST");
54
- expect(node).toBeTruthy();
55
- expect(container.querySelector(".card-title")?.textContent).toContain("Task description");
56
- expect(node.getAttribute("draggable")).toBe("false");
57
- expect(container.querySelector(".card")?.getAttribute("draggable")).toBe("false");
58
- });
59
-
60
- it("shows active indicator with capitalized status for in-progress executing tasks", () => {
61
- const props = createProps(
62
- createTask({
63
- column: "in-progress",
64
- status: "executing",
65
- steps: [{ name: "step one", status: "in-progress" }],
66
- }),
67
- );
68
-
69
- const { container } = render(<GraphTaskNode {...props} />);
70
- const node = screen.getByTestId("graph-task-node-FN-TEST");
71
- expect(node.className).toContain("graph-task-node--active");
72
- expect(container.querySelector(".card")?.className).toContain("agent-active");
73
- expect(screen.getByText("Executing")).toBeTruthy();
74
- expect(container.querySelector(".graph-task-active-indicator")).toBeTruthy();
75
- });
76
-
77
- it("defaults indicator text to Executing when status is missing on in-progress tasks", () => {
78
- const props = createProps(createTask({ column: "in-progress", status: undefined }));
79
- const { container } = render(<GraphTaskNode {...props} />);
80
-
81
- expect(container.querySelector(".graph-task-active-indicator")).toBeTruthy();
82
- expect(screen.getByText("Executing")).toBeTruthy();
83
- });
84
-
85
- it("applies in-review visual class and does not apply active class for in-review tasks", () => {
86
- const props = createProps(createTask({ column: "in-review", status: "idle" }));
87
- const { container } = render(<GraphTaskNode {...props} />);
88
-
89
- const node = screen.getByTestId("graph-task-node-FN-TEST");
90
- expect(container.querySelector(".graph-task-active-indicator")).toBeFalsy();
91
- expect(node.className).toContain("graph-task-node--in-review");
92
- expect(node.className).not.toContain("graph-task-node--active");
93
- });
94
-
95
- it.each(["todo", "triage", "in-progress"] as const)("does not apply in-review class for %s tasks", (column) => {
96
- const props = createProps(createTask({ column, status: column === "in-progress" ? "executing" : "idle" }));
97
-
98
- render(<GraphTaskNode {...props} />);
99
- expect(screen.getByTestId("graph-task-node-FN-TEST").className).not.toContain("graph-task-node--in-review");
100
- });
101
-
102
- it("does not render active indicator for paused in-progress tasks", () => {
103
- const props = createProps(createTask({ column: "in-progress", status: "executing", paused: true }));
104
- const { container } = render(<GraphTaskNode {...props} />);
105
-
106
- expect(container.querySelector(".graph-task-active-indicator")).toBeFalsy();
107
- });
108
-
109
- it("does not render active indicator for failed in-progress tasks", () => {
110
- const props = createProps(createTask({ column: "in-progress", status: "failed" }));
111
- const { container } = render(<GraphTaskNode {...props} />);
112
-
113
- expect(container.querySelector(".graph-task-active-indicator")).toBeFalsy();
114
- });
115
-
116
- it("sets current-step attribute for active task when current step is valid", () => {
117
- const props = createProps(
118
- createTask({
119
- column: "in-progress",
120
- status: "executing",
121
- steps: [
122
- { name: "step one", status: "done" },
123
- { name: "step two", status: "done" },
124
- { name: "step three", status: "in-progress" },
125
- ],
126
- currentStep: 2,
127
- }),
128
- );
129
-
130
- render(<GraphTaskNode {...props} />);
131
- expect(screen.getByTestId("graph-task-node-FN-TEST").getAttribute("data-current-step")).toBe("2");
132
- });
133
-
134
- it("sets current-step attribute to zero when first step is active", () => {
135
- const props = createProps(
136
- createTask({
137
- column: "in-progress",
138
- status: "executing",
139
- steps: [{ name: "step one", status: "in-progress" }],
140
- currentStep: 0,
141
- }),
142
- );
143
-
144
- render(<GraphTaskNode {...props} />);
145
- expect(screen.getByTestId("graph-task-node-FN-TEST").getAttribute("data-current-step")).toBe("0");
146
- });
147
-
148
- it("omits current-step attribute when current step is out of bounds", () => {
149
- const props = createProps(
150
- createTask({
151
- column: "in-progress",
152
- status: "executing",
153
- steps: [{ name: "step one", status: "in-progress" }],
154
- currentStep: 10,
155
- }),
156
- );
157
-
158
- render(<GraphTaskNode {...props} />);
159
- expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
160
- });
161
-
162
- it("omits current-step attribute when current step is negative", () => {
163
- const props = createProps(createTask({ column: "in-progress", status: "executing", steps: [], currentStep: -1 }));
164
-
165
- render(<GraphTaskNode {...props} />);
166
- expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
167
- });
168
-
169
- it("omits current-step attribute when current step is undefined", () => {
170
- const props = createProps(createTask({ column: "in-progress", status: "executing", steps: [], currentStep: undefined }));
171
-
172
- render(<GraphTaskNode {...props} />);
173
- expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
174
- });
175
-
176
- it("does not set current-step for non-active tasks", () => {
177
- const props = createProps(
178
- createTask({
179
- column: "todo",
180
- status: "queued",
181
- steps: [{ name: "step one", status: "in-progress" }],
182
- currentStep: 0,
183
- }),
184
- );
185
-
186
- render(<GraphTaskNode {...props} />);
187
- expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
188
- });
189
-
190
- it("sets current-step to native step index when workflow steps are present", () => {
191
- const props = createProps(
192
- createTask({
193
- column: "in-progress",
194
- status: "executing",
195
- steps: [
196
- { id: "native-1", name: "native one", status: "done" },
197
- { id: "native-2", name: "native two", status: "in-progress" },
198
- ],
199
- enabledWorkflowSteps: ["wf-1"],
200
- currentStep: 1,
201
- }),
202
- );
203
-
204
- render(<GraphTaskNode {...props} />);
205
- expect(screen.getByTestId("graph-task-node-FN-TEST").getAttribute("data-current-step")).toBe("1");
206
- });
207
-
208
- it("does not set current-step when native step list is empty even with workflow steps", () => {
209
- const props = createProps(
210
- createTask({
211
- column: "in-progress",
212
- status: "executing",
213
- steps: [],
214
- enabledWorkflowSteps: ["wf-1"],
215
- currentStep: 0,
216
- }),
217
- );
218
-
219
- render(<GraphTaskNode {...props} />);
220
- expect(screen.getByTestId("graph-task-node-FN-TEST").hasAttribute("data-current-step")).toBe(false);
221
- });
222
-
223
- it("clicking card opens task detail exactly once", () => {
224
- const props = createProps(createTask());
225
- const { container } = render(<GraphTaskNode {...props} />);
226
-
227
- const card = container.querySelector(".card");
228
- expect(card).toBeTruthy();
229
- fireEvent.click(card!);
230
- expect(props.onOpenDetail).toHaveBeenCalledTimes(1);
231
- expect(props.onOpenDetail).toHaveBeenCalledWith(expect.objectContaining({ id: "FN-TEST" }));
232
- });
233
-
234
- it("clicking active indicator surface opens task detail", () => {
235
- const props = createProps(createTask({ column: "in-progress", status: "executing" }));
236
- const { container } = render(<GraphTaskNode {...props} />);
237
-
238
- const indicator = container.querySelector(".graph-task-active-indicator");
239
- expect(indicator).toBeTruthy();
240
- fireEvent.click(indicator!);
241
-
242
- expect(props.onOpenDetail).toHaveBeenCalledTimes(1);
243
- expect(props.onOpenDetail).toHaveBeenCalledWith(expect.objectContaining({ id: "FN-TEST" }));
244
- });
245
-
246
- it("applies highlighted class only when requested", () => {
247
- const highlightedProps = createProps(createTask({ id: "FN-HL" }));
248
- const neutralProps = createProps(createTask({ id: "FN-NEUTRAL" }));
249
-
250
- const { unmount } = render(<GraphTaskNode {...highlightedProps} isHighlighted={true} />);
251
- expect(screen.getByTestId("graph-task-node-FN-HL").className).toContain("graph-task-node--highlighted");
252
- unmount();
253
-
254
- render(<GraphTaskNode {...neutralProps} />);
255
- const neutral = screen.getByTestId("graph-task-node-FN-NEUTRAL");
256
- expect(neutral.className).not.toContain("graph-task-node--highlighted");
257
- expect(neutral.className).not.toContain("graph-task-node--dimmed");
258
- });
259
-
260
- it("renders the same TaskCard structure as board usage", () => {
261
- const task = createTask({
262
- id: "FN-SAME",
263
- column: "in-progress",
264
- status: "executing",
265
- error: "Execution failed",
266
- missionId: "M-1",
267
- sourceType: "automation",
268
- sourceAgentId: "agent-1",
269
- steps: [{ name: "sync", status: "in-progress" }],
270
- currentStep: 0,
271
- });
272
- const props = createProps(task);
273
-
274
- const { container } = render(
275
- <div>
276
- <TaskCard {...props} disableDrag={true} />
277
- <GraphTaskNode {...props} />
278
- </div>,
279
- );
280
-
281
- const cards = container.querySelectorAll(".card");
282
- expect(cards.length).toBe(2);
283
-
284
- const [boardCard, graphCard] = cards;
285
- const selectors = [
286
- ".card-id",
287
- ".card-title",
288
- ".card-status-badge",
289
- ".card-step-dot",
290
- ".card-step-name",
291
- ".card-progress",
292
- ".card-progress-fill",
293
- ".card-error",
294
- ".card-mission-badge",
295
- ".card-provider-icons",
296
- ".card-agent-badge",
297
- ];
298
-
299
- for (const selector of selectors) {
300
- expect(Boolean(boardCard.querySelector(selector))).toBe(Boolean(graphCard.querySelector(selector)));
301
- }
302
-
303
- expect(Boolean(boardCard.querySelector(".card-error"))).toBe(Boolean(graphCard.querySelector(".card-error")));
304
- expect(boardCard.querySelector(".card-id")?.textContent).toBe(graphCard.querySelector(".card-id")?.textContent);
305
- expect(boardCard.querySelector(".card-title")?.textContent).toBe(graphCard.querySelector(".card-title")?.textContent);
306
- });
307
- });
@@ -1,60 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3
- import { GraphToolbar } from "../GraphToolbar";
4
-
5
- describe("GraphToolbar", () => {
6
- afterEach(() => {
7
- cleanup();
8
- });
9
- it("renders controls and zoom percent", () => {
10
- render(
11
- <GraphToolbar
12
- zoom={1.25}
13
- onZoomIn={vi.fn()}
14
- onZoomOut={vi.fn()}
15
- onFitToGraph={vi.fn()}
16
- onResetView={vi.fn()}
17
- />,
18
- );
19
-
20
- expect(screen.getByRole("button", { name: "Zoom in" })).toBeTruthy();
21
- expect(screen.getByRole("button", { name: "Zoom out" })).toBeTruthy();
22
- expect(screen.getByRole("button", { name: "Fit to graph" })).toBeTruthy();
23
- expect(screen.getByRole("button", { name: "Reset view" })).toBeTruthy();
24
- expect(screen.getByText("125%")).toBeTruthy();
25
- });
26
-
27
- it("fires callbacks", () => {
28
- const onZoomIn = vi.fn();
29
- const onZoomOut = vi.fn();
30
- const onFitToGraph = vi.fn();
31
- const onResetView = vi.fn();
32
-
33
- render(
34
- <GraphToolbar
35
- zoom={1}
36
- onZoomIn={onZoomIn}
37
- onZoomOut={onZoomOut}
38
- onFitToGraph={onFitToGraph}
39
- onResetView={onResetView}
40
- />,
41
- );
42
-
43
- fireEvent.click(screen.getByRole("button", { name: "Zoom in" }));
44
- fireEvent.click(screen.getByRole("button", { name: "Zoom out" }));
45
- fireEvent.click(screen.getByRole("button", { name: "Fit to graph" }));
46
- fireEvent.click(screen.getByRole("button", { name: "Reset view" }));
47
-
48
- expect(onZoomIn).toHaveBeenCalledOnce();
49
- expect(onZoomOut).toHaveBeenCalledOnce();
50
- expect(onFitToGraph).toHaveBeenCalledOnce();
51
- expect(onResetView).toHaveBeenCalledOnce();
52
- });
53
-
54
- it("applies toolbar class", () => {
55
- render(
56
- <GraphToolbar zoom={1} onZoomIn={vi.fn()} onZoomOut={vi.fn()} onFitToGraph={vi.fn()} onResetView={vi.fn()} />,
57
- );
58
- expect(screen.getByTestId("graph-toolbar").className).toContain("graph-toolbar");
59
- });
60
- });
@@ -1,75 +0,0 @@
1
- import { afterEach, describe, expect, it } from "vitest";
2
- import { cleanup, render, screen } from "@testing-library/react";
3
- import { GraphEdges } from "../edges";
4
- import type { GraphEdge } from "../types";
5
-
6
- function renderEdges(edges: GraphEdge[], highlightedEdgeIds?: Set<string>) {
7
- const positions = new Map([
8
- ["A", { x: 0, y: 0 }],
9
- ["B", { x: 320, y: 180 }],
10
- ["C", { x: 640, y: 180 }],
11
- ]);
12
-
13
- return render(
14
- <GraphEdges
15
- edges={edges}
16
- positions={positions}
17
- highlightedEdgeIds={highlightedEdgeIds}
18
- />,
19
- );
20
- }
21
-
22
- describe("GraphEdges", () => {
23
- afterEach(() => {
24
- cleanup();
25
- });
26
- it("renders single edge", () => {
27
- renderEdges([{ source: "A", target: "B" }]);
28
- const edge = screen.getAllByTestId("dependency-edge")[0];
29
- expect(edge.getAttribute("opacity")).toBe("1");
30
- expect(edge.getAttribute("stroke")).toBe("var(--border)");
31
- });
32
-
33
- it("renders multiple edges", () => {
34
- renderEdges([
35
- { source: "A", target: "B" },
36
- { source: "A", target: "C" },
37
- ]);
38
- expect(screen.getAllByTestId("dependency-edge")).toHaveLength(2);
39
- });
40
-
41
- it("supports edges with same source", () => {
42
- renderEdges([
43
- { source: "A", target: "B" },
44
- { source: "A", target: "C" },
45
- ]);
46
- expect(screen.getAllByTestId("dependency-edge")).toHaveLength(2);
47
- });
48
-
49
- it("supports edges with same target", () => {
50
- renderEdges([
51
- { source: "B", target: "A" },
52
- { source: "C", target: "A" },
53
- ]);
54
- expect(screen.getAllByTestId("dependency-edge")).toHaveLength(2);
55
- });
56
-
57
- it("dims non-highlighted edges when highlight set provided", () => {
58
- renderEdges(
59
- [
60
- { source: "A", target: "B" },
61
- { source: "A", target: "C" },
62
- ],
63
- new Set(["A->B"]),
64
- );
65
-
66
- const all = screen.getAllByTestId("dependency-edge");
67
- const highlighted = all.find((edge) => edge.getAttribute("data-edge-id") === "A->B");
68
- const dimmed = all.find((edge) => edge.getAttribute("data-edge-id") === "A->C");
69
-
70
- expect(highlighted?.getAttribute("opacity")).toBe("1");
71
- expect(highlighted?.getAttribute("class") ?? "").toContain("graph-edge--highlighted");
72
- expect(dimmed?.getAttribute("opacity")).toBe("0.15");
73
- expect(dimmed?.getAttribute("class") ?? "").toContain("graph-edge--dimmed");
74
- });
75
- });
@@ -1,62 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { render, renderHook } from "@testing-library/react";
3
- import type { Task } from "@fusion/core";
4
- import { filterGraphTasks } from "../filters";
5
- import { useGraphData } from "../useGraphData";
6
- import { DependencyGraph } from "../DependencyGraph";
7
-
8
- function createTask(id: string, column: Task["column"], dependencies: string[] = [], status?: Task["status"]): Task {
9
- return {
10
- id,
11
- description: id,
12
- column,
13
- status,
14
- dependencies,
15
- steps: [],
16
- currentStep: 0,
17
- log: [],
18
- } as Task;
19
- }
20
-
21
- describe("dependency graph filtering", () => {
22
- it("includes triage/todo/in-progress/in-review and excludes done/archived by column", () => {
23
- const tasks = [
24
- createTask("T", "triage", [], "done"),
25
- createTask("TD", "todo", [], "done"),
26
- createTask("P", "in-progress", [], "done"),
27
- createTask("R", "in-review", [], "done"),
28
- createTask("D", "done", [], "in-progress"),
29
- createTask("A", "archived", [], "in-progress"),
30
- ];
31
-
32
- expect(filterGraphTasks(tasks).map((task) => task.id)).toEqual(["T", "TD", "P", "R"]);
33
- });
34
-
35
- it("keeps standalone tasks without dependencies as nodes", () => {
36
- const tasks = [createTask("A", "todo")];
37
- const { result } = renderHook(() => useGraphData(tasks));
38
-
39
- expect(result.current.nodes.map((node) => node.task.id)).toEqual(["A"]);
40
- expect(result.current.edges).toEqual([]);
41
- });
42
-
43
- it("keeps only edges to included dependency tasks for mixed-status dependencies", () => {
44
- const filteredTasks = filterGraphTasks([
45
- createTask("A", "in-progress", ["B", "DONE", "ARCH"]),
46
- createTask("B", "todo"),
47
- createTask("DONE", "done"),
48
- createTask("ARCH", "archived"),
49
- ]);
50
-
51
- const { result } = renderHook(() => useGraphData(filteredTasks));
52
- expect(result.current.edges).toEqual([{ source: "A", target: "B" }]);
53
- });
54
-
55
- it("renders empty state when all tasks are done/archived", () => {
56
- const { container } = render(
57
- <DependencyGraph tasks={[createTask("D", "done"), createTask("A", "archived")]} onOpenTaskDetail={() => {}} />,
58
- );
59
-
60
- expect(container.textContent).toContain("No active tasks");
61
- });
62
- });
@@ -1,78 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { Task } from "@fusion/core";
3
- import { EXCLUDED_COLUMNS, filterGraphTasks, INCLUDED_COLUMNS } from "../filters";
4
-
5
- function createTask(id: string, column: Task["column"], dependencies: string[] = []): Task {
6
- return {
7
- id,
8
- description: `Task ${id}`,
9
- column,
10
- dependencies,
11
- steps: [],
12
- currentStep: 0,
13
- log: [],
14
- } as Task;
15
- }
16
-
17
- describe("filterGraphTasks", () => {
18
- it("returns empty for empty input", () => {
19
- expect(filterGraphTasks([])).toEqual([]);
20
- });
21
-
22
- it("includes tasks from all included columns", () => {
23
- const tasks = Array.from(INCLUDED_COLUMNS).map((column, index) => createTask(`FN-${index + 1}`, column));
24
-
25
- expect(filterGraphTasks(tasks)).toEqual(tasks);
26
- });
27
-
28
- it("returns empty when only excluded columns are present", () => {
29
- const tasks = Array.from(EXCLUDED_COLUMNS).map((column, index) => createTask(`FN-${index + 1}`, column));
30
-
31
- expect(filterGraphTasks(tasks)).toEqual([]);
32
- });
33
-
34
- it("includes and excludes exact columns for mixed input", () => {
35
- const tasks = [
36
- createTask("FN-1", "triage"),
37
- createTask("FN-2", "todo"),
38
- createTask("FN-3", "in-progress"),
39
- createTask("FN-4", "in-review"),
40
- createTask("FN-5", "done"),
41
- createTask("FN-6", "archived"),
42
- ];
43
-
44
- expect(filterGraphTasks(tasks).map((task) => task.id)).toEqual(["FN-1", "FN-2", "FN-3", "FN-4"]);
45
- });
46
-
47
- it.each([
48
- ["triage", true],
49
- ["todo", true],
50
- ["in-progress", true],
51
- ["in-review", true],
52
- ["done", false],
53
- ["archived", false],
54
- ] as const)("column %s inclusion=%s", (column, included) => {
55
- const task = createTask("FN-1", column);
56
- const result = filterGraphTasks([task]);
57
-
58
- expect(result.length > 0).toBe(included);
59
- });
60
-
61
- it("gracefully excludes tasks with invalid columns", () => {
62
- const invalidTask = {
63
- ...createTask("FN-invalid", "todo"),
64
- column: undefined,
65
- } as unknown as Task;
66
-
67
- expect(filterGraphTasks([invalidTask])).toEqual([]);
68
- });
69
-
70
- it("preserves task object identity", () => {
71
- const taskA = createTask("FN-1", "todo");
72
- const taskB = createTask("FN-2", "in-review");
73
- const result = filterGraphTasks([taskA, taskB]);
74
-
75
- expect(result[0]).toBe(taskA);
76
- expect(result[1]).toBe(taskB);
77
- });
78
- });
@@ -1,95 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { clearPositions, loadPositions, mergePositions, savePositions } from "../utils/graphPositionStorage";
3
-
4
- function createStorage() {
5
- const store = new Map<string, string>();
6
- return {
7
- getItem: (key: string) => store.get(key) ?? null,
8
- setItem: (key: string, value: string) => {
9
- store.set(key, value);
10
- },
11
- removeItem: (key: string) => {
12
- store.delete(key);
13
- },
14
- };
15
- }
16
-
17
- describe("graphPositionStorage", () => {
18
- beforeEach(() => {
19
- vi.unstubAllGlobals();
20
- vi.stubGlobal("window", { localStorage: createStorage() });
21
- });
22
-
23
- it("loadPositions returns parsed positions from localStorage", () => {
24
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", JSON.stringify({ a: { x: 1, y: 2 } }));
25
- expect(loadPositions("p1")).toEqual({ a: { x: 1, y: 2 } });
26
- });
27
-
28
- it("loadPositions returns empty object when localStorage is empty", () => {
29
- expect(loadPositions("p1")).toEqual({});
30
- });
31
-
32
- it("loadPositions returns empty object for invalid json", () => {
33
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", "{oops");
34
- expect(loadPositions("p1")).toEqual({});
35
- });
36
-
37
- it("loadPositions skips entries with invalid position shape", () => {
38
- window.localStorage.setItem(
39
- "kb:p1:fusion-plugin-dependency-graph:positions",
40
- JSON.stringify({
41
- good: { x: 1, y: 2 },
42
- badX: { x: "1", y: 2 },
43
- badY: { x: 1, y: null },
44
- }),
45
- );
46
-
47
- expect(loadPositions("p1")).toEqual({ good: { x: 1, y: 2 } });
48
- });
49
-
50
- it("savePositions writes filtered positions json to scoped localStorage key", () => {
51
- savePositions({ a: { x: 1, y: 2 }, b: { x: 3, y: 4 } }, new Set(["a"]), "p1");
52
- expect(window.localStorage.getItem("kb:p1:fusion-plugin-dependency-graph:positions")).toBe(JSON.stringify({ a: { x: 1, y: 2 } }));
53
- });
54
-
55
- it("clearPositions removes scoped localStorage key", () => {
56
- window.localStorage.setItem("kb:p1:fusion-plugin-dependency-graph:positions", JSON.stringify({ a: { x: 1, y: 2 } }));
57
- clearPositions("p1");
58
- expect(window.localStorage.getItem("kb:p1:fusion-plugin-dependency-graph:positions")).toBeNull();
59
- });
60
-
61
- it("mergePositions prefers saved for overlap and keeps auto-layout for new tasks", () => {
62
- expect(
63
- mergePositions(
64
- { a: { x: 1, y: 1 }, b: { x: 2, y: 2 } },
65
- { a: { x: 10, y: 10 } },
66
- new Set(["a", "b"]),
67
- ),
68
- ).toEqual({ a: { x: 10, y: 10 }, b: { x: 2, y: 2 } });
69
- });
70
-
71
- it("mergePositions omits non-visible ids", () => {
72
- expect(
73
- mergePositions(
74
- { a: { x: 1, y: 1 }, hidden: { x: 9, y: 9 } },
75
- { hidden: { x: 10, y: 10 } },
76
- new Set(["a"]),
77
- ),
78
- ).toEqual({ a: { x: 1, y: 1 } });
79
- });
80
-
81
- it("mergePositions returns auto-layout unchanged when saved is empty", () => {
82
- expect(mergePositions({ a: { x: 1, y: 2 } }, {}, new Set(["a"]))).toEqual({ a: { x: 1, y: 2 } });
83
- });
84
-
85
- it("loadPositions returns empty object when localStorage.getItem is unavailable", () => {
86
- vi.stubGlobal("window", { localStorage: {} });
87
- expect(loadPositions("p1")).toEqual({});
88
- });
89
-
90
- it("savePositions and clearPositions are no-ops when localStorage methods are unavailable", () => {
91
- vi.stubGlobal("window", { localStorage: {} });
92
- expect(() => savePositions({ a: { x: 1, y: 2 } }, new Set(["a"]), "p1")).not.toThrow();
93
- expect(() => clearPositions("p1")).not.toThrow();
94
- });
95
- });