101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
import { readdirSync, readFileSync } from "node:fs";
|
|
import { join, relative } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const ROOTS = ["src/app/admin", "src/components/admin"];
|
|
const GRAPHICAL_ALLOWLIST = [
|
|
"src/app/admin/favicon/favicon-generator.tsx",
|
|
"src/app/admin/import/clone/import-clone-client.tsx",
|
|
"src/app/admin/import/furni/import-furni-client.tsx",
|
|
"src/components/admin/catalog/items-shop-preview.tsx",
|
|
];
|
|
|
|
const DATA_COLOR_ALLOWLIST = [
|
|
"src/app/admin/alerts/page.tsx",
|
|
"src/app/admin/banners/banners-manager.tsx",
|
|
"src/app/admin/events/events-table.tsx",
|
|
"src/app/admin/events/types/event-types-manager.tsx",
|
|
"src/app/admin/favicon/favicon-generator.tsx",
|
|
"src/app/admin/help-questions/new/page.tsx",
|
|
"src/app/admin/help-questions/[id]/page.tsx",
|
|
"src/app/admin/prefixes/prefixes-client.tsx",
|
|
"src/app/admin/tags/page.tsx",
|
|
"src/app/admin/teams/page.tsx",
|
|
"src/app/admin/theme/page.tsx",
|
|
];
|
|
|
|
const PUBLIC_STRUCTURAL_TOKEN =
|
|
/var\(--(?:color-(?:background|surface|dropdown|navbar|navbar-text|text|text-muted|primary|primary-hover)|border-subtle|border-color)\)/g;
|
|
const HARDCODED_UI_PALETTE =
|
|
/(?:text|bg|border|ring|from|to|via)-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:[1-9]00|50)(?:\/\d+)?/g;
|
|
|
|
function sourceFiles(directory: string): string[] {
|
|
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
const path = join(directory, entry.name);
|
|
return entry.isDirectory()
|
|
? sourceFiles(path)
|
|
: /\.(?:ts|tsx)$/.test(entry.name)
|
|
? [path]
|
|
: [];
|
|
});
|
|
}
|
|
|
|
describe("admin theme source audit", () => {
|
|
it("keeps ordinary admin text theme-aware", () => {
|
|
const violations: string[] = [];
|
|
for (const root of ROOTS) {
|
|
for (const file of sourceFiles(root)) {
|
|
const normalized = relative(process.cwd(), file).replaceAll("\\", "/");
|
|
if (GRAPHICAL_ALLOWLIST.includes(normalized)) continue;
|
|
const source = readFileSync(file, "utf8");
|
|
const risky = source.match(
|
|
/(?:text-(?:gray|slate)-(?:400|500|600|700|800|900)|text-white(?:\/\d+)?|\)\]0)/g,
|
|
);
|
|
if (risky)
|
|
violations.push(`${normalized}: ${[...new Set(risky)].join(", ")}`);
|
|
}
|
|
}
|
|
expect(violations).toEqual([]);
|
|
});
|
|
|
|
it("keeps admin chrome independent from public structural colors", () => {
|
|
const violations: string[] = [];
|
|
for (const root of ROOTS) {
|
|
for (const file of sourceFiles(root)) {
|
|
const normalized = relative(process.cwd(), file).replaceAll("\\", "/");
|
|
if (normalized.endsWith("admin-theme-source-audit.test.ts")) continue;
|
|
const source = readFileSync(file, "utf8");
|
|
const risky = [
|
|
...(source.match(PUBLIC_STRUCTURAL_TOKEN) ?? []),
|
|
...(source.match(HARDCODED_UI_PALETTE) ?? []),
|
|
];
|
|
const filtered = [
|
|
...DATA_COLOR_ALLOWLIST,
|
|
...GRAPHICAL_ALLOWLIST,
|
|
].includes(normalized)
|
|
? risky.filter((match) => match.startsWith("var("))
|
|
: risky;
|
|
if (filtered.length)
|
|
violations.push(
|
|
`${normalized}: ${[...new Set(filtered)].join(", ")}`,
|
|
);
|
|
}
|
|
}
|
|
expect(violations).toEqual([]);
|
|
});
|
|
|
|
it("keeps every import workflow on semantic admin status and overlay colors", () => {
|
|
const violations = sourceFiles("src/app/admin/import").flatMap((file) => {
|
|
const source = readFileSync(file, "utf8");
|
|
const risky =
|
|
source.match(/rgba?\([^)]*\)|(?:bg-black|text-white)(?:\/\d+)?/g) ?? [];
|
|
return risky.length
|
|
? [
|
|
`${relative(process.cwd(), file).replaceAll("\\", "/")}: ${[...new Set(risky)].join(", ")}`,
|
|
]
|
|
: [];
|
|
});
|
|
expect(violations).toEqual([]);
|
|
});
|
|
});
|