declare const process: { cwd(): string }; declare function require(name: string): { readFileSync?: (path: string, encoding: string) => string; join?: (...parts: string[]) => string; }; const { readFileSync } = require("node:fs"); const { join } = require("node:path"); if (!readFileSync || !join) { throw new Error("测试运行环境缺少文件读取能力"); } const css = readFileSync(join(process.cwd(), "src/assets/styles.css"), "utf-8"); const loginVisual = cssBlock(".login-visual"); assertIncludes(loginVisual, "background: var(--surface-soft);"); assertIncludes(loginVisual, "color: var(--text);"); assertNotIncludes(loginVisual, "rgba(var(--primary-rgb), 0.94)"); const ledgerVisual = cssBlock(".ledger-visual"); assertIncludes(ledgerVisual, "background: var(--card);"); assertIncludes(ledgerVisual, "padding: 22px;"); const appearancePanel = cssBlock(".appearance-panel"); assertIncludes(appearancePanel, "width: min(480px, calc(100vw - 20px));"); const primaryFocus = cssBlock(".primary-button:focus-visible"); assertIncludes(primaryFocus, "outline: 0;"); assertIncludes(primaryFocus, "box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.16);"); const tableRows = cssBlock(".data-table tbody tr"); assertIncludes(tableRows, "transition: background var(--motion-duration-fast) ease;"); const realtimeHeaderActions = cssBlock(".realtime-header-actions"); assertIncludes(realtimeHeaderActions, "display: grid;"); assertIncludes(realtimeHeaderActions, "grid-template-columns: minmax(210px, 242px) 140px;"); assertIncludes(realtimeHeaderActions, "align-items: end;"); const realtimeDateField = cssBlock(".realtime-date-field"); assertIncludes(realtimeDateField, "min-width: 0;"); const realtimeOverviewPanel = cssBlock(".realtime-overview-panel"); assertIncludes(realtimeOverviewPanel, "overflow: hidden;"); const realtimeOverviewHeader = cssBlock(".realtime-overview-header"); assertIncludes(realtimeOverviewHeader, "align-items: center;"); assertIncludes(realtimeOverviewHeader, "padding: 12px var(--panel-padding) 10px;"); const realtimeOverviewTitle = cssBlock(".realtime-overview-title"); assertIncludes(realtimeOverviewTitle, "display: flex;"); assertIncludes(realtimeOverviewTitle, "align-items: baseline;"); assertIncludes(realtimeOverviewTitle, "gap: 10px;"); const realtimeOverviewTitleText = cssBlock(".realtime-overview-title h2,\n.realtime-overview-title p"); assertIncludes(realtimeOverviewTitleText, "margin: 0;"); const realtimeOverviewStats = cssBlock(".realtime-overview-stats"); assertIncludes(realtimeOverviewStats, "padding-top: 0;"); const realtimeOverviewNumbers = cssBlock(".realtime-overview-stats .compact-stat strong"); assertIncludes(realtimeOverviewNumbers, "font-size: 24px;"); const realtimeSyncChip = cssBlock(".realtime-sync-chip"); assertIncludes(realtimeSyncChip, "min-width: 96px;"); assertIncludes(realtimeSyncChip, "justify-content: center;"); const realtimeTableFilterBar = cssBlock(".realtime-table-filter-bar"); assertIncludes(realtimeTableFilterBar, "display: grid;"); assertIncludes(realtimeTableFilterBar, "grid-template-columns: minmax(180px, 220px) minmax(240px, 1fr) 78px auto;"); assertIncludes(realtimeTableFilterBar, "align-items: end;"); assertIncludes(realtimeTableFilterBar, "padding: 10px var(--panel-padding);"); const realtimeClearFilter = cssBlock(".realtime-clear-filter"); assertIncludes(realtimeClearFilter, "min-height: var(--control-height);"); assertIncludes(realtimeClearFilter, "width: 78px;"); assertIncludes(realtimeClearFilter, "justify-self: start;"); assertIncludes(realtimeClearFilter, "padding: 0 12px;"); assertIncludes(realtimeClearFilter, "white-space: nowrap;"); const realtimeFilterResult = cssBlock(".realtime-filter-result"); assertIncludes(realtimeFilterResult, "justify-self: end;"); const payrollCompactToolbar = cssBlock(".payroll-compact-toolbar"); assertIncludes(payrollCompactToolbar, "grid-template-columns: minmax(190px, 240px) minmax(0, 1fr) auto;"); assertIncludes(payrollCompactToolbar, "padding: 12px var(--panel-padding);"); const payrollToolbarSummary = cssBlock(".payroll-toolbar-summary"); assertIncludes(payrollToolbarSummary, "grid-template-columns: minmax(110px, 0.7fr) minmax(110px, 0.7fr) minmax(110px, 0.7fr) minmax(270px, 1.45fr);"); assertIncludes(payrollToolbarSummary, "border-inline: 1px solid var(--line-soft);"); const payrollToolbarRangeText = cssBlock(".payroll-toolbar-range strong,\n.payroll-toolbar-range small"); assertIncludes(payrollToolbarRangeText, "overflow: visible;"); assertIncludes(payrollToolbarRangeText, "white-space: normal;"); assertIncludes(payrollToolbarRangeText, "text-overflow: clip;"); const payrollToolbarActions = cssBlock(".payroll-toolbar-actions"); assertIncludes(payrollToolbarActions, "grid-template-columns: repeat(2, 138px);"); assertIncludes(payrollToolbarActions, "justify-content: end;"); const payrollToolbarActionButtons = cssBlock(".payroll-toolbar-actions .primary-button,\n.payroll-toolbar-actions .secondary-button"); assertIncludes(payrollToolbarActionButtons, "min-height: 34px;"); assertIncludes(payrollToolbarActionButtons, "padding: 0 10px;"); assertIncludes(payrollToolbarActionButtons, "font-size: 13px;"); const payrollDetailPanel = cssBlock(".payroll-detail-panel"); assertIncludes(payrollDetailPanel, "border-color: rgba(var(--accent-rgb), 0.2);"); assertIncludes(payrollDetailPanel, "overflow: hidden;"); const payrollDetailHeader = cssBlock(".payroll-detail-header"); assertIncludes(payrollDetailHeader, "grid-template-columns: minmax(0, 1fr) auto;"); const payrollDetailSummary = cssBlock(".payroll-detail-summary"); assertIncludes(payrollDetailSummary, "grid-template-columns: repeat(4, minmax(110px, auto));"); const salaryDetailTable = cssBlock(".salary-detail-table"); assertIncludes(salaryDetailTable, "font-size: 13px;"); assertIncludes(salaryDetailTable, "min-width: 1560px;"); const salarySummaryToolbar = cssBlock(".salary-summary-toolbar"); assertIncludes(salarySummaryToolbar, "grid-template-columns: minmax(220px, 1fr) repeat(3, minmax(140px, 180px));"); const salarySummaryTable = cssBlock(".salary-summary-table"); assertIncludes(salarySummaryTable, "min-width: 860px;"); const salarySummaryTableCells = cssBlock(".salary-summary-table th,\n.salary-summary-table td"); assertIncludes(salarySummaryTableCells, "padding: 12px 14px;"); const salaryDetailDrawer = cssBlock(".salary-detail-drawer"); assertIncludes(salaryDetailDrawer, "grid-template-columns: repeat(4, minmax(0, 1fr));"); const salaryDetailDrawerHeader = cssBlock(".salary-detail-drawer-header"); assertIncludes(salaryDetailDrawerHeader, "grid-column: 1 / -1;"); const salaryDetailCells = cssBlock(".salary-detail-table th,\n.salary-detail-table td"); assertIncludes(salaryDetailCells, "padding: 10px 12px;"); const payrollSecondaryGrid = cssBlock(".payroll-secondary-grid"); assertIncludes(payrollSecondaryGrid, "grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);"); assertNotIncludes(css, ".payroll-workbench {"); assertNotIncludes(css, ".payroll-flow-strip"); assertNotIncludes(css, ".payroll-summary-band"); assertIncludes(css, "@media (max-width: 1320px)"); assertIncludes(css, ".payroll-compact-toolbar {\n grid-template-columns: 1fr;\n }"); assertIncludes(css, ".payroll-toolbar-summary {\n grid-template-columns: repeat(2, minmax(0, 1fr));"); assertIncludes(css, ".payroll-secondary-grid {\n grid-template-columns: 1fr;\n }"); assertIncludes(css, ".payroll-toolbar-summary,\n .payroll-toolbar-actions,\n .payroll-detail-summary {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }"); assertIncludes(css, ".realtime-header-actions {\n grid-template-columns: 1fr;\n }"); assertIncludes(css, ".realtime-overview-title {\n align-items: flex-start;\n flex-direction: column;"); assertIncludes(css, ".realtime-table-filter-bar {\n grid-template-columns: 1fr;"); assertNotIncludes(css, ".payroll-command-step"); assertNotIncludes(css, ".payroll-step-index"); assertNotIncludes(css, ".payroll-command-copy"); assertNotIncludes(css, ".payroll-period-row"); assertNotIncludes(css, ".payroll-source-row"); assertNotIncludes(css, ".payroll-source-copy"); function cssBlock(selector: string): string { const pattern = new RegExp(`${escapeRegExp(selector)}\\s*\\{([\\s\\S]*?)\\n\\}`, "m"); const match = css.match(pattern); if (!match) { throw new Error(`缺少样式块 ${selector}`); } return match[1]; } function assertIncludes(content: string, expected: string): void { if (!content.includes(expected)) { throw new Error(`期望包含 ${expected}`); } } function assertNotIncludes(content: string, unexpected: string): void { if (content.includes(unexpected)) { throw new Error(`不应包含 ${unexpected}`); } } function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }