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 2ddf483b48..6c9e230b78 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 0e008190b2..26bd13d82a 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 2d778fcb8b..bf1c4d546a 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 5e7f15a5c5..a2e2bd251b 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 dfd754189d..4fffc446f4 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 ed2158a456..ec7345de98 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 7be0a0e2bf..8e5982fa8c 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 {