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
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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<string>} 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;
|
||||
}
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user