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 root = process.cwd(); const view = readFileSync(join(root, "src/views/EmployeesView.vue"), "utf-8"); const css = readFileSync(join(root, "src/assets/styles.css"), "utf-8"); assertIncludes(view, "view-stack employees-page"); assertIncludes(view, "employee-list-header"); assertIncludes(view, "employee-list-title"); assertIncludes(view, "employee-inline-metric"); assertNotIncludes(view, "employee-header-actions"); assertIncludes(view, "employee_info_keyword"); assertIncludes(view, "organization_keyword"); assertIncludes(view, "employee-filter-employee"); assertIncludes(view, "employee-filter-organization"); assertIncludes(view, "employee-filter-status"); assertIncludes(view, "employee-search-button"); assertIncludes(view, "employee-status-pill"); assertIncludes(view, "employee-status-dot"); assertNotIncludes(view, "placeholder=\"编号、姓名、钉钉ID、部门或岗位\""); assertNotIncludes(view, "class=\"status-chip\""); assertIncludes(view, "管理员工资料,工资核算会优先使用这里的信息。"); assertIncludes(view, "在职员工"); assertIncludes(view, ""); assertIncludes(view, "找员工"); assertIncludes(view, "placeholder=\"输入姓名、编号或手机号\""); assertIncludes(view, "找部门/岗位"); assertIncludes(view, "placeholder=\"输入部门或岗位名称\""); assertIncludes(view, "钉钉账号"); assertIncludes(view, "employee.dingtalk_user_id || \"未绑定\""); assertIncludes(view, "钉钉账号"); assertIncludes(view, "placeholder=\"用于匹配钉钉考勤,可不填\""); assertNotIncludes(view, "钉钉用户ID"); assertNotIncludes(view, "组织"); assertNotIncludes(view, "参与薪资计算"); const pageBlock = cssBlock(".employees-page"); assertIncludes(pageBlock, "gap: 14px;"); const headerBlock = cssBlock(".employee-list-header"); assertIncludes(headerBlock, "display: grid;"); assertIncludes(headerBlock, "grid-template-columns: minmax(0, 1fr) auto;"); const titleBlock = cssBlock(".employee-list-title"); assertIncludes(titleBlock, "display: flex;"); assertIncludes(titleBlock, "align-items: center;"); const metricBlock = cssBlock(".employee-inline-metric"); assertIncludes(metricBlock, "min-height: 30px;"); assertIncludes(metricBlock, "box-shadow: none;"); const filterBlock = cssBlock(".employee-filter-bar"); assertIncludes(filterBlock, "grid-template-columns: minmax(180px, 260px) minmax(180px, 260px) minmax(130px, 160px) 104px minmax(0, 1fr);"); const scopedFilterBlock = cssBlock(".table-filter-bar.employee-filter-bar"); assertIncludes(scopedFilterBlock, "grid-template-columns: minmax(180px, 260px) minmax(180px, 260px) minmax(130px, 160px) 104px minmax(0, 1fr);"); const statusBlock = cssBlock(".employee-status-pill"); assertIncludes(statusBlock, "display: inline-flex;"); assertIncludes(statusBlock, "min-width: 0;"); assertIncludes(statusBlock, "background: transparent;"); const dotBlock = cssBlock(".employee-status-dot"); assertIncludes(dotBlock, "width: 7px;"); assertIncludes(dotBlock, "height: 7px;"); assertIncludes(css, ".table-filter-bar.employee-filter-bar {\n grid-template-columns: 1fr;"); 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, "\\$&"); }