From 175aab15ac84472fe85a80fa9164fbc0b3c7c0d2 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 14 Jun 2026 08:39:17 -0700 Subject: [PATCH] FN-6441: rescue and quarantine dashboard component tests Rescue passing dashboard component tests and move still-failing orphaned tests under the quarantine ratchet. - Remove the FN-6441 dashboard component batch from the curated skip-list. - Keep passing PlanningModeModal, TaskReviewTab, and TerminalModal coverage documented in app backfill. - Register remaining failing dashboard component/CSS tests in the dated quarantine ledger and Vitest excludes. - Teach the dashboard curated inventory guard to accept dated quarantine entries without returning them to the skip-list. Files changed: .../PlanningModeModal.ui-interactions.test.tsx | 9 ++- .../components/__tests__/TaskReviewTab.test.tsx | 4 ++ .../components/__tests__/TerminalModal.test.tsx | 4 ++ packages/dashboard/vitest.config.ts | 14 +++++ scripts/__tests__/check-test-inventory.test.mjs | 32 +++++++++++ scripts/check-test-inventory.mjs | 67 +++++++++++++++++----- scripts/lib/dashboard-curated-skiplist.json | 56 ------------------ scripts/lib/test-quarantine.json | 58 ++++++++++++++++++- 8 files changed, 172 insertions(+), 72 deletions(-) Fusion-Task-Id: FN-6441 Fusion-Task-Lineage: 19065d1f-31eb-45fc-a6ce-083773b094e4 --- ...PlanningModeModal.ui-interactions.test.tsx | 9 ++- .../__tests__/TaskReviewTab.test.tsx | 4 ++ .../__tests__/TerminalModal.test.tsx | 4 ++ packages/dashboard/vitest.config.ts | 14 ++++ .../__tests__/check-test-inventory.test.mjs | 32 +++++++++ scripts/check-test-inventory.mjs | 67 +++++++++++++++---- scripts/lib/dashboard-curated-skiplist.json | 56 ---------------- scripts/lib/test-quarantine.json | 58 +++++++++++++++- 8 files changed, 172 insertions(+), 72 deletions(-) diff --git a/packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx b/packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx index ecd904ed31..d1f70762b2 100644 --- a/packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx +++ b/packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx @@ -1,7 +1,12 @@ +/* +FNXC:DashboardTests 2026-06-14-08:31: +FN-6441 rescued this orphaned component test after standalone dashboard-app execution passed without assertion, timeout, or source-code changes. Keep the planning modal UI-interaction coverage in app backfill so question flow, summary, and breakdown interactions remain executed after leaving the skip-list. + +FNXC:DashboardTests 2026-06-14-08:32: +PlanningModeModal calls useToast(), which throws without a ToastProvider. These tests render it bare, so the hook stays mocked in the same style as PlanningModeModal.autosize.test.tsx instead of introducing broad provider wiring during skip-list rescue. +*/ import { describe, it, expect, vi, beforeEach } from "vitest"; -// PlanningModeModal calls useToast(), which throws without a ToastProvider. -// These tests render it bare, so mock the hook (mirrors PlanningModeModal.autosize.test.tsx). vi.mock("../../hooks/useToast", () => ({ useToast: () => ({ addToast: vi.fn(), diff --git a/packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx b/packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx index b23fd95f1d..748ed9d98b 100644 --- a/packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx @@ -1,3 +1,7 @@ +/* +FNXC:DashboardTests 2026-06-14-08:31: +FN-6441 rescued this orphaned component test after standalone dashboard-app execution passed without assertion, timeout, or source-code changes. Keep it registered through the app backfill lane so task-review UI regressions cannot silently fall out of quality coverage again. +*/ import { describe, it, expect, vi, beforeEach } from "vitest"; import { act, render as rtlRender, screen, fireEvent, waitFor } from "@testing-library/react"; import { TaskReviewTab } from "../TaskReviewTab"; diff --git a/packages/dashboard/app/components/__tests__/TerminalModal.test.tsx b/packages/dashboard/app/components/__tests__/TerminalModal.test.tsx index 5bddb79422..c20177f556 100644 --- a/packages/dashboard/app/components/__tests__/TerminalModal.test.tsx +++ b/packages/dashboard/app/components/__tests__/TerminalModal.test.tsx @@ -1,3 +1,7 @@ +/* +FNXC:DashboardTests 2026-06-14-08:31: +FN-6441 rescued this orphaned component test after standalone dashboard-app execution passed without assertion, timeout, or source-code changes. Keep the terminal modal coverage in app backfill because keyboard, session, and mobile terminal regressions are user-facing and should not remain skip-listed. +*/ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { render, screen, fireEvent, waitFor, act } from "@testing-library/react"; import { TerminalModal, _resetInitialViewportHeight, ctrlChar, altChar } from "../TerminalModal"; diff --git a/packages/dashboard/vitest.config.ts b/packages/dashboard/vitest.config.ts index 2213e45934..19ec0efe48 100644 --- a/packages/dashboard/vitest.config.ts +++ b/packages/dashboard/vitest.config.ts @@ -241,7 +241,21 @@ const quarantinedDashboardTests: string[] = [ FNXC:DashboardTests 2026-06-14-02:24: FN-6433 rescued the dashboard quarantine batch after unquarantined app-backfill and API-quality runs passed with no assertion or timeout changes. Keep this array empty unless a future dashboard quarantine is mirrored in scripts/lib/test-quarantine.json in the same commit. + + FNXC:DashboardTests 2026-06-14-08:28: + FN-6441 removed the dashboard component orphan batch from the curated skip-list so passing rescues run in backfill and still-failing tests are excluded only through the dated quarantine ledger. Keep these one-line excludes mirrored with scripts/lib/test-quarantine.json until each file is rescued or deleted under the deletion ratchet. */ + "app/components/__tests__/ChatView.regular-composer-no-right-line.test.tsx", + "app/components/__tests__/MissionManager.test.tsx", + "app/components/__tests__/ModalReentry.test.tsx", + "app/components/__tests__/ModelSelectorTab.test.tsx", + "app/components/__tests__/NewAgentDialog.test.tsx", + "app/components/__tests__/OAuthReloginBanner.test.tsx", + "app/components/__tests__/PlanningModeModal.favorites.test.tsx", + "app/components/__tests__/PlanningModeModal.questions.test.tsx", + "app/components/__tests__/PlanningModeModal.swipe-back.test.tsx", + "app/components/__tests__/SkillsView.css.test.ts", + "app/components/__tests__/mobile-css.test.tsx", ]; const qualityApiTests = [ diff --git a/scripts/__tests__/check-test-inventory.test.mjs b/scripts/__tests__/check-test-inventory.test.mjs index 2f9d78983d..e907073f00 100644 --- a/scripts/__tests__/check-test-inventory.test.mjs +++ b/scripts/__tests__/check-test-inventory.test.mjs @@ -119,6 +119,38 @@ test("curated guard: a skip-listed file does not trip the unregistered check", ( assert.equal(ok, true); }); +test("curated guard: a quarantined file is registered without returning to the skip-list", () => { + const { ok, errors } = validateDashboardCurated({ + includedFiles: new Set(), + allTestFiles: ["packages/dashboard/app/quarantined.test.ts"], + skipList: [], + quarantineList: [ + { + file: "packages/dashboard/app/quarantined.test.ts", + reason: "quarantined under deletion ratchet FN-4", + quarantinedAt: "2026-06-14", + }, + ], + }); + assert.equal(ok, true, errors.join("; ")); +}); + +test("curated guard: rejects quarantine entries without a ratchet date", () => { + const { ok, errors } = validateDashboardCurated({ + includedFiles: new Set(), + allTestFiles: ["packages/dashboard/app/quarantined.test.ts"], + skipList: [], + quarantineList: [ + { + file: "packages/dashboard/app/quarantined.test.ts", + reason: "quarantined under deletion ratchet FN-4", + }, + ], + }); + assert.equal(ok, false); + assert.ok(errors.some((e) => e.includes("quarantinedAt"))); +}); + // --------------------------------------------------------------------------- // end-to-end curated guard against a synthetic temp fixture dir, exercising // the real file walk + skip-list validation in one pass (no real repo file). diff --git a/scripts/check-test-inventory.mjs b/scripts/check-test-inventory.mjs index 7deab3e825..746befdee1 100644 --- a/scripts/check-test-inventory.mjs +++ b/scripts/check-test-inventory.mjs @@ -27,10 +27,10 @@ * --dashboard-curated * Assert that every `*.test.{ts,tsx}` file under packages/dashboard/app * and packages/dashboard/src is included by at least one *executed* - * dashboard quality project, OR listed on the explicit skip-list with a - * non-empty reason. Fails (exit 1) otherwise. This closes the curated-gate - * coverage hole: a new dashboard test file that nobody registered trips - * this guard. + * dashboard quality project, OR listed on the explicit skip-list / dated + * quarantine ledger with a non-empty reason. Fails (exit 1) otherwise. + * This closes the curated-gate coverage hole: a new dashboard test file + * that nobody registered trips this guard. * * The capture spec (which packages/projects to enumerate) is data, not code: * it lives in scripts/lib/test-inventory-spec.json so the CI shard planner and @@ -48,9 +48,10 @@ const REPO_ROOT = resolve(__dirname, ".."); const DEFAULT_SPEC_PATH = join(__dirname, "lib", "test-inventory-spec.json"); const DASHBOARD_SKIPLIST_PATH = join(__dirname, "lib", "dashboard-curated-skiplist.json"); +const TEST_QUARANTINE_PATH = join(__dirname, "lib", "test-quarantine.json"); // --------------------------------------------------------------------------- -// Spec + skip-list loading +// Spec + skip-list / quarantine loading // --------------------------------------------------------------------------- function loadSpec(specPathOverride) { @@ -73,6 +74,17 @@ function loadSkipList(skipListPathOverride) { return { skipListPath, entries: raw.entries }; } +function loadQuarantineList(quarantinePathOverride) { + const quarantinePath = + quarantinePathOverride || process.env.FUSION_TEST_QUARANTINE || TEST_QUARANTINE_PATH; + if (!existsSync(quarantinePath)) return { quarantinePath, entries: [] }; + const raw = JSON.parse(readFileSync(quarantinePath, "utf8")); + if (!Array.isArray(raw.entries)) { + throw new Error(`quarantine ledger ${quarantinePath} must have an "entries" array`); + } + return { quarantinePath, entries: raw.entries }; +} + // --------------------------------------------------------------------------- // vitest list invocation // --------------------------------------------------------------------------- @@ -201,9 +213,10 @@ function walkTestFiles(rootDir, repoRoot) { * @param {Set} opts.includedFiles repo-relative files executed by quality projects * @param {string[]} opts.allTestFiles repo-relative dashboard app/src test files * @param {Array<{file:string,reason:string}>} opts.skipList + * @param {Array<{file:string,reason:string,quarantinedAt?:string}>} [opts.quarantineList] * @returns {{ ok: boolean, errors: string[] }} */ -export function validateDashboardCurated({ includedFiles, allTestFiles, skipList }) { +export function validateDashboardCurated({ includedFiles, allTestFiles, skipList, quarantineList = [] }) { const errors = []; const skipByFile = new Map(); for (const entry of skipList) { @@ -217,18 +230,36 @@ export function validateDashboardCurated({ includedFiles, allTestFiles, skipList skipByFile.set(entry.file, entry); } - // A skip-listed file that is actually covered is allowed but noisy; we don't - // error on it (it keeps the guard green while a flaky file is being fixed). + const quarantineByFile = new Map(); + for (const entry of quarantineList) { + if (!entry || typeof entry.file !== "string" || entry.file.length === 0) { + errors.push(`quarantine entry missing "file": ${JSON.stringify(entry)}`); + continue; + } + if (typeof entry.reason !== "string" || entry.reason.trim().length === 0) { + errors.push(`quarantine entry for ${entry.file} has an empty "reason"`); + } + if (typeof entry.quarantinedAt !== "string" || entry.quarantinedAt.trim().length === 0) { + errors.push(`quarantine entry for ${entry.file} has an empty "quarantinedAt"`); + } + quarantineByFile.set(entry.file, entry); + } + + /* + FNXC:DashboardTesting 2026-06-14-08:42: + A quarantined dashboard test is intentionally not executed by quality projects, but it must not be re-added to the curated skip-list. Treat the dated quarantine ledger as a second explicit registration source so rescued tests can leave the skip-list while failing tests remain governed by the deletion ratchet. + */ for (const file of allTestFiles) { if (includedFiles.has(file)) continue; if (skipByFile.has(file)) continue; + if (quarantineByFile.has(file)) continue; errors.push( - `dashboard test file is not executed by any quality project and is not skip-listed: ${file}`, + `dashboard test file is not executed by any quality project and is not skip-listed or quarantined: ${file}`, ); } - // Stale skip-list entries pointing at deleted files are a soft error so the - // list doesn't rot, but only when the file genuinely no longer exists. + // Stale explicit registrations pointing at deleted files are a soft error so + // the lists don't rot, but only when the file genuinely no longer exists. for (const entry of skipList) { if (!entry || typeof entry.file !== "string") continue; if (!allTestFiles.includes(entry.file) && !includedFiles.has(entry.file)) { @@ -238,6 +269,15 @@ export function validateDashboardCurated({ includedFiles, allTestFiles, skipList } } } + for (const entry of quarantineList) { + if (!entry || typeof entry.file !== "string") continue; + if (!allTestFiles.includes(entry.file) && !includedFiles.has(entry.file)) { + const abs = join(REPO_ROOT, entry.file); + if (!existsSync(abs)) { + errors.push(`quarantine ledger references a non-existent file: ${entry.file}`); + } + } + } return { ok: errors.length === 0, errors }; } @@ -321,7 +361,8 @@ async function main() { ].sort(); const includedFiles = listExecutedDashboardQualityFiles(); const { entries: skipList } = loadSkipList(); - const { ok, errors } = validateDashboardCurated({ includedFiles, allTestFiles, skipList }); + const { entries: quarantineList } = loadQuarantineList(); + const { ok, errors } = validateDashboardCurated({ includedFiles, allTestFiles, skipList, quarantineList }); if (!ok) { console.error(`✗ dashboard curated-gate guard failed (${errors.length} issue(s)):`); for (const e of errors) console.error(` - ${e}`); @@ -330,7 +371,7 @@ async function main() { console.log( `✓ dashboard curated gate complete: ${allTestFiles.length} test files, ${ includedFiles.size - } executed, ${skipList.length} skip-listed`, + } executed, ${skipList.length} skip-listed, ${quarantineList.length} quarantined`, ); return; } diff --git a/scripts/lib/dashboard-curated-skiplist.json b/scripts/lib/dashboard-curated-skiplist.json index cc30f671c8..fa441e22e2 100644 --- a/scripts/lib/dashboard-curated-skiplist.json +++ b/scripts/lib/dashboard-curated-skiplist.json @@ -5,62 +5,6 @@ "file": "packages/dashboard/app/__tests__/build-output.test.ts", "reason": "asserts the built bundle; runs standalone via `pnpm --filter @fusion/dashboard test:build` (needs a prior vite build), not in the unit gate" }, - { - "file": "packages/dashboard/app/components/__tests__/ChatView.regular-composer-no-right-line.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/MissionManager.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/ModalReentry.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/ModelSelectorTab.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/NewAgentDialog.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/OAuthReloginBanner.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/PlanningModeModal.favorites.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/PlanningModeModal.questions.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/PlanningModeModal.swipe-back.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/PlanningModeModal.ui-interactions.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/SkillsView.css.test.ts", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/TerminalModal.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, - { - "file": "packages/dashboard/app/components/__tests__/mobile-css.test.tsx", - "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6441)" - }, { "file": "packages/dashboard/app/hooks/__tests__/quickChatLastSessionStorage.test.ts", "reason": "pre-existing failure (orphaned from curated gate, never previously executed in CI; tracked as FN-6442)" diff --git a/scripts/lib/test-quarantine.json b/scripts/lib/test-quarantine.json index 2439bcba67..d788a1cf45 100644 --- a/scripts/lib/test-quarantine.json +++ b/scripts/lib/test-quarantine.json @@ -1,4 +1,60 @@ { "$comment": "Flaky-test quarantine ledger (deletion ratchet \u2014 see AGENTS.md 'Flaky tests: quarantine on sight' and docs/testing.md 'Quarantine ledger and the deletion ratchet'). A test observed failing without a corresponding real bug is quarantined ON SIGHT: add an entry here AND a matching one-line `exclude` entry in that package's vitest config, in the same commit. Every entry needs a non-empty `reason` (link the failing run) and a `quarantinedAt` date \u2014 the entry expires 14 days later, at which point the test file is DELETED unless someone rescues it with evidence it catches real regressions plus a root-cause fix (never appeasement). There is deliberately no loader module and no automation around this file: it is a dated record, the vitest config exclude is the mechanism, and the sweep is policy executed by whoever touches the suite.", - "entries": [] + "entries": [ + { + "file": "packages/dashboard/app/components/__tests__/ChatView.regular-composer-no-right-line.test.tsx", + "reason": "FN-6441: orphaned dashboard component test fails standalone; ChatView emits act warnings and regular-composer right-line invariant assertion fails. Quarantined instead of widening waits or weakening assertions.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/MissionManager.test.tsx", + "reason": "FN-6441: orphaned dashboard component test fails standalone with stale mission hierarchy/progress/status expectations while most cases pass. Quarantined for rescue/delete ratchet instead of assertion appeasement.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/ModalReentry.test.tsx", + "reason": "FN-6441: orphaned dashboard component test fails standalone because PlanningModal cases render outside ToastProvider. Quarantined for harness rescue instead of product/source changes.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/ModelSelectorTab.test.tsx", + "reason": "FN-6441: orphaned dashboard component test fails standalone across model selector cases because expected Executor Model labels/options are no longer rendered by the current component contract. Quarantined for harness/expectation rescue.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/NewAgentDialog.test.tsx", + "reason": "FN-6441: orphaned dashboard component test fails standalone across broad dialog flows with duplicate fetch/update calls and stale favorite labels. Quarantined for focused rescue rather than timeout/assertion appeasement.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/OAuthReloginBanner.test.tsx", + "reason": "FN-6441: orphaned dashboard component test times out every case under current async/polling behavior. Quarantined instead of increasing test timeouts.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/PlanningModeModal.favorites.test.tsx", + "reason": "FN-6441: orphaned dashboard component test fails standalone because PlanningModeModal favorite/keyboard cases render outside ToastProvider. Quarantined for harness rescue.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/PlanningModeModal.questions.test.tsx", + "reason": "FN-6441: orphaned dashboard component test fails standalone because PlanningModeModal question/summary cases render outside ToastProvider. Quarantined for harness rescue.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/PlanningModeModal.swipe-back.test.tsx", + "reason": "FN-6441: orphaned dashboard component test fails standalone because PlanningModeModal mobile navigation cases render outside ToastProvider. Quarantined for harness rescue.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/SkillsView.css.test.ts", + "reason": "FN-6441: orphaned dashboard CSS guardrail fails standalone because runtime-card toggle positioning expectation no longer matches current stylesheet. Quarantined for rescue/delete review without weakening assertion.", + "quarantinedAt": "2026-06-14" + }, + { + "file": "packages/dashboard/app/components/__tests__/mobile-css.test.tsx", + "reason": "FN-6441: orphaned dashboard CSS foundation test fails standalone on stale workflow-step-manager modal and breakpoint assertions. Quarantined for rescue/delete review without broad CSS changes.", + "quarantinedAt": "2026-06-14" + } + ] }