Files
fusion/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx
gsxdsm 29d94e0fa3 fix: drop dependency-graph plugin's use of removed stuck-task tagging
The dependency-graph plugin imported the deleted taskStuck util through its
dashboard interop shim, breaking the dashboard vite build. Remove the isStuck
gate from graph nodes, the taskStuckTimeoutMs prop threading, the taskStuck
module declaration from dashboard-interop.d.ts, and the stalled-card-as-stuck
test coverage. Covered by the existing remove-stuck-task-tagging changeset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:58:58 -07:00

109 lines
4.5 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import type React from "react";
import type { Task } from "@fusion/core";
import { GraphTaskNode } from "../GraphTaskNode";
/*
FNXC:DependencyGraphTests 2026-07-08-13:10:
GraphTaskNode renders the REAL TaskCard; TaskCard's RuntimeFallbackBadge calls the dashboard's useToast() hook, and this file has no ToastProvider. Mock useToast (same as the dashboard's own TaskCard.test.tsx) to avoid "useToast must be used within ToastProvider".
*/
/*
FNXC:DependencyGraphTests 2026-07-18-04:35:
RuntimeFallbackBadge uses useOptionalToast (soft-fail path). Mock both exports so
GraphTaskNode suites stay provider-free.
*/
vi.mock("@fusion/dashboard/app/hooks/useToast", () => ({
useToast: () => ({ addToast: vi.fn(), removeToast: vi.fn(), toasts: [] }),
useOptionalToast: () => ({ addToast: vi.fn(), removeToast: vi.fn(), toasts: [] }),
}));
function task(id = "FN-1"): Task {
return { id, description: id, column: "todo", dependencies: [], steps: [], currentStep: 0, log: [] } as Task;
}
function props(overrides: Partial<React.ComponentProps<typeof GraphTaskNode>> = {}): React.ComponentProps<typeof GraphTaskNode> {
return {
task: task(),
projectId: "p1",
position: { x: 0, y: 0 },
scale: 1,
isSelected: false,
isHighlighted: false,
isDimmed: false,
onNodePositionChange: vi.fn(),
onNodeDragStateChange: vi.fn(),
onOpenDetail: vi.fn(),
addToast: vi.fn(),
onUpdateTask: vi.fn(),
onArchiveTask: vi.fn(),
onUnarchiveTask: vi.fn(),
onDeleteTask: vi.fn(),
onRetryTask: vi.fn(),
onOpenDetailWithTab: vi.fn(),
onMoveTask: vi.fn(),
onOpenMission: vi.fn(),
lastFetchTimeMs: Date.now(),
workflowStepNameLookup: new Map<string, string>(),
...overrides,
};
}
afterEach(() => {
cleanup();
});
describe("GraphTaskNode drag", () => {
it("does not open detail after drag threshold is exceeded", () => {
const onOpenDetail = vi.fn();
render(<GraphTaskNode {...props({ onOpenDetail, isSelected: true })} />);
const node = screen.getByTestId("graph-task-node-FN-1");
fireEvent.pointerDown(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
fireEvent.pointerMove(node, { pointerId: 1, clientX: 25, clientY: 25, isPrimary: true });
fireEvent.pointerUp(node, { pointerId: 1, clientX: 25, clientY: 25, isPrimary: true });
fireEvent.click(node);
expect(onOpenDetail).not.toHaveBeenCalled();
});
it("applies dragging class only after threshold move", () => {
const onNodePositionChange = vi.fn();
render(<GraphTaskNode {...props({ onNodePositionChange, isSelected: true })} />);
const node = screen.getByTestId("graph-task-node-FN-1");
fireEvent.pointerDown(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
fireEvent.pointerMove(node, { pointerId: 1, clientX: 12, clientY: 12, isPrimary: true });
expect(node.className).not.toContain("graph-node--dragging");
fireEvent.pointerMove(node, { pointerId: 1, clientX: 20, clientY: 20, isPrimary: true });
expect(node.className).toContain("graph-node--dragging");
expect(onNodePositionChange).toHaveBeenCalled();
fireEvent.pointerUp(node, { pointerId: 1, clientX: 20, clientY: 20, isPrimary: true });
expect(node.className).not.toContain("graph-node--dragging");
});
it("composes highlight and dragging classes", () => {
render(<GraphTaskNode {...props({ isSelected: true, isHighlighted: true })} />);
const node = screen.getByTestId("graph-task-node-FN-1");
fireEvent.pointerDown(node, { pointerId: 1, clientX: 0, clientY: 0, isPrimary: true });
fireEvent.pointerMove(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
expect(node.className).toContain("graph-task-node--highlighted");
expect(node.className).toContain("graph-node--dragging");
});
it("does not drag when node is not selected", () => {
const onNodePositionChange = vi.fn();
render(<GraphTaskNode {...props({ onNodePositionChange, isSelected: false })} />);
const node = screen.getByTestId("graph-task-node-FN-1");
fireEvent.pointerDown(node, { pointerId: 1, clientX: 10, clientY: 10, isPrimary: true });
fireEvent.pointerMove(node, { pointerId: 1, clientX: 25, clientY: 25, isPrimary: true });
expect(node.className).not.toContain("graph-node--dragging");
expect(node.className).not.toContain("graph-node--draggable");
expect(onNodePositionChange).not.toHaveBeenCalled();
});
});