diff --git a/packages/dashboard/app/components/__tests__/TaskCard.badge-height.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.badge-height.test.tsx index 2ddf483b4..6c9e230b7 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.badge-height.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.badge-height.test.tsx @@ -29,6 +29,13 @@ vi.mock("../ProviderIcon", () => ({ vi.mock("../../hooks/useTaskDiffStats", () => ({ useTaskDiffStats: () => ({ stats: null, loading: false }), })); +vi.mock("../../hooks/useToast", () => ({ + useToast: () => ({ + addToast: vi.fn(), + removeToast: vi.fn(), + toasts: [], + }), +})); const noop = () => {}; diff --git a/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx index 0e008190b..26bd13d82 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx @@ -36,6 +36,13 @@ vi.mock("../PluginSlot", () => ({ vi.mock("../../hooks/useTaskDiffStats", () => ({ useTaskDiffStats: () => ({ stats: null, loading: false }), })); +vi.mock("../../hooks/useToast", () => ({ + useToast: () => ({ + addToast: vi.fn(), + removeToast: vi.fn(), + toasts: [], + }), +})); const noop = () => {}; diff --git a/packages/dashboard/app/components/__tests__/TaskCard.footer-wrap.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.footer-wrap.test.tsx index 2d778fcb8..bf1c4d546 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.footer-wrap.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.footer-wrap.test.tsx @@ -60,6 +60,13 @@ vi.mock("../../api", () => ({ vi.mock("../../hooks/useConfirm", () => ({ useConfirm: () => ({ confirm: vi.fn(async () => true) }), })); +vi.mock("../../hooks/useToast", () => ({ + useToast: () => ({ + addToast: vi.fn(), + removeToast: vi.fn(), + toasts: [], + }), +})); const noop = () => {}; diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.github-tracking-header.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.github-tracking-header.test.tsx index 5e7f15a5c..a2e2bd251 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.github-tracking-header.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.github-tracking-header.test.tsx @@ -35,14 +35,18 @@ describe("FN-4224 GitHub tracking header layout", () => { const css = loadAllAppCss(); + /* + FNXC:TaskDetailCSS 2026-07-08-13:00: + The github/gitlab tracking header rules were consolidated into a shared selector list (.detail-github-tracking-section .detail-source-header, .detail-gitlab-tracking-section .detail-source-header {…}), so the selector is no longer immediately followed by `{`. Allow the selector list (comma + sibling selector) between the tracked selector and the brace via [^{]* while still pinning the layout contract (flex-wrap/align-items/min-width). + */ expect(css).toMatch( - /\.detail-github-tracking-section\s+\.detail-source-header\s*\{[^}]*flex-wrap:\s*nowrap;[^}]*align-items:\s*center;[^}]*min-width:\s*0;/, + /\.detail-github-tracking-section\s+\.detail-source-header[^{]*\{[^}]*flex-wrap:\s*nowrap;[^}]*align-items:\s*center;[^}]*min-width:\s*0;/, ); expect(css).toMatch( - /\.detail-github-tracking-section\s+\.detail-source-summary\s*\{[^}]*flex:\s*1 1 auto;[^}]*flex-wrap:\s*nowrap;[^}]*min-width:\s*0;/, + /\.detail-github-tracking-section\s+\.detail-source-summary[^{]*\{[^}]*flex:\s*1 1 auto;[^}]*flex-wrap:\s*nowrap;[^}]*min-width:\s*0;/, ); expect(css).toMatch( - /@media[^{]*\(max-width:\s*768px\)[^{]*\{[\s\S]*?\.detail-github-tracking-section\s+\.detail-source-header\s*\{[^}]*flex-wrap:\s*nowrap;[^}]*min-width:\s*0;[^}]*\}[\s\S]*?\.detail-github-tracking-section\s+\.detail-source-summary\s*\{[^}]*flex:\s*1 1 auto;[^}]*flex-wrap:\s*nowrap;[^}]*min-width:\s*0;[^}]*\}/, + /@media[^{]*\(max-width:\s*768px\)[^{]*\{[\s\S]*?\.detail-github-tracking-section\s+\.detail-source-header[^{]*\{[^}]*flex-wrap:\s*nowrap;[^}]*min-width:\s*0;[^}]*\}[\s\S]*?\.detail-github-tracking-section\s+\.detail-source-summary[^{]*\{[^}]*flex:\s*1 1 auto;[^}]*flex-wrap:\s*nowrap;[^}]*min-width:\s*0;[^}]*\}/, ); }); }); diff --git a/packages/engine/src/__tests__/step-session-executor.test.ts b/packages/engine/src/__tests__/step-session-executor.test.ts index dfd754189..4fffc446f 100644 --- a/packages/engine/src/__tests__/step-session-executor.test.ts +++ b/packages/engine/src/__tests__/step-session-executor.test.ts @@ -1399,7 +1399,15 @@ describe("StepSessionExecutor", () => { agentStore: { saveRun } as any, } as any); - const results = await executor.executeAll(); + // FNXC:EngineTests 2026-07-09-06:00: + // executeAll retries the failing step 3× with sleep() delays between attempts. With + // useFakeTimers({ shouldAdvanceTime: true }) these sleeps advance REAL wall-clock time if + // the test awaits executeAll directly (was 22.6s, ballooning under CI load and busting the + // shard-2 watchdog). Fast-forward the retry sleeps via fake timers like the sibling retry + // tests below, so the loop completes in milliseconds. + const resultsPromise = executor.executeAll(); + await vi.advanceTimersByTimeAsync(60_000); + const results = await resultsPromise; expect(results).toEqual([{ stepIndex: 0, success: false, error: "boom", retries: 3, tokenUsage: undefined }]); const terminalRun = saveRun.mock.calls.at(-1)?.[0]; diff --git a/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx b/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx index ed2158a45..ec7345de9 100644 --- a/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx +++ b/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.drag.test.tsx @@ -3,6 +3,13 @@ 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". +*/ +vi.mock("@fusion/dashboard/app/hooks/useToast", () => ({ + useToast: () => ({ 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; diff --git a/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx b/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx index 7be0a0e2b..8e5982fa8 100644 --- a/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx +++ b/plugins/fusion-plugin-dependency-graph/src/__tests__/GraphTaskNode.test.tsx @@ -3,6 +3,13 @@ import type { Task } from "@fusion/core"; import { afterEach, describe, expect, it, vi } from "vitest"; import { TaskCard } from "@fusion/dashboard/app/components/TaskCard"; import { GraphTaskNode } from "../GraphTaskNode"; +/* +FNXC:DependencyGraphTests 2026-07-08-13:10: +GraphTaskNode renders the REAL TaskCard (to verify prop pass-through), and TaskCard now renders RuntimeFallbackBadge which calls the dashboard's useToast() hook. This file has no ToastProvider, so mock useToast the same way the dashboard's own TaskCard.test.tsx does to avoid "useToast must be used within ToastProvider". +*/ +vi.mock("@fusion/dashboard/app/hooks/useToast", () => ({ + useToast: () => ({ addToast: vi.fn(), removeToast: vi.fn(), toasts: [] }), +})); function createTask(overrides: Partial = {}): Task { return {