实现 B-2b 统一报销工作台迁移(reimbursement-form-shared-component-migration)
这个提交包含在:
@@ -330,7 +330,12 @@ function workflowTaskDetail(status: "pending" | "completed" = "pending") {
|
||||
entityType: "reimbursement",
|
||||
currentStatus: isCompleted ? "approved" : "reviewing",
|
||||
currentStepId: isCompleted ? null : "step-dg7",
|
||||
contextData: { standardViolations: [] },
|
||||
contextData: {
|
||||
standardViolations: [],
|
||||
budgetCheck: null,
|
||||
budgetCheckStatus: "not_applicable",
|
||||
parseStatus: "ready",
|
||||
},
|
||||
startedAt: "2026-05-12T08:00:00.000Z",
|
||||
completedAt: isCompleted ? "2026-05-12T09:00:00.000Z" : null,
|
||||
},
|
||||
@@ -842,29 +847,6 @@ async function expectMobileDetailHeaderSpacing(page: Page, testId: string) {
|
||||
expect(metrics!.gap).toBeGreaterThanOrEqual(12);
|
||||
}
|
||||
|
||||
async function expectApprovalActionsStable(page: Page) {
|
||||
const actions = await page.evaluate(() =>
|
||||
Array.from(document.querySelectorAll<HTMLElement>(".mobile-approval-action-grid > button")).map((button) => {
|
||||
const rect = button.getBoundingClientRect();
|
||||
return {
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
top: rect.top,
|
||||
width: rect.width,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
expect(actions).toHaveLength(3);
|
||||
const firstTop = actions[0].top;
|
||||
for (const action of actions) {
|
||||
expect(Math.abs(action.top - firstTop)).toBeLessThanOrEqual(2);
|
||||
expect(action.width).toBeGreaterThanOrEqual(64);
|
||||
}
|
||||
expect(actions[0].right).toBeLessThanOrEqual(actions[1].left + 2);
|
||||
expect(actions[1].right).toBeLessThanOrEqual(actions[2].left + 2);
|
||||
}
|
||||
|
||||
async function expectActionTileGapStable(page: Page) {
|
||||
const gaps = await page.evaluate(() =>
|
||||
Array.from(document.querySelectorAll<HTMLElement>("[data-testid='mobile-action-grid'] .mobile-icon-action")).map((tile) => {
|
||||
@@ -999,31 +981,24 @@ test.describe("MIF7 移动信息架构整改", () => {
|
||||
|
||||
await mobile.page.getByTestId("mobile-pending-summary").getByRole("link").first().click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/approvals\/task-dg7$/);
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-detail")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("reimbursement-approval-evidence")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-list")).toHaveCount(0);
|
||||
await expect(mobile.page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-detail")).not.toContainText("业务单号");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-detail")).toContainText("报销单 BX20260512001");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-history")).toContainText("提交 · 测试员工");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-history")).toContainText("转审 · 测试审批人");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-history")).not.toContainText("transferToEmployeeId");
|
||||
await expectMobileApprovalStatusSlots(mobile.page);
|
||||
await expectMobileDetailHeaderSpacing(mobile.page, "mobile-approvals-detail-header");
|
||||
await expectApprovalActionsStable(mobile.page);
|
||||
await expect(mobile.page.getByTestId("reimbursement-mobile-workspace")).not.toContainText("业务单号");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-detail-header")).toContainText("BX20260512001");
|
||||
await expect(mobile.page.getByTestId("reimbursement-mobile-workspace")).toContainText("提交 · 测试员工");
|
||||
await expect(mobile.page.getByTestId("reimbursement-mobile-workspace")).toContainText("转审 · 测试审批人");
|
||||
await expect(mobile.page.getByTestId("reimbursement-mobile-workspace")).not.toContainText("transferToEmployeeId");
|
||||
await expectMobileDetailHeaderSpacing(mobile.page, "mobile-reimbursement-detail-header");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-sticky-actions")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-approve")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-approve")).toHaveCSS("min-height", "48px");
|
||||
await expectNoMobileShellRegression(mobile.page);
|
||||
await mobile.page.screenshot({ path: screenshotPath("manager-approval-push-detail"), fullPage: true });
|
||||
|
||||
await mobile.page.getByTestId("mobile-approvals-detail-header").getByRole("button", { name: "返回" }).click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile$/);
|
||||
|
||||
await mobile.page.getByTestId("mobile-pending-summary").getByRole("link").first().click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/approvals\/task-dg7$/);
|
||||
await mobile.page.getByTestId("mobile-approval-reimbursement-detail-link").click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/reimbursements\/reimbursement-dg7$/);
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-items")).toBeVisible();
|
||||
await mobile.page.getByTestId("mobile-reimbursement-detail-header").getByRole("button", { name: "返回" }).click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/approvals\/task-dg7$/);
|
||||
await expectApprovalActionsStable(mobile.page);
|
||||
await expect(mobile.page).toHaveURL(/\/mobile$/);
|
||||
|
||||
await mobile.page.goto("/mobile/approvals");
|
||||
await expect(mobile.page.getByRole("tab", { name: "待我审批" })).toBeVisible();
|
||||
@@ -1034,9 +1009,10 @@ test.describe("MIF7 移动信息架构整改", () => {
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-task-task-dg7-completed")).toBeVisible();
|
||||
await mobile.page.getByTestId("mobile-approvals-task-task-dg7-completed").click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/approvals\/task-dg7-completed$/);
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-detail-header")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-detail-header")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
await mobile.page.getByTestId("mobile-approvals-detail-header").getByRole("button", { name: "返回" }).click();
|
||||
await mobile.page.getByTestId("mobile-reimbursement-detail-header").getByRole("button", { name: "返回" }).click();
|
||||
await expect(mobile.page).toHaveURL(/\/mobile\/approvals\?taskTab=completed$/);
|
||||
await expect(mobile.page.getByRole("tab", { name: "已处理" })).toHaveAttribute("data-state", "active");
|
||||
await mobile.page.screenshot({ path: screenshotPath("approver-task-tabs"), fullPage: true });
|
||||
@@ -1130,8 +1106,8 @@ test.describe("MIF7 移动信息架构整改", () => {
|
||||
await expect(mobile.page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
const sectionOrder = await mobile.page.evaluate(() =>
|
||||
[
|
||||
"mobile-reimbursement-items",
|
||||
"mobile-reimbursement-attachments",
|
||||
"approval-item-evidence",
|
||||
"approval-attachments",
|
||||
"mobile-reimbursement-processing-progress",
|
||||
].map((testId) => document.querySelector(`[data-testid='${testId}']`)?.getBoundingClientRect().top ?? -1),
|
||||
);
|
||||
|
||||
@@ -53,18 +53,52 @@ test("R10 移动审批与移动报销复用同一底部动作容器", () => {
|
||||
const sharedActionBar = source("src/components/patterns/MobileStickyActionBar.tsx");
|
||||
const businessEditTemplate = source("src/components/patterns/BusinessEditTemplate.tsx");
|
||||
const mobileApprovals = source("src/pages/MobileApprovalsPage.tsx");
|
||||
const reimbursementApproval = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx");
|
||||
const mobileReimbursement = source("src/pages/MobileReimbursementDetailPage.tsx");
|
||||
const reimbursementMobileShell = source("src/components/reimbursements/ReimbursementMobileWorkspaceShell.tsx");
|
||||
|
||||
expect(sharedActionBar).toContain("mobile-sticky-action-surface fixed inset-x-0 bottom-0");
|
||||
expect(sharedActionBar).toContain("env(safe-area-inset-bottom,0px)");
|
||||
expect(businessEditTemplate).toContain("MOBILE_STICKY_ACTION_PAGE_CLASS");
|
||||
expect(businessEditTemplate).toContain("<MobileStickyActionBar");
|
||||
expect(mobileApprovals).toContain("<MobileStickyActionBar");
|
||||
expect(reimbursementApproval).toContain("<MobileStickyActionBar");
|
||||
expect(mobileReimbursement).toContain("<MobileStickyActionBar");
|
||||
expect(reimbursementMobileShell).toContain("MOBILE_STICKY_ACTION_PAGE_CLASS");
|
||||
expect(reimbursementMobileShell).toContain("slots.mobileActions ? MOBILE_STICKY_ACTION_PAGE_CLASS : undefined");
|
||||
expect(mobileApprovals).not.toContain('className="mobile-sticky-action-surface fixed');
|
||||
expect(reimbursementApproval).not.toContain('className="mobile-sticky-action-surface fixed');
|
||||
expect(mobileReimbursement).not.toContain('className="mobile-sticky-action-surface fixed');
|
||||
});
|
||||
|
||||
test("B-2b 报销工作台双端共享内容合同且 task detail 只由路由查询一次", () => {
|
||||
const desktopShell = source("src/components/reimbursements/ReimbursementDesktopWorkspaceShell.tsx");
|
||||
const mobileShell = source("src/components/reimbursements/ReimbursementMobileWorkspaceShell.tsx");
|
||||
const desktopRoute = source("src/pages/WorkflowTaskRoutePage.tsx");
|
||||
const mobileRoute = source("src/pages/MobileWorkflowTaskRoutePage.tsx");
|
||||
const approvalAdapter = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx");
|
||||
const workflowDetail = source("src/pages/WorkflowTaskDetailPage.tsx");
|
||||
const mobileApprovals = source("src/pages/MobileApprovalsPage.tsx");
|
||||
|
||||
for (const shell of [desktopShell, mobileShell]) {
|
||||
expect(shell).toContain("ReimbursementWorkspaceModel");
|
||||
expect(shell).toContain("ReimbursementWorkspaceSlots");
|
||||
expect(shell).toContain("ReimbursementEvidenceCapabilities");
|
||||
expect(shell).toContain("<ReimbursementItemEvidenceList");
|
||||
expect(shell.indexOf("slots.beforeEvidence")).toBeLessThan(shell.indexOf("<ReimbursementItemEvidenceList"));
|
||||
expect(shell.indexOf("<ReimbursementItemEvidenceList")).toBeLessThan(shell.indexOf("slots.afterEvidence"));
|
||||
expect(shell.indexOf("slots.afterEvidence")).toBeLessThan(shell.indexOf("slots.timeline"));
|
||||
}
|
||||
|
||||
expect(desktopRoute.match(/fetchWorkflowTaskDetail\(/g)).toHaveLength(1);
|
||||
expect(mobileRoute.match(/fetchWorkflowTaskDetail\(/g)).toHaveLength(1);
|
||||
expect(approvalAdapter).not.toContain("fetchWorkflowTaskDetail");
|
||||
expect(workflowDetail).not.toContain("ReimbursementItemEvidenceList");
|
||||
expect(workflowDetail).not.toContain("提交时检查");
|
||||
expect(mobileApprovals).not.toContain("selectedTask?.reimbursement");
|
||||
expect(mobileApprovals).not.toContain("reimbursementDecisionReady");
|
||||
});
|
||||
|
||||
test("R1/R6 未知状态和个人事项状态不回落到技术枚举", () => {
|
||||
const statusBadge = source("src/components/common/StatusBadge.tsx");
|
||||
const todosPage = source("src/pages/TodosPage.tsx");
|
||||
|
||||
@@ -351,9 +351,9 @@ test("E2E 移动审批:报销证据不进入 IndexedDB 且离线动作 fail cl
|
||||
}
|
||||
|
||||
await page.context().setOffline(true);
|
||||
await expect(page.getByText("请联网加载完整明细和票据后再处理该报销审批")).toBeVisible();
|
||||
await expect(page.getByText("报销审批需联网核对完整报销内容;当前不能通过、退回、驳回或转审。")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-approvals-approve")).toBeDisabled();
|
||||
await page.getByTestId("mobile-approvals-more").click();
|
||||
await expect(page.getByTestId("mobile-approvals-return")).toBeDisabled();
|
||||
await expect(page.getByTestId("mobile-approvals-reject")).toBeDisabled();
|
||||
await expect(page.getByTestId("mobile-approvals-transfer")).toBeDisabled();
|
||||
});
|
||||
|
||||
@@ -221,7 +221,12 @@ const workflowDetail = {
|
||||
entityType: "reimbursement",
|
||||
currentStatus: "reviewing",
|
||||
currentStepId: "manager-review",
|
||||
contextData: { standardViolations: [] },
|
||||
contextData: {
|
||||
standardViolations: [],
|
||||
budgetCheck: null,
|
||||
budgetCheckStatus: "not_applicable",
|
||||
parseStatus: "ready",
|
||||
},
|
||||
startedAt: "2026-05-27T09:00:00.000Z",
|
||||
completedAt: null,
|
||||
},
|
||||
@@ -1252,16 +1257,16 @@ test("已完成审批只保留中文只读摘要", async ({ page }) => {
|
||||
await page.goto("/workflow/tasks/task-wave3-completed");
|
||||
|
||||
await expect(page.getByTestId("workflow-task-action-panel")).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-task-action-panel")).toContainText("当前审批角色:财务");
|
||||
await expect(page.getByTestId("workflow-task-action-panel")).toContainText("完成时间:2026年5月27日 18:10");
|
||||
await expect(page.getByTestId("workflow-task-overview").getByText("可执行动作", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-task-action-panel")).toContainText("审批角色:财务");
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toContainText("2026年5月27日 18:10");
|
||||
await expect(page.getByTestId("workflow-task-approve")).toHaveCount(0);
|
||||
await expect(page.getByTestId("workflow-task-return-toggle")).toHaveCount(0);
|
||||
await expect(page.getByTestId("workflow-task-reject-toggle")).toHaveCount(0);
|
||||
await expect(page.getByText("更多", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText("通过 · 王顺理", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("2026年5月27日 18:10", { exact: true })).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-task-back")).toContainText("返回审批列表");
|
||||
await expect(page.getByRole("link", { name: "返回审批列表" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("1280px 资产详情保持可用双栏和可点击操作区", async ({ page }) => {
|
||||
@@ -1296,49 +1301,55 @@ test("1280px 资产详情保持可用双栏和可点击操作区", async ({ page
|
||||
test("报销详情固定使用上海时区并收敛只读信息", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.goto("/reimbursements/reim-wave3");
|
||||
await expect(page.getByRole("heading", { name: "报销概况" })).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "当前状态与下一步" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "业务摘要" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "补充附件" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "预算与审核提示" })).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-back")).toContainText("返回报销列表");
|
||||
await expect(page.getByTestId("reimbursement-summary")).toContainText("¥1,860.00");
|
||||
await expect(page.getByTestId("reimbursement-items")).toContainText("¥860.00");
|
||||
await expect(page.getByTestId("reimbursement-attachments")).toContainText("2026年5月27日 17:10");
|
||||
await expect(page.getByTestId("reimbursement-materials").getByRole("button", { name: /查看发票原件/ })).toHaveCount(1);
|
||||
await expect(page.getByTestId("reimbursement-materials").getByRole("button", { name: /查看补充附件/ })).toHaveCount(1);
|
||||
await expect(page.getByTestId("reimbursement-materials").getByRole("button", { name: /移除|删除/ })).toHaveCount(0);
|
||||
const pcInvoicePreview = page.getByTestId("reimbursement-materials").getByRole("button", { name: /查看发票原件/ });
|
||||
const pcAttachmentPreview = page.getByTestId("reimbursement-materials").getByRole("button", { name: /查看补充附件/ });
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toContainText("¥1,860.00");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toContainText("¥860.00");
|
||||
await expect(page.getByTestId("approval-attachments")).toContainText("现场说明.pdf");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /查看发票原件/ })).toHaveCount(1);
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /查看补充附件/ })).toHaveCount(1);
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /移除|删除/ })).toHaveCount(0);
|
||||
const pcInvoicePreview = page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /查看发票原件/ });
|
||||
const pcMaterialPreview = page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: "查看完整材料" });
|
||||
const pcAttachmentPreview = page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /查看补充附件/ });
|
||||
await pcInvoicePreview.focus();
|
||||
await expect(pcInvoicePreview).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(pcMaterialPreview).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(pcAttachmentPreview).toBeFocused();
|
||||
await expect(page.getByTestId("reimbursement-workflow-progress")).toContainText("负责人复核");
|
||||
await expect(page.getByTestId("reimbursement-workflow-progress")).toContainText("付款复核");
|
||||
await expect(page.getByTestId("reimbursement-workflow-progress")).not.toContainText(/第 \d+ 步/);
|
||||
|
||||
await page.goto("/workflow/tasks/task-wave3");
|
||||
await expect(page.getByTestId("workflow-task-overview")).toContainText("2026年5月27日 17:05");
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toContainText("2026年5月27日 17:00");
|
||||
await expect(page.getByText(`提交 · ${visualMe.name}`)).toBeVisible();
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto("/mobile/reimbursements/reim-wave3");
|
||||
await expect(page.getByRole("heading", { name: "报销概况" })).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-reimbursement-detail-header")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "明细与票据" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "当前办理摘要" })).toHaveCount(0);
|
||||
await expect(page.getByTestId("mobile-reimbursement-detail-focus")).toContainText(visualMe.name);
|
||||
await expect(page.getByTestId("mobile-reimbursement-detail-focus")).toContainText("¥1,860.00");
|
||||
await expect(page.getByTestId("mobile-reimbursement-items")).toContainText("¥860.00");
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toContainText(visualMe.name);
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toContainText("¥1,860.00");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toContainText("¥860.00");
|
||||
await expect(page.getByTestId("mobile-reimbursement-processing-progress")).toContainText("负责人复核");
|
||||
await expect(page.getByTestId("mobile-reimbursement-attachments")).toContainText("2026年5月27日 17:10");
|
||||
await expect(page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: /查看发票原件/ })).toHaveCount(1);
|
||||
await expect(page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: /查看补充附件/ })).toHaveCount(1);
|
||||
await expect(page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: /移除|删除/ })).toHaveCount(0);
|
||||
const mobileInvoicePreview = page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: /查看发票原件/ });
|
||||
const mobileAttachmentPreview = page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: /查看补充附件/ });
|
||||
await expect(page.getByTestId("approval-attachments")).toContainText("现场说明.pdf");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /查看发票原件/ })).toHaveCount(1);
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /查看补充附件/ })).toHaveCount(1);
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /移除|删除/ })).toHaveCount(0);
|
||||
const mobileInvoicePreview = page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /查看发票原件/ });
|
||||
const mobileMaterialPreview = page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: "查看完整材料" });
|
||||
const mobileAttachmentPreview = page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /查看补充附件/ });
|
||||
await mobileInvoicePreview.focus();
|
||||
await expect(mobileInvoicePreview).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(mobileMaterialPreview).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(mobileAttachmentPreview).toBeFocused();
|
||||
await expect(page.getByTestId("mobile-reimbursement-processing-progress")).toContainText("付款完成");
|
||||
await expect(page.getByTestId("mobile-reimbursement-actions")).toHaveCount(0);
|
||||
@@ -1351,15 +1362,13 @@ test("Wave3 PC 详情和通知中心视觉截图", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
|
||||
await page.goto("/reimbursements/reim-wave3");
|
||||
await expect(page.getByRole("heading", { name: /报销详情/ })).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-overview")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-summary")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "报销概况" })).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: `报销单 ${reimbursementDetail.formNumber}` })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "明细与票据" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "当前状态与下一步" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "业务摘要" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "补充附件" })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "预算与审核提示" })).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-attachments")).toContainText("2026年5月27日 17:10");
|
||||
await expect(page.getByTestId("approval-attachments")).toContainText("现场说明.pdf");
|
||||
await expect(page.getByTestId("reimbursement-first-screen-actions")).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-action-panel")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-action-panel").getByRole("button", { name: "撤回" })).toBeVisible();
|
||||
@@ -1372,7 +1381,7 @@ test("Wave3 PC 详情和通知中心视觉截图", async ({ page }) => {
|
||||
|
||||
await page.goto("/workflow/tasks/task-wave3");
|
||||
await page.setViewportSize({ width: 1100, height: 768 });
|
||||
await expect(page.getByRole("heading", { name: "审批详情" })).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toBeVisible();
|
||||
await expect(page.locator(".app-shell-sidebar")).toBeHidden();
|
||||
await expect(page.getByRole("button", { name: "打开菜单" })).toBeVisible();
|
||||
await expect(page.locator(".detail-workspace-grid")).toHaveCSS("grid-template-columns", /.+ .+/);
|
||||
@@ -1383,12 +1392,11 @@ test("Wave3 PC 详情和通知中心视觉截图", async ({ page }) => {
|
||||
await expect(page.getByRole("button", { name: "打开菜单" })).toBeHidden();
|
||||
await expect(page.getByTestId("workflow-task-next-action")).toHaveCount(0);
|
||||
await expect(page.getByText("审批动作")).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { name: "处理", exact: true })).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-task-overview")).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-task-overview")).toContainText("2026年5月27日 17:05");
|
||||
await expect(page.locator(".workflow-action-panel")).toHaveCSS("position", "sticky");
|
||||
await expect(page.locator(".workflow-action-panel")).toHaveCSS("top", "96px");
|
||||
await expect(page.locator(".workflow-action-panel")).toHaveCSS("z-index", "30");
|
||||
await expect(page.getByTestId("workflow-task-action-panel")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toContainText("2026年5月27日 17:00");
|
||||
const workflowActionPanel = page.getByTestId("workflow-task-action-panel").locator("xpath=ancestor::section[1]");
|
||||
await expect(workflowActionPanel).toHaveCSS("position", "sticky");
|
||||
await expect(workflowActionPanel).toHaveCSS("top", "96px");
|
||||
const workflowLayout = await page.locator(".detail-workspace-grid").evaluate((workspace) => {
|
||||
const [main, aside] = Array.from(workspace.children) as HTMLElement[];
|
||||
return {
|
||||
@@ -1402,20 +1410,15 @@ test("Wave3 PC 详情和通知中心视觉截图", async ({ page }) => {
|
||||
expect(workflowLayout.mainWidth).toBeGreaterThanOrEqual(560);
|
||||
expect(workflowLayout.asideWidth).toBeGreaterThanOrEqual(280);
|
||||
expect(workflowLayout.overflow).toBeLessThanOrEqual(2);
|
||||
const formNumberLineCount = await page.getByText(reimbursementDetail.formNumber, { exact: true }).evaluate((element) => {
|
||||
const formNumberLineCount = await page.getByRole("heading", { name: `报销单 ${reimbursementDetail.formNumber}` }).evaluate((element) => {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
return range.getClientRects().length;
|
||||
});
|
||||
expect(formNumberLineCount).toBe(1);
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.getByTestId("workflow-task-reimbursement-detail-link").click();
|
||||
await expect(page).toHaveURL(/\/reimbursements\/reim-wave3$/);
|
||||
await expect(page.getByTestId("reimbursement-overview")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-back")).toContainText("返回审批详情");
|
||||
await page.getByTestId("reimbursement-back").click();
|
||||
await expect(page).toHaveURL(/\/workflow\/tasks\/task-wave3$/);
|
||||
await expect(page.getByTestId("workflow-task-overview")).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-task-reimbursement-detail-link")).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toBeVisible();
|
||||
await page.screenshot({ path: screenshotPath("02-pc-workflow-detail.png"), fullPage: true });
|
||||
|
||||
await page.goto("/notifications");
|
||||
@@ -1458,20 +1461,18 @@ test("Wave3 PC 详情和通知中心视觉截图", async ({ page }) => {
|
||||
await page.screenshot({ path: screenshotPath("04-pc-contracts-registry.png"), fullPage: true });
|
||||
});
|
||||
|
||||
test("PC 报销详情按来源返回并保留列表筛选", async ({ page }) => {
|
||||
test("PC 报销审批只接受安全审批列表返回目标", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.goto("/reimbursements?status=reviewing&page=1&scope=all");
|
||||
await page.getByTestId("reimbursement-detail-link").first().click();
|
||||
await expect(page).toHaveURL(/\/workflow\/tasks\/task-wave3$/);
|
||||
await expect(page.getByTestId("workflow-task-back")).toContainText("返回审批列表");
|
||||
await page.getByTestId("workflow-task-back").click();
|
||||
await expect(page).toHaveURL(/\/reimbursements\?status=reviewing&page=1&scope=all$/);
|
||||
await expect(page.getByRole("link", { name: "返回审批列表" })).toBeVisible();
|
||||
await page.getByRole("link", { name: "返回审批列表" }).click();
|
||||
await expect(page).toHaveURL(/\/workflow\/tasks$/);
|
||||
|
||||
await page.goto("/workflow/tasks/task-wave3");
|
||||
await page.getByTestId("workflow-task-reimbursement-detail-link").click();
|
||||
await expect(page.getByTestId("reimbursement-back")).toContainText("返回审批详情");
|
||||
await page.getByTestId("reimbursement-back").click();
|
||||
await expect(page).toHaveURL(/\/workflow\/tasks\/task-wave3$/);
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-task-reimbursement-detail-link")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("/contracts-registry 在 768x1024 与 1024x768 下操作列可达", async ({ page }) => {
|
||||
@@ -1549,14 +1550,14 @@ test("Wave3 移动关键页视觉截图", async ({ page }) => {
|
||||
await page.screenshot({ path: screenshotPath("05-mobile-reimbursements.png"), fullPage: true });
|
||||
|
||||
await page.goto("/mobile/reimbursements/reim-wave3");
|
||||
await expect(page.getByTestId("mobile-reimbursement-detail-focus")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "报销概况" })).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "明细与票据" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "当前办理摘要" })).toHaveCount(0);
|
||||
await expect(page.getByTestId("mobile-reimbursement-detail-focus")).toContainText(visualMe.name);
|
||||
await expect(page.getByTestId("mobile-reimbursement-attachments")).toContainText("2026年5月27日 17:10");
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toContainText(visualMe.name);
|
||||
await expect(page.getByTestId("approval-attachments")).toContainText("现场说明.pdf");
|
||||
await expect(page.getByTestId("mobile-reimbursement-actions")).toHaveCount(0);
|
||||
await expect(page.getByText("PDF/JPG/PNG/WebP,单次最多 5 个")).toHaveCount(0);
|
||||
await expect(page.getByTestId("mobile-reimbursement-items")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toBeVisible();
|
||||
await expect(page.locator("table")).toHaveCount(0);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({ path: screenshotPath("06-mobile-reimbursement-detail.png"), fullPage: true });
|
||||
@@ -1570,8 +1571,8 @@ test("Wave3 移动关键页视觉截图", async ({ page }) => {
|
||||
await page.screenshot({ path: screenshotPath("07-mobile-approvals.png"), fullPage: true });
|
||||
|
||||
await page.goto("/mobile/approvals/task-wave3");
|
||||
await expect(page.getByTestId("mobile-approvals-detail")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-approvals-detail-focus")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toBeVisible();
|
||||
await expect(page.locator("table")).toHaveCount(0);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({ path: screenshotPath("08-mobile-approval-detail.png"), fullPage: true });
|
||||
|
||||
@@ -105,6 +105,7 @@ function reimbursementDetail(overrides: { reimbursementDate?: string; descriptio
|
||||
},
|
||||
],
|
||||
invoices: [],
|
||||
attachments: [],
|
||||
budgetHint: null,
|
||||
pendingWorkflow: null,
|
||||
workflowProgress: null,
|
||||
@@ -376,11 +377,11 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
|
||||
await expect(page.getByRole("heading", { name: `报销详情 ${formNumber}` })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: `报销单 ${formNumber}` })).toBeVisible();
|
||||
await expect(page.getByText("报销ID")).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-business-summary")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-attachments")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-related-records")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toBeVisible();
|
||||
await expect(page.getByTestId("approval-attachments")).toBeVisible();
|
||||
await expect(page.getByText("执行支付")).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-delete-draft")).toHaveCount(0);
|
||||
await page.getByTestId("reimbursement-more-toggle").click();
|
||||
@@ -413,20 +414,20 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
|
||||
await expect(page.getByTestId("reimbursement-summary")).toContainText("报销总额");
|
||||
await expect(page.getByTestId("reimbursement-items-table")).toContainText("共 1 条明细");
|
||||
await expect(page.getByTestId("reimbursement-items-table")).toContainText("报销合计");
|
||||
await expect(page.getByTestId("reimbursement-operational-hints")).toContainText("待关联发票或补充无票说明");
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toContainText("¥168.30");
|
||||
await expect(page.getByTestId("approval-item-evidence")).toContainText("明细 1");
|
||||
await expect(page.getByTestId("approval-item-evidence")).toContainText("所属票据(0)");
|
||||
await expect(page.getByTestId("approval-item-evidence")).toContainText("这条明细尚无对应票据");
|
||||
|
||||
await page.getByTestId("reimbursement-business-summary").getByRole("button", { name: "编辑", exact: true }).click();
|
||||
await page.getByRole("button", { name: "编辑", exact: true }).click();
|
||||
const editor = page.getByTestId("reimbursement-header-editor");
|
||||
await editor.getByLabel("报销日期").fill("2026-05-07");
|
||||
await editor.getByLabel("报销事由").fill("办公网络费用");
|
||||
await editor.getByRole("button", { name: "保存", exact: true }).click();
|
||||
|
||||
await expect(page.getByTestId("reimbursement-header-editor")).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-business-summary")).toContainText("办公网络费用");
|
||||
await expect(page.getByTestId("reimbursement-summary")).toContainText("2026-05-07");
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toContainText("办公网络费用");
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toContainText("2026年5月7日");
|
||||
});
|
||||
|
||||
test("移动报销卡片显示业务编号", async ({ page }) => {
|
||||
@@ -448,8 +449,8 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
await expect(page.locator("table:visible")).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("mobile-reimbursement-card").getByRole("link").click();
|
||||
await expect(page.getByTestId("mobile-reimbursement-items")).toContainText("共 1 条明细");
|
||||
await expect(page.getByTestId("mobile-reimbursement-items")).toContainText("报销合计 ¥168.30");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence").getByTestId("approval-item-evidence")).toHaveCount(1);
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toContainText("¥168.30");
|
||||
});
|
||||
|
||||
test("管理员查看他人报销时保持只读并可在移动端切换全部报销", async ({ page }) => {
|
||||
@@ -526,15 +527,13 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
await expect(page).toHaveURL(/\/portal/);
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.goto(`/reimbursements/${otherReimbursementId}`);
|
||||
await expect(page.getByTestId("reimbursement-overview")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-withdraw")).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-withdraw-comment")).toHaveCount(0);
|
||||
await expect(page.getByText("只读查看")).toBeVisible();
|
||||
await expect(page.getByText("等待报销人完善")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-item-add")).toHaveCount(0);
|
||||
await expect(page.getByRole("combobox", { name: "关联项目" })).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-invoices")).toContainText("明细:未关联");
|
||||
await expect(page.getByTestId("reimbursement-invoices")).toContainText("已关联");
|
||||
await expect(page.getByTestId("approval-unlinked-invoices")).toContainText("R102-OTHER");
|
||||
await expect(page.getByTestId("approval-item-evidence")).toContainText("所属票据(0)");
|
||||
await expect(page.getByText("草稿或驳回状态可移除")).toHaveCount(0);
|
||||
|
||||
await page.setViewportSize({ width: 375, height: 812 });
|
||||
@@ -565,9 +564,9 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
await page.getByText("其他员工").click();
|
||||
await expect(page).toHaveURL(new RegExp(`/mobile/reimbursements/${otherReimbursementId}$`));
|
||||
await expect(page.getByText("只读查看")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-reimbursement-items").getByRole("button", { name: "新增" })).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-invoices")).toContainText("明细:未关联");
|
||||
await expect(page.getByTestId("reimbursement-invoices")).toContainText("已关联");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence").getByRole("button", { name: /新增|编辑|删除/ })).toHaveCount(0);
|
||||
await expect(page.getByTestId("approval-unlinked-invoices")).toBeVisible();
|
||||
await expect(page.getByTestId("approval-item-evidence")).toContainText("所属票据(0)");
|
||||
await expect(page.getByRole("button", { name: "移除" })).toHaveCount(0);
|
||||
const routeState = await page.evaluate(() => window.history.state?.usr ?? null);
|
||||
expect(routeState?.from).toContain("status=reviewing");
|
||||
@@ -585,7 +584,7 @@ test.describe("R102 reimbursement main flow", () => {
|
||||
test("报销明细记录切换时支持取消保持与确认放弃", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
await page.getByTestId("reimbursement-item-edit").click();
|
||||
await page.getByTestId("approval-item-evidence").getByRole("button", { name: "编辑明细" }).click();
|
||||
|
||||
const addItem = page.getByTestId("reimbursement-item-add");
|
||||
await addItem.click();
|
||||
|
||||
@@ -156,7 +156,7 @@ test.describe.serial("R109 企业微信移动报销反馈专项", () => {
|
||||
await employee.page.screenshot({ path: screenshotPath("mobile-reimbursements") });
|
||||
await card.getByRole("link").click();
|
||||
await expect(employee.page).toHaveURL(new RegExp(`/mobile/reimbursements/${seeded.id}`));
|
||||
await expect(employee.page.getByTestId("mobile-reimbursement-detail")).toBeVisible();
|
||||
await expect(employee.page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await employee.page.screenshot({ path: screenshotPath("mobile-reimbursement-detail") });
|
||||
|
||||
const detailMetrics = await collectMobileMetrics(employee.page);
|
||||
|
||||
@@ -131,7 +131,7 @@ test.describe.serial("R110 手机和 PC 路由分流", () => {
|
||||
|
||||
await mobile.page.goto(`/reimbursements/${seeded.id}`);
|
||||
await expect(mobile.page).toHaveURL(new RegExp(`/mobile/reimbursements/${seeded.id}$`));
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-detail")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expectNoPcShell(mobile.page);
|
||||
|
||||
await mobile.page.goto(`/reimbursements/${seeded.id}/print`);
|
||||
|
||||
@@ -810,11 +810,8 @@ async function exerciseInvoiceGuardRecoveryAfterPrecheckConfirm(
|
||||
);
|
||||
await confirmDialog.getByRole("button", { name: "确认提交", exact: true }).click();
|
||||
|
||||
if (options.mobile) {
|
||||
await expect(page.getByTestId("mobile-reimbursement-invoice-guard-summary")).toContainText("共 2 项需要处理");
|
||||
} else {
|
||||
await expect(page.getByTestId("reimbursement-action-error").first()).toContainText("1 张发票未选择报销明细");
|
||||
}
|
||||
await expect(page.getByTestId("reimbursement-action-error").first()).toContainText("1 张发票未选择报销明细");
|
||||
await expect(page.getByText("这些票据当前没有明确费用归属,暂时无法提交;请为每张票据选择对应的报销明细。")).toBeVisible();
|
||||
await expect(confirmDialog).toHaveCount(0);
|
||||
await expect(page.locator("div.fixed.inset-0[data-state='open']")).toHaveCount(0);
|
||||
const firstGuardIssue = page.locator('[data-reimbursement-guard-issue="true"]').first();
|
||||
@@ -827,7 +824,7 @@ async function exerciseInvoiceGuardRecoveryAfterPrecheckConfirm(
|
||||
invoiceId: invoice.id,
|
||||
linkedItemId: targetItemId,
|
||||
}]);
|
||||
await expect(page.getByText("这张发票还没有选择报销明细,请在下方完成关联。")).toHaveCount(0);
|
||||
await expect(page.getByText("这些票据当前没有明确费用归属,暂时无法提交;请为每张票据选择对应的报销明细。")).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("reimbursement-submit").click();
|
||||
await expect(confirmDialog).toBeVisible();
|
||||
@@ -853,18 +850,12 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
await expect(page.getByText("报销详情 BX20260508001")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "报销单 BX20260508001" })).toBeVisible();
|
||||
await expect(page.getByText("预算与审核提示")).toHaveCount(0);
|
||||
await expect(page.getByTestId("reimbursement-attachments")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-related-records")).toBeVisible();
|
||||
const pcSectionOrder = await page.locator("[data-testid='reimbursement-overview'], [data-testid='reimbursement-items'], [data-testid='reimbursement-materials'], [data-testid='reimbursement-workflow-progress']")
|
||||
.evaluateAll((nodes) => nodes.map((node) => node.getAttribute("data-testid")));
|
||||
expect(pcSectionOrder).toEqual([
|
||||
"reimbursement-overview",
|
||||
"reimbursement-workflow-progress",
|
||||
"reimbursement-items",
|
||||
"reimbursement-materials",
|
||||
]);
|
||||
await expect(page.getByTestId("reimbursement-desktop-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toBeVisible();
|
||||
await expect(page.getByTestId("approval-attachments")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-workflow-progress")).toBeVisible();
|
||||
await expect(page.locator('a[href="#reimbursement-workflow-progress-panel"]')).toHaveCount(0);
|
||||
await expect(page.locator('a[href="#reimbursement-processing-progress-panel"]')).toHaveCount(0);
|
||||
await expect(page.getByText("ERP")).toHaveCount(0);
|
||||
@@ -882,29 +873,23 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.getByText("北京悦海堂餐饮管理有限公司").first()).toBeVisible();
|
||||
await expect(page.getByText("明细:业务招待 · 客户招待")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-materials")).toContainText("发票 2 · 补充附件 0");
|
||||
await expect(page.getByTestId("reimbursement-materials")).toContainText("数电发票 · 12345678");
|
||||
await expect(page.getByTestId("reimbursement-materials")).toContainText("电子火车票 · 12345679");
|
||||
await expect(page.getByTestId("reimbursement-materials")).not.toContainText("无代码");
|
||||
await expect(page.getByTestId("reimbursement-materials")).not.toContainText("未识别销售方");
|
||||
const itemRow = page.getByTestId("reimbursement-item-row");
|
||||
await expect(itemRow.getByRole("button", { name: /查看关联发票/ })).toHaveCount(0);
|
||||
await expect(itemRow).not.toContainText("电子火车票");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toContainText("12345678");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toContainText("12345679");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).not.toContainText("无代码");
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).not.toContainText("未识别销售方");
|
||||
await expect(page.getByRole("button", { name: /查看发票原件/ })).toHaveCount(2);
|
||||
await expect(page.getByRole("button", { name: "查看原件", exact: true })).toHaveCount(0);
|
||||
|
||||
await page.locator("#reimbursement-attachment-upload").setInputFiles({
|
||||
name: "付款截图.png",
|
||||
mimeType: "image/png",
|
||||
buffer: Buffer.from("r111 attachment"),
|
||||
});
|
||||
await page.getByRole("button", { name: "上传附件" }).click();
|
||||
await page.getByRole("button", { name: /上传.*附件/ }).click();
|
||||
await expect(page.getByTestId("approval-attachments")).toContainText("付款截图.png");
|
||||
await expect(page.getByRole("button", { name: "查看补充附件:付款截图.png" })).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-travel-summary")).toContainText("合成差旅概要");
|
||||
await expect(page.getByTestId("reimbursement-travel-summary")).toContainText("测试地点");
|
||||
await expect(page.getByTestId("reimbursement-materials")).toContainText("发票 2 · 补充附件 1");
|
||||
await page.getByTestId("reimbursement-materials-open-all").click();
|
||||
await page.getByRole("button", { name: "查看完整材料" }).click();
|
||||
await expect.poll(() => page.evaluate(() => (window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls.length)).toBe(1);
|
||||
expect(await page.evaluate(() => (window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls[0]?.[0])).toContain("previewToken=merged-pdf");
|
||||
|
||||
@@ -943,11 +928,11 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await login(page);
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
|
||||
await expect(page.getByText("该金额通常应选择办公电子设备。")).toBeVisible();
|
||||
await expect(page.getByText(/该金额通常应选择办公电子设备/)).toBeVisible();
|
||||
await page.getByTestId("reimbursement-item-add").click();
|
||||
await page.locator("#expenseTypeId").selectOption("expense-type-office-supplies-r111");
|
||||
await page.getByTestId("reimbursement-item-amount").fill("2000.00");
|
||||
await expect(page.getByText("该金额通常应选择办公电子设备。")).toHaveCount(2);
|
||||
await expect(page.getByText(/该金额通常应选择办公电子设备/)).toHaveCount(2);
|
||||
await page.locator("#expenseTypeId").selectOption("expense-type-assets-r111");
|
||||
await expect(page.getByTestId("reimbursement-item-asset-approval-basis")).toHaveCount(0);
|
||||
await expect(page.getByText(/请在明细说明中写明物品名称、实际使用人或责任人、存放位置/)).toBeVisible();
|
||||
@@ -980,11 +965,11 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await page.keyboard.press("Escape");
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto(`/mobile/reimbursements/${reimbursementId}`);
|
||||
await expect(page.getByText("该金额通常应选择办公电子设备。")).toBeVisible();
|
||||
await page.getByRole("button", { name: "新增", exact: true }).click();
|
||||
await expect(page.getByText(/该金额通常应选择办公电子设备/)).toBeVisible();
|
||||
await page.getByRole("button", { name: "新增明细", exact: true }).click();
|
||||
await page.getByLabel("费用类型").selectOption("expense-type-office-supplies-r111");
|
||||
await page.getByRole("textbox", { name: "金额", exact: true }).fill("2000.00");
|
||||
await expect(page.getByText("该金额通常应选择办公电子设备。")).toHaveCount(2);
|
||||
await expect(page.getByText(/该金额通常应选择办公电子设备/)).toHaveCount(2);
|
||||
await page.getByLabel("费用类型").selectOption("expense-type-assets-r111");
|
||||
await expect(page.getByText("事前批准依据", { exact: true })).toHaveCount(0);
|
||||
await page.getByRole("textbox", { name: "说明", exact: true }).fill("合成设备,测试员工使用,测试位置");
|
||||
@@ -1013,8 +998,8 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await page.setViewportSize({ width: 1440, height: 960 });
|
||||
await page.goto(`/reimbursements/${reimbursementId}`);
|
||||
await expect(page.getByRole("button", { name: "提交报销", exact: true })).toBeVisible();
|
||||
await expect(page.getByText("提交审批", { exact: true })).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-overview").getByText("添加发票", { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText("下一步:提交审批", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("所属票据(0)")).toHaveCount(0);
|
||||
await page.getByRole("button", { name: "提交报销", exact: true }).click();
|
||||
await expect.poll(() => state.submitRequestCount).toBe(1);
|
||||
});
|
||||
@@ -1118,13 +1103,10 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await page.getByTestId("reimbursement-submit").click();
|
||||
|
||||
await expect(page.getByTestId("reimbursement-action-error").first()).toContainText("1 张发票未选择报销明细");
|
||||
await expect(page.getByText("尚未关联正式发票,请到“报销材料”添加发票并选择本明细。")).toBeVisible();
|
||||
await expect(page.getByText("关联发票的票面金额未识别,请重试识别或联系财务处理。")).toBeVisible();
|
||||
await expect(
|
||||
page.getByText("申报金额 ¥1000.00 高于关联发票合计 ¥600.00,请补充发票或调整申报金额。"),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText("这张发票还没有选择报销明细,请在下方完成关联。")).toBeVisible();
|
||||
await expect(page.getByText("票面金额未自动识别,需由财务查看原件核对。")).toBeVisible();
|
||||
await expect(page.getByText("这条明细尚无对应票据,暂时无法提交;请添加票据并选择这条明细。")).toBeVisible();
|
||||
await expect(page.getByText("当前申报金额高于已读取的票面金额合计,暂时不能据此通过;请核对明细金额和票据是否完整。")).toBeVisible();
|
||||
await expect(page.getByText("这些票据当前没有明确费用归属,暂时无法提交;请为每张票据选择对应的报销明细。")).toBeVisible();
|
||||
await expect(page.getByText("票面金额需财务查看原件核对;这是一项审批提示,不会阻止提交。")).toBeVisible();
|
||||
await expect(page.getByText(
|
||||
"该发票的票面金额未能读取,暂时无法提交;请重新上传这张发票,或联系财务处理。",
|
||||
)).toBeVisible();
|
||||
@@ -1136,9 +1118,7 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await page.getByLabel("选择报销明细").selectOption("item-missing-r111");
|
||||
state.invoiceItemLinkError = { invoice: "invalid_unlinked_item_state" };
|
||||
await page.getByRole("button", { name: "确认关联" }).click();
|
||||
await expect(
|
||||
page.getByTestId("reimbursement-invoices").getByText("这张发票的关联状态已变化,请刷新后重试。"),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId("approval-unlinked-invoices").getByText("这张发票的关联状态已变化,请刷新后重试。")).toBeVisible();
|
||||
expect(state.invoices.find((invoice) => invoice.id === unlinkedInvoiceId)?.linkedItemId).toBeNull();
|
||||
state.invoiceItemLinkError = null;
|
||||
await page.getByRole("button", { name: "确认关联" }).click();
|
||||
@@ -1149,10 +1129,8 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
invoiceId: unlinkedInvoiceId,
|
||||
linkedItemId: "item-missing-r111",
|
||||
}]);
|
||||
await expect(page.getByText("尚未关联正式发票,请到“报销材料”添加发票并选择本明细。")).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByText("申报金额 ¥1000.00 高于关联发票合计 ¥600.00,请补充发票或调整申报金额。"),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText("这条明细尚无对应票据,暂时无法提交;请添加票据并选择这条明细。")).toHaveCount(0);
|
||||
await expect(page.getByText("当前申报金额高于已读取的票面金额合计,暂时不能据此通过;请核对明细金额和票据是否完整。")).toBeVisible();
|
||||
|
||||
const structuredGuardDetails = state.submitGuardDetails;
|
||||
state.submitGuardDetails = {
|
||||
@@ -1176,10 +1154,10 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto(`/mobile/reimbursements/${reimbursementId}`);
|
||||
await page.getByTestId("reimbursement-submit").click();
|
||||
await expect(page.getByTestId("mobile-reimbursement-invoice-guard-summary")).toContainText("共 4 项需要处理");
|
||||
await expect(page.getByText("尚未关联正式发票,请到“报销材料”添加发票并选择本明细。")).toBeVisible();
|
||||
await expect(page.getByText("这张发票还没有选择报销明细,请在下方完成关联。")).toBeVisible();
|
||||
await expect(page.getByText("票面金额未自动识别,需由财务查看原件核对。")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-action-error")).toContainText("票据检查未通过");
|
||||
await expect(page.getByText("这条明细尚无对应票据,暂时无法提交;请添加票据并选择这条明细。")).toBeVisible();
|
||||
await expect(page.getByText("这些票据当前没有明确费用归属,暂时无法提交;请为每张票据选择对应的报销明细。")).toBeVisible();
|
||||
await expect(page.getByText("票面金额需财务查看原件核对;这是一项审批提示,不会阻止提交。")).toBeVisible();
|
||||
await expect(page.getByText(
|
||||
"该发票的票面金额未能读取,暂时无法提交;请重新上传这张发票,或联系财务处理。",
|
||||
)).toBeVisible();
|
||||
@@ -1217,8 +1195,8 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
try {
|
||||
await login(mobile.page);
|
||||
await mobile.page.goto(`/mobile/reimbursements/${reimbursementId}`);
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-detail")).toBeVisible();
|
||||
await expect(mobile.page.getByRole("heading", { name: "报销材料" })).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(mobile.page.getByRole("heading", { name: "明细与票据" })).toBeVisible();
|
||||
|
||||
const beforeDialogMetrics = await collectVisualMetrics(mobile.page);
|
||||
expect(beforeDialogMetrics.scrollWidth).toBeLessThanOrEqual(beforeDialogMetrics.width + 2);
|
||||
@@ -1276,36 +1254,31 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
).toBeVisible({ timeout: 15_000 });
|
||||
await mobile.page.keyboard.press("Escape");
|
||||
await expect(mobile.page.getByText("北京悦海堂餐饮管理有限公司").first()).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("发票 2 · 补充附件 0");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("数电发票 · 12345678");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("电子火车票 · 12345679");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).not.toContainText("无代码");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).not.toContainText("未识别销售方");
|
||||
const mobileSectionOrder = await mobile.page.locator("[data-testid='mobile-reimbursement-detail-focus'], [data-testid='mobile-reimbursement-items'], [data-testid='mobile-reimbursement-materials'], [data-testid='mobile-reimbursement-processing-section']")
|
||||
.evaluateAll((nodes) => nodes.map((node) => node.getAttribute("data-testid")));
|
||||
expect(mobileSectionOrder).toEqual([
|
||||
"mobile-reimbursement-detail-focus",
|
||||
"mobile-reimbursement-processing-section",
|
||||
"mobile-reimbursement-items",
|
||||
"mobile-reimbursement-materials",
|
||||
]);
|
||||
const mobileItem = mobile.page.locator("article.mobile-list-card").filter({ hasText: "客户招待" }).first();
|
||||
await expect(mobileItem.getByRole("button", { name: /查看关联发票/ })).toHaveCount(0);
|
||||
await expect(mobileItem).not.toContainText("电子火车票");
|
||||
const mobileEvidence = mobile.page.getByTestId("reimbursement-approval-evidence");
|
||||
await expect(mobileEvidence).toContainText("12345678");
|
||||
await expect(mobileEvidence).toContainText("12345679");
|
||||
await expect(mobileEvidence).not.toContainText("无代码");
|
||||
await expect(mobileEvidence).not.toContainText("未识别销售方");
|
||||
const mobileItem = mobile.page.getByTestId("approval-item-evidence").filter({ hasText: "客户招待" }).first();
|
||||
await expect(mobileItem).toContainText("12345678");
|
||||
await expect(mobileItem).not.toContainText("12345679");
|
||||
await expect(mobile.page.getByTestId("approval-unlinked-invoices")).toContainText("12345679");
|
||||
await expect(mobileEvidence.getByRole("button", { name: /查看发票原件/ })).toHaveCount(2);
|
||||
await expect(mobile.page.getByRole("button", { name: "查看原件", exact: true })).toHaveCount(0);
|
||||
|
||||
await mobile.page.getByRole("button", { name: "添加附件" }).click();
|
||||
await mobile.page.getByTestId("mobile-reimbursement-materials").locator("input[type='file']").setInputFiles({
|
||||
const maintenanceSection = mobile.page.locator("section").filter({ has: mobile.page.getByRole("heading", { name: "维护报销内容" }) });
|
||||
await expect(maintenanceSection.getByRole("button", { name: "选择附件" })).toBeVisible();
|
||||
await maintenanceSection.locator("input[type='file']").setInputFiles({
|
||||
name: "付款截图.png",
|
||||
mimeType: "image/png",
|
||||
buffer: Buffer.from("r111 mobile attachment"),
|
||||
});
|
||||
await mobile.page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: "上传附件" }).click();
|
||||
await maintenanceSection.getByRole("button", { name: "上传 1 个附件" }).click();
|
||||
await expect(mobile.page.getByRole("button", { name: "查看补充附件:付款截图.png" })).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-travel-summary")).toContainText("合成差旅概要");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-travel-summary")).toContainText("测试地点");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("发票 2 · 补充附件 1");
|
||||
const openAllButton = mobile.page.getByTestId("mobile-reimbursement-materials-open-all");
|
||||
await expect(mobile.page.getByText("合成差旅概要")).toBeVisible();
|
||||
await expect(mobile.page.getByText("测试地点")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("approval-attachments")).toContainText("补充附件(1)");
|
||||
const openAllButton = mobile.page.getByRole("button", { name: "查看完整材料" });
|
||||
const openAllTarget = await openAllButton.boundingBox();
|
||||
expect(openAllTarget?.height ?? 0).toBeGreaterThanOrEqual(44);
|
||||
await openAllButton.click();
|
||||
|
||||
@@ -424,7 +424,7 @@ test.describe("R5 真机反馈移动交互", () => {
|
||||
expect(listMetrics.scrollWidth).toBeLessThanOrEqual(listMetrics.width + 2);
|
||||
|
||||
await page.goto("/mobile/reimbursements/r5-reimbursement-1");
|
||||
await expect(page.getByTestId("mobile-reimbursement-detail")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-nav-reimbursements")).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "返回" })).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-reimbursement-sticky-actions")).toBeVisible();
|
||||
|
||||
@@ -577,7 +577,7 @@ test.describe("R6 移动响应式体系", () => {
|
||||
await expectMobilePageStable(page, device.label, `${device.key}-mobile-leave`);
|
||||
|
||||
await page.goto("/mobile/reimbursements/r6-responsive-reimbursement");
|
||||
await expect(page.getByTestId("mobile-reimbursement-detail")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
await expect(page.getByTestId("mobile-reimbursement-sticky-actions")).toBeVisible();
|
||||
const detailMetrics = await expectMobilePageStable(page, device.label, `${device.key}-mobile-reimbursement-detail`);
|
||||
|
||||
@@ -30,7 +30,7 @@ const MOBILE_PAGES = [
|
||||
{ key: "home", path: "/mobile", marker: "mobile-action-grid", deep: false },
|
||||
{ key: "approvals", path: "/mobile/approvals", marker: "mobile-approvals-list", deep: false },
|
||||
{ key: "reimbursements", path: "/mobile/reimbursements", marker: "mobile-reimbursements-list", deep: false },
|
||||
{ key: "reimbursement-detail", path: "/mobile/reimbursements/mud6-reimbursement", marker: "mobile-reimbursement-detail", deep: true },
|
||||
{ key: "reimbursement-detail", path: "/mobile/reimbursements/mud6-reimbursement", marker: "reimbursement-mobile-workspace", deep: true },
|
||||
{ key: "purchase-requests", path: "/mobile/purchase-requests", marker: "mobile-purchase-requests-list", deep: false },
|
||||
{ key: "me", path: "/mobile/me", marker: "mobile-more-links", deep: false },
|
||||
{ key: "profile", path: "/mobile/profile", marker: "mobile-profile-page", deep: false },
|
||||
|
||||
@@ -563,7 +563,7 @@ async function exerciseMobileRendering(page: Page, key: string, label: string) {
|
||||
expect(meMetrics.mobileMe.hasDecorativeTopLine, `${label} mobile section top line hidden`).toBe(false);
|
||||
|
||||
await page.goto("/mobile/reimbursements/wcm6-reimbursement");
|
||||
await expect(page.getByTestId("mobile-reimbursement-detail")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
await expect(page.getByTestId("mobile-reimbursement-sticky-actions")).toBeVisible();
|
||||
const detailMetrics = await expectRenderingStable(page, label, `${key}-mobile-reimbursement-detail`);
|
||||
|
||||
@@ -94,7 +94,12 @@ const pendingTaskDetail = {
|
||||
entityType: "reimbursement",
|
||||
currentStatus: "submitted",
|
||||
currentStepId: pendingTask.stepId,
|
||||
contextData: { standardViolations: [] },
|
||||
contextData: {
|
||||
standardViolations: [],
|
||||
budgetCheck: null,
|
||||
budgetCheckStatus: "not_applicable",
|
||||
parseStatus: "ready",
|
||||
},
|
||||
startedAt: "2026-05-05T08:50:00.000Z",
|
||||
completedAt: null,
|
||||
},
|
||||
@@ -115,6 +120,57 @@ const pendingTaskDetail = {
|
||||
history: [],
|
||||
};
|
||||
|
||||
const pendingReimbursementDetail = {
|
||||
...pendingTaskDetail.reimbursement,
|
||||
submitterId: "employee-mock-2",
|
||||
ownerId: "employee-mock-2",
|
||||
departmentId: "dept-1",
|
||||
department: { id: "dept-1", name: "研发" },
|
||||
submittedAt: "2026-05-05T08:50:00.000Z",
|
||||
projectId: null,
|
||||
linkedTravelRequestId: null,
|
||||
linkedTravelRequest: null,
|
||||
invoices: [
|
||||
{
|
||||
id: "invoice-r89-1",
|
||||
status: "bound",
|
||||
invoiceCode: "044001900111",
|
||||
invoiceNumber: "R8900001",
|
||||
invoiceDate: "2026-05-05",
|
||||
sellerName: "测试供应商",
|
||||
totalAmount: "1683.00",
|
||||
originalName: "r89-invoice.pdf",
|
||||
mimeType: "application/pdf",
|
||||
linkedItemId: "item-r89-1",
|
||||
amountEvidenceStatus: "verified",
|
||||
},
|
||||
],
|
||||
attachments: [],
|
||||
budgetHint: null,
|
||||
workflowProgress: { currentStepIndex: 0, totalSteps: 1, steps: [] },
|
||||
processingProgress: { stages: [] },
|
||||
approvalHistory: [],
|
||||
riskFlags: [],
|
||||
items: [
|
||||
{
|
||||
id: "item-r89-1",
|
||||
expenseTypeId: "expense-r89-1",
|
||||
expenseDate: "2026-05-05",
|
||||
amount: "1683.00",
|
||||
description: "R89 移动审批验证",
|
||||
assetPriorApprovalBasis: null,
|
||||
expenseType: {
|
||||
id: "expense-r89-1",
|
||||
code: "TRAVEL",
|
||||
name: "交通费",
|
||||
category: "travel",
|
||||
enabled: true,
|
||||
requiresInvoice: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function navigationItem(key: string, href: string, label: string, icon: string, placement = "main") {
|
||||
return { key, href, label, icon, placement, permissionCodes: [], featureFlagKey: null };
|
||||
}
|
||||
@@ -328,6 +384,11 @@ async function mockR89Apis(page: Page) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === `/api/reimbursements/${pendingReimbursementDetail.id}`) {
|
||||
await fulfillJson(route, pendingReimbursementDetail);
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === `/api/employees/${mockMe.id}/profile`) {
|
||||
await fulfillJson(route, {
|
||||
employee: {
|
||||
@@ -422,7 +483,8 @@ test.describe("R89 移动底部导航与核心页面", () => {
|
||||
await expect(page).toHaveURL(/\/mobile\/approvals/);
|
||||
await expect(page.getByTestId("mobile-approvals-list")).toBeVisible();
|
||||
await page.getByTestId(`mobile-approvals-task-${pendingTask.id}`).click();
|
||||
await expect(page.getByTestId("mobile-approvals-detail")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-approvals-approve")).toBeVisible();
|
||||
await expect(page.getByTestId("mobile-approvals-reject")).toBeVisible();
|
||||
await page.getByTestId("mobile-approvals-approve").click();
|
||||
@@ -430,13 +492,12 @@ test.describe("R89 移动底部导航与核心页面", () => {
|
||||
await page.getByTestId("workflow-action-confirm-cancel").click();
|
||||
await page.context().setOffline(true);
|
||||
await page.evaluate(() => window.dispatchEvent(new Event("offline")));
|
||||
await page.getByTestId("mobile-approvals-approve").click();
|
||||
await expect(page.getByTestId("workflow-action-confirm-dialog")).toContainText("只会保存为待同步草稿");
|
||||
await page.getByTestId("workflow-action-confirm-cancel").click();
|
||||
await expect(page.getByTestId("mobile-approvals-approve")).toBeDisabled();
|
||||
await expect(page.getByTestId("reimbursement-mobile-workspace")).toContainText("报销审批需联网核对完整报销内容");
|
||||
await page.context().setOffline(false);
|
||||
await page.evaluate(() => window.dispatchEvent(new Event("online")));
|
||||
await attachScreenshot(page, testInfo, "r97-mobile-approvals.png");
|
||||
await page.getByTestId("mobile-approvals-detail-header").getByRole("button", { name: "返回" }).click();
|
||||
await page.getByTestId("mobile-reimbursement-detail-header").getByRole("button", { name: "返回" }).click();
|
||||
await expect(page).toHaveURL(/\/mobile\/approvals/);
|
||||
|
||||
await page.getByTestId("mobile-nav-reimbursements").click();
|
||||
|
||||
@@ -315,8 +315,7 @@ test.describe("R7E 审批动作二次确认", () => {
|
||||
await login(page, MANAGER);
|
||||
await page.goto(`/workflow/tasks/${pendingTask.id}`);
|
||||
await expect(page.getByTestId("workflow-task-overview")).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-action-consequence-help")).toContainText("退回:");
|
||||
await expect(page.getByTestId("workflow-action-consequence-help")).toContainText("驳回:");
|
||||
await expect(page.getByTestId("workflow-action-consequence-help")).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("workflow-task-approve").click();
|
||||
await expect(page.getByTestId("workflow-action-confirm-dialog")).toContainText("确认后将进入下一审批节点");
|
||||
@@ -324,13 +323,13 @@ test.describe("R7E 审批动作二次确认", () => {
|
||||
expect(actionCalls.approve).toBe(0);
|
||||
|
||||
await page.getByTestId("workflow-task-reject-toggle").click();
|
||||
await expect(page.getByTestId("workflow-action-consequence-help")).toContainText("流程");
|
||||
await page.getByTestId("workflow-task-reject-comment").fill("资料不符合要求,请重新发起。");
|
||||
await page.getByTestId("workflow-task-reject").click();
|
||||
await expect(page.getByTestId("workflow-action-confirm-dialog")).toContainText("流程将终止");
|
||||
await page.getByTestId("workflow-action-confirm-cancel").click();
|
||||
expect(actionCalls.reject).toBe(0);
|
||||
|
||||
await page.locator("summary", { hasText: "更多" }).click();
|
||||
await page.getByTestId("workflow-task-transfer-menu-item").click();
|
||||
await expect(page.getByTestId("workflow-task-transfer-disabled-reason")).toHaveText("请先选择目标审批人。");
|
||||
await expect(page.getByTestId("workflow-task-transfer")).toBeDisabled();
|
||||
@@ -391,6 +390,12 @@ test.describe("R7E 审批动作二次确认", () => {
|
||||
};
|
||||
const reimbursementDetail = {
|
||||
id: reimbursementId,
|
||||
formNumber: "BX-APPROVAL-001",
|
||||
status: "reviewing",
|
||||
reimbursementDate: "2026-09-01",
|
||||
totalAmount: "180.00",
|
||||
description: "客户拜访交通费",
|
||||
owner: { id: "owner", employeeNo: "E001", name: "申请人" },
|
||||
items: [{
|
||||
id: "item-1", expenseTypeId: "type-1", expenseDate: "2026-09-01", amount: "180.00",
|
||||
description: "客户拜访打车", assetPriorApprovalBasis: null,
|
||||
@@ -417,10 +422,16 @@ test.describe("R7E 审批动作二次确认", () => {
|
||||
await login(page, MANAGER);
|
||||
await page.goto(`/workflow/tasks/${pendingTask.id}`);
|
||||
|
||||
await expect(page.getByRole("heading", { name: "报销单 BX-APPROVAL-001" })).toHaveCount(1);
|
||||
await expect(page.getByText("客户拜访交通费", { exact: true })).toHaveCount(1);
|
||||
await expect(page.getByTestId("reimbursement-approval-evidence")).toContainText("客户拜访打车");
|
||||
await expect(page.getByTestId("approval-unlinked-invoices")).toContainText("FP-002");
|
||||
await expect(page.getByTestId("reimbursement-approval-checks")).toContainText("该次提交未记录预算快照");
|
||||
await expect(page.locator("#reimbursement-workspace-snapshot")).toContainText("该次提交未留存预算快照");
|
||||
await expect(page.getByText("当前申报金额高于已读取的票面金额合计")).toBeVisible();
|
||||
await expect(page.getByTestId("workflow-task-overview")).toHaveCount(0);
|
||||
await expect(page.getByTestId("workflow-task-summary")).toHaveCount(0);
|
||||
await expect(page.getByText("打开完整原始表单")).toHaveCount(0);
|
||||
await expect(page.getByText(/0 条/)).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "编辑明细" })).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "删除明细" })).toHaveCount(0);
|
||||
await expect(page.getByRole("button", { name: "选择明细" })).toHaveCount(0);
|
||||
@@ -452,7 +463,6 @@ test.describe("R7E 审批动作二次确认", () => {
|
||||
await expect(page.getByTestId("workflow-task-approve")).toBeDisabled();
|
||||
await expect(page.getByTestId("workflow-task-return-toggle")).toBeDisabled();
|
||||
await expect(page.getByTestId("workflow-task-reject-toggle")).toBeDisabled();
|
||||
await page.locator("summary", { hasText: "更多" }).click();
|
||||
await expect(page.getByTestId("workflow-task-transfer-menu-item")).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户