Files
fusion/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx
gsxdsm 1afce9532a fix: green full-suite after product and CI PG drift (#2285)
## Summary
Main Full Suite was red again after release/desktop workflow drift,
engine mock gaps, mission landed-SHA gating, and compound-engineering PG
admin auth on GHA (`USER=runner`).

## Fixes
| Area | Failure | Fix |
|------|---------|-----|
| desktop `release-workflow` | expected old `find artifacts -type f` |
assert pruned collect + `release-files/*` |
| `step-session-executor` | missing
`resolveExecutorFallbackThinkingLevel` | mock export |
| tool-availability tests | empty tools (cascade from above) | fixed by
mock |
| `skill-resolver` | TDZ on `mockFiles` during import | `vi.hoisted`
filesystem state |
| `merge-error-recovery` | enqueue no-op when not started | set
`started=true` |
| mission behavioral posture | `blocked` (no landed SHA / git probe) |
`mergeDetails.commitSha` + staleness stub |
| GraphTaskNode | missing `useOptionalToast` | mock both toast exports |
| CE `pipeline-store.pg` | psql as `runner` | admin via
`FUSION_PG_TEST_URL_BASE` |

## Test plan
- [x] step-session-executor, skill-resolver, merge-error-recovery,
mission-validator-behavioral-posture (203)
- [x] release-workflow (10)
- [x] `pnpm --filter @fusion/engine test:core` (294)
- [ ] Full Suite (non-blocking) after merge

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Improved desktop release artifact collection by pruning nested
`runtime` and `migrations` directories and consistently staging release
uploads via a dedicated `release-files` mapping.

- **Tests**
- Enhanced engine merge error-recovery coverage and mission validator
behavioral posture setup.
- Improved test reliability by synchronizing mocked filesystem state,
executor fallbacks, and toast hook variants.
- Updated Postgres test harness/admin commands to use a configurable
base URL; refined related Windows changeset description.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-17 23:22:04 -07:00

110 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(),
taskStuckTimeoutMs: 1000,
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();
});
});