import { readFileSync } from "node:fs"; import path from "node:path"; import { expect, test } from "@playwright/test"; import { buildReimbursementApprovalEvidence } from "../../src/lib/reimbursementApprovalProjection"; import { resolveReimbursementApprovalBlockReason } from "../../src/lib/reimbursementApprovalReadiness"; import { buildReimbursementAmountSummary, parseMoneyToMinorUnits, } from "../../src/lib/reimbursementWorkspace"; function source(relativePath: string) { return readFileSync(path.resolve(process.cwd(), relativePath), "utf8"); } test("报销金额使用整数分判断完整性且不产生展示用第二总额", () => { expect(parseMoneyToMinorUnits("0")).toBe(0n); expect(parseMoneyToMinorUnits("0.1")).toBe(10n); expect(parseMoneyToMinorUnits("1234.56")).toBe(123456n); expect(parseMoneyToMinorUnits("1,234.56")).toBeNull(); expect(parseMoneyToMinorUnits("0.001")).toBeNull(); expect(parseMoneyToMinorUnits("NaN")).toBeNull(); expect(parseMoneyToMinorUnits(null)).toBeNull(); expect(buildReimbursementAmountSummary("0.30", [{ amount: "0.1" }, { amount: "0.20" }])).toEqual({ authoritativeTotalAmount: "0.30", integrity: "ready", }); expect(buildReimbursementAmountSummary("1000.00", [{ amount: "400" }, { amount: "600.00" }])).toEqual({ authoritativeTotalAmount: "1000.00", integrity: "ready", }); expect(buildReimbursementAmountSummary("100.00", [{ amount: "99.99" }])).toEqual({ authoritativeTotalAmount: "100.00", integrity: "mismatch", }); expect(buildReimbursementAmountSummary("100.00", [{ amount: "invalid" }])).toEqual({ authoritativeTotalAmount: "100.00", integrity: "unavailable", }); expect(buildReimbursementAmountSummary("0.00", [])).toEqual({ authoritativeTotalAmount: "0.00", integrity: "ready", }); }); test("报销审批五类阻断原因由同一投影给出且正常态保持安静", () => { const base = { amountIntegrity: "ready" as const, detailState: "ready" as const, online: true, snapshotParseStatus: "ready", }; expect(resolveReimbursementApprovalBlockReason(base)).toBeNull(); expect(resolveReimbursementApprovalBlockReason({ ...base, online: false })).toBe("报销审批需联网核对完整报销内容;当前不能通过、退回、驳回或转审。"); expect(resolveReimbursementApprovalBlockReason({ ...base, detailState: "loading" })).toBe("正在加载完整报销内容和票据;加载完成前不能作出审批决定。"); expect(resolveReimbursementApprovalBlockReason({ ...base, detailState: "error" })).toBe("报销内容未加载完整,暂不能审批,请刷新后重试"); expect(resolveReimbursementApprovalBlockReason({ ...base, snapshotParseStatus: "invalid" })).toBe("提交时检查记录无法解析,暂时不能审批;请联系系统管理员核对。"); expect(resolveReimbursementApprovalBlockReason({ ...base, snapshotParseStatus: undefined })).toBe("提交时检查记录无法解析,暂时不能审批;请联系系统管理员核对。"); expect(resolveReimbursementApprovalBlockReason({ ...base, amountIntegrity: "mismatch" })).toBe("报销总额与明细汇总不一致,请联系管理员"); expect(resolveReimbursementApprovalBlockReason({ ...base, amountIntegrity: "unavailable" })).toBe("明细金额无法完整核对,请联系管理员"); }); test("动作面板复用唯一阻断投影且预算三态判断保持原合同", () => { const adapter = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx"); expect(adapter.match(/resolveReimbursementApprovalBlockReason\(/g)).toHaveLength(1); expect(adapter).toContain("throw new Error(decisionBlockReason"); expect(adapter).toContain("setActionError(decisionBlockReason"); expect(adapter).toContain("{decisionBlockReason}"); expect(adapter.match(/snapshot\?\.budgetCheckStatus === "missing"/g)).toHaveLength(3); expect(adapter).not.toContain("完整内容已就绪"); expect(adapter).not.toContain("内容未就绪,审批动作已停用"); expect(adapter).not.toContain("statusSummary"); expect(adapter).toContain("actionError || activeSecondaryAction ?"); }); test("旧审批层叠 class 与更多菜单样式无定义或消费者", () => { const styles = source("src/index.css"); const productSources = [ source("src/components/patterns/BusinessDetailTemplate.tsx"), source("src/pages/WorkflowTaskDetailPage.tsx"), source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx"), ].join("\n"); for (const legacyClass of ["workflow-action-panel", "workflow-action-more"]) { expect(styles).not.toContain(legacyClass); expect(productSources).not.toContain(legacyClass); } expect(productSources).toContain("detail-action-panel lg:sticky lg:top-24"); }); test("PC 与移动明细只展示共享模型中的服务端权威合计", () => { const evidenceList = source("src/components/reimbursements/ReimbursementItemEvidenceList.tsx"); const desktopDetail = source("src/pages/ReimbursementDetailPage.tsx"); const mobileDetail = source("src/pages/MobileReimbursementDetailPage.tsx"); const approvalAdapter = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx"); const desktopShell = source("src/components/reimbursements/ReimbursementDesktopWorkspaceShell.tsx"); const mobileShell = source("src/components/reimbursements/ReimbursementMobileWorkspaceShell.tsx"); expect(evidenceList).toContain("
{ const desktopShell = source("src/components/reimbursements/ReimbursementDesktopWorkspaceShell.tsx"); const mobileShell = source("src/components/reimbursements/ReimbursementMobileWorkspaceShell.tsx"); const approvalAdapter = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx"); expect(desktopShell).not.toContain("slots.timeline"); expect(desktopShell.indexOf("slots.actionPanel")).toBeLessThan(desktopShell.indexOf("slots.asideContext")); expect(mobileShell.indexOf("slots.afterEvidence")).toBeLessThan(mobileShell.indexOf("slots.timeline")); expect(approvalAdapter).toContain("asideContext: timeline"); expect(approvalAdapter.match(/^\s+timeline,$/gm)).toHaveLength(1); }); test("申请人进度与审批历史保持有意分工", () => { const desktopDetail = source("src/pages/ReimbursementDetailPage.tsx"); const mobileDetail = source("src/pages/MobileReimbursementDetailPage.tsx"); const approvalAdapter = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx"); expect(desktopDetail).toContain("