Files
fusion/packages/dashboard/app/__tests__/css-hygiene-scan.test.ts
gsxdsm 7e903494ec FN-6186: tokenized dashboard accent text colors
Add a dashboard CSS hygiene scan and align accent text colors with the CTA text token.

- add a dashboard CSS hygiene regression scan for recent component styles
- assert tokenized focus styles, mobile breakpoints, and no raw hex or rgba colors in scanned CSS
- replace hardcoded white text with var(--cta-text) in background task, pull request, task card, and task field accent surfaces

Files changed:
 .../app/__tests__/css-hygiene-scan.test.ts         | 191 +++++++++++++++++++++
 .../app/components/BackgroundTasksIndicator.css    |   2 +-
 .../dashboard/app/components/PullRequestView.css   |   2 +-
 packages/dashboard/app/components/TaskCard.css     |   2 +-
 .../dashboard/app/components/TaskFieldsSection.css |   2 +-
 5 files changed, 195 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-6186

Fusion-Task-Lineage: c579bcda-01f9-4495-bf59-78d1dfef816c
2026-06-10 06:02:12 -07:00

192 lines
6.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { join, resolve } from "node:path";
const componentsDir = resolve(__dirname, "../components");
const recentlyLandedResponsiveFiles = [
"PluginManager.css",
"WorkflowNodeEditor.css",
"TaskDetailModal.css",
"WorkflowSelector.css",
"WorkflowSettingsPanel.css",
];
const recentlyLandedHygieneFiles = [
...recentlyLandedResponsiveFiles,
"MobileWorkflowGraphView.css",
"BackgroundTasksIndicator.css",
"PullRequestView.css",
"TaskCard.css",
"TaskFieldsSection.css",
];
function readComponentCss(name: string): string {
return readFileSync(join(componentsDir, name), "utf-8");
}
function linesWith(source: string, predicate: (line: string) => boolean): string[] {
return source
.split("\n")
.map((line, index) => ({ line, index }))
.filter(({ line }) => predicate(line))
.map(({ line, index }) => `${index + 1}: ${line.trim()}`);
}
function extractSelectorBlocks(source: string, selector: string): string[] {
const blocks: string[] = [];
let searchFrom = 0;
while (searchFrom < source.length) {
const start = source.indexOf(selector, searchFrom);
if (start === -1) break;
const selectorStart = source.lastIndexOf("}", start) + 1;
const open = source.indexOf("{", start);
if (open === -1) break;
const selectorList = source.slice(selectorStart, open);
if (!selectorList.split(",").some((entry) => entry.trim() === selector)) {
searchFrom = start + selector.length;
continue;
}
let depth = 1;
let index = open + 1;
while (index < source.length && depth > 0) {
if (source[index] === "{") depth += 1;
else if (source[index] === "}") depth -= 1;
index += 1;
}
blocks.push(source.slice(open + 1, index - 1));
searchFrom = index;
}
return blocks;
}
describe("component CSS hygiene scan regressions", () => {
it("keeps fixed text-on-accent CSS on --cta-text instead of hardcoded white", () => {
const fixedSelectors: Array<[string, string]> = [
["BackgroundTasksIndicator.css", ".background-tasks-indicator__pill--attention"],
["PullRequestView.css", ".pr-action--merge-confirm"],
["TaskCard.css", ".card-field-badge--boolean"],
["TaskFieldsSection.css", ".task-field-chip.is-active"],
];
const findings = fixedSelectors.flatMap(([file, selector]) => {
const blocks = extractSelectorBlocks(readComponentCss(file), selector);
const issues: string[] = [];
if (!blocks.some((block) => block.includes("color: var(--cta-text);"))) {
issues.push(`${file}:${selector}: missing color: var(--cta-text)`);
}
if (blocks.some((block) => /color:\s*#fff\b/i.test(block))) {
issues.push(`${file}:${selector}: uses hardcoded #fff`);
}
return issues;
});
expect(findings).toEqual([]);
});
it("does not use hardcoded hex colors outside token fallback patterns in scanned recent CSS", () => {
const hexPattern = /#[0-9a-fA-F]{3,8}\b/;
const findings = recentlyLandedHygieneFiles.flatMap((file) =>
linesWith(
readComponentCss(file),
(line) => hexPattern.test(line) && !line.includes("var(--")
).map((line) => `${file}:${line}`)
);
expect(findings).toEqual([]);
});
it("does not reintroduce raw rgba() calls in scanned recent CSS", () => {
const findings = recentlyLandedHygieneFiles.flatMap((file) =>
linesWith(readComponentCss(file), (line) => line.includes("rgba(")).map(
(line) => `${file}:${line}`
)
);
expect(findings).toEqual([]);
});
it("keeps mobile breakpoints in recently landed responsive components", () => {
const findings = recentlyLandedResponsiveFiles.filter(
(file) => !readComponentCss(file).includes("@media (max-width: 768px)")
);
expect(findings).toEqual([]);
});
it("keeps focus-visible coverage for recently landed interactive surfaces", () => {
const expectedFocusSelectors: Record<string, string[]> = {
"PluginManager.css": [
".plugin-registry-search-input:focus-visible",
".plugin-registry-action:focus-visible",
".plugin-registry-retry:focus-visible",
],
"WorkflowNodeEditor.css": [
".wf-layout-toggle:focus-visible",
".wf-template-option:focus-visible",
".wf-ai-prompt:focus-visible",
".wf-mobile-tab:focus-visible",
".wf-mobile-add-option:focus-visible",
".wf-mobile-template-option:focus-visible",
],
"TaskDetailModal.css": [
".detail-summarize-title-btn:focus-visible",
".detail-provenance-link:focus-visible",
".detail-priority-select:focus-visible",
".detail-source-toggle:focus-visible",
".changed-files-back-button:focus-visible",
".modal-edit-btn:focus-visible",
".detail-tab:focus-visible",
],
"MobileWorkflowGraphView.css": [
".mobile-wf-node-main:focus-visible",
".mobile-wf-node-expand:focus-visible",
".mobile-wf-edge-chip:focus-visible",
],
};
const missing = Object.entries(expectedFocusSelectors).flatMap(([file, selectors]) => {
const css = readComponentCss(file);
return selectors
.filter((selector) => !css.includes(selector))
.map((selector) => `${file}: missing ${selector}`);
});
expect(missing).toEqual([]);
});
it("uses tokenized focus-visible styling in scanned recent CSS", () => {
const focusRulePattern = /([^{}]*:focus-visible[^{}]*)\{([^{}]*)\}/g;
const findings = recentlyLandedHygieneFiles.flatMap((file) => {
const css = readComponentCss(file);
const issues: string[] = [];
for (const match of css.matchAll(focusRulePattern)) {
const selector = match[1].trim().replace(/\s+/g, " ");
const block = match[2];
const hasFocusStyling = /(?:outline|box-shadow)\s*:/.test(block);
const hasRawColor = /(?:#[0-9a-fA-F]{3,8}\b|rgba?\()/.test(block);
if (hasRawColor) {
issues.push(`${file}:${selector}: uses raw focus color`);
}
if (hasFocusStyling && !block.includes("var(--")) {
issues.push(`${file}:${selector}: focus styling is not tokenized`);
}
}
return issues;
});
expect(findings).toEqual([]);
});
});