## 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 -->
110 lines
4.5 KiB
TypeScript
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();
|
|
});
|
|
});
|