import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { afterEach, expect, test, vi } from "vitest"; import { ReviewWorkbenchDetailScreen } from "@/features/reviews/review-workbench-detail"; const pushMock = vi.fn(); vi.mock("next/navigation", () => ({ useRouter: () => ({ push: pushMock, }), })); function createOrderDetailPayload( orderId = 101, options: { pendingManualConfirm?: boolean } = {}, ) { return { mode: "proxy", data: { orderId, workflowId: `wf-${orderId}`, customerLevel: "mid", serviceMode: "semi_pro", status: "waiting_review", statusMeta: { label: "待审核", tone: "warning", }, currentStep: "review", currentStepLabel: "人工审核", modelId: 101, poseId: 202, garmentAssetId: 303, sceneRefAssetId: 404, currentRevisionAssetId: null, currentRevisionVersion: null, latestRevisionAssetId: null, latestRevisionVersion: null, revisionCount: 0, reviewTaskStatus: "pending", pendingManualConfirm: options.pendingManualConfirm ?? false, createdAt: "2026-03-27T00:00:00Z", updatedAt: "2026-03-27T00:10:00Z", finalAsset: null, finalAssetState: { kind: "business-empty", title: "最终图暂未生成", description: "当前订单还没有可展示的最终结果。", }, assets: [ { id: 11, orderId, type: "fusion", stepName: "fusion", stepLabel: "融合", label: "融合产物", uri: "mock://fusion-preview", metadata: null, createdAt: "2026-03-27T00:09:00Z", isMock: true, }, ], assetGalleryState: { kind: "ready", }, hasMockAssets: true, }, }; } function createWorkflowPayload( orderId = 101, options: { pendingManualConfirm?: boolean } = {}, ) { return { mode: "proxy", data: { orderId, workflowId: `wf-${orderId}`, workflowType: "mid_end", status: "waiting_review", currentRevisionAssetId: null, currentRevisionVersion: null, latestRevisionAssetId: null, latestRevisionVersion: null, revisionCount: 0, reviewTaskStatus: "pending", pendingManualConfirm: options.pendingManualConfirm ?? false, statusMeta: { label: "待审核", tone: "warning", }, currentStep: "review", currentStepLabel: "人工审核", createdAt: "2026-03-27T00:00:00Z", updatedAt: "2026-03-27T00:10:00Z", steps: [ { id: 1, workflowRunId: 9001, name: "review", label: "人工审核", status: "waiting", statusMeta: { label: "等待人工处理", tone: "warning", }, input: null, output: null, errorMessage: null, startedAt: "2026-03-27T00:09:00Z", endedAt: null, containsMockAssets: true, mockAssetUris: ["mock://fusion-preview"], isCurrent: true, isFailed: false, }, ], stepTimelineState: { kind: "ready", }, failureCount: 0, hasMockAssets: true, }, }; } function createFetchMock(options: { pendingManualConfirm?: boolean } = {}) { return vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const url = typeof input === "string" ? input : input.toString(); if (url === "/api/orders/101") { return new Response(JSON.stringify(createOrderDetailPayload(101, options)), { status: 200, headers: { "content-type": "application/json" }, }); } if (url === "/api/workflows/101") { return new Response(JSON.stringify(createWorkflowPayload(101, options)), { status: 200, headers: { "content-type": "application/json" }, }); } if (url === "/api/reviews/101/submit" && init?.method === "POST") { return new Response( JSON.stringify({ mode: "proxy", data: { orderId: 101, workflowId: "wf-101", decision: "approve", decisionMeta: { label: "通过", tone: "success", }, status: "queued", }, }), { status: 200, headers: { "content-type": "application/json" }, }, ); } if (url === "/api/orders/101/revisions" && init?.method === "POST") { return new Response( JSON.stringify({ mode: "proxy", data: { orderId: 101, workflowId: "wf-101", assetId: 501, parentAssetId: 11, rootAssetId: 11, versionNo: 1, reviewTaskStatus: "revision_uploaded", latestRevisionAssetId: 501, revisionCount: 1, }, }), { status: 201, headers: { "content-type": "application/json" }, }, ); } if (url === "/api/reviews/101/confirm-revision" && init?.method === "POST") { return new Response( JSON.stringify({ mode: "proxy", data: { orderId: 101, workflowId: "wf-101", revisionAssetId: 501, decision: "approve", decisionMeta: { label: "通过", tone: "success", }, status: "submitted", }, }), { status: 200, headers: { "content-type": "application/json" }, }, ); } throw new Error(`Unhandled fetch: ${url}`); }); } afterEach(() => { vi.unstubAllGlobals(); pushMock.mockReset(); }); test("requires a comment before rerun_face submission in detail view", async () => { const fetchMock = createFetchMock(); vi.stubGlobal("fetch", fetchMock); render(); await screen.findByRole("heading", { level: 1, name: "订单 #101" }); fireEvent.click(screen.getByRole("button", { name: "重跑 Face" })); expect(await screen.findByText("请填写审核备注")).toBeInTheDocument(); expect(fetchMock).not.toHaveBeenCalledWith( "/api/reviews/101/submit", expect.anything(), ); }); test("submits approve then returns to the workbench list", async () => { const fetchMock = createFetchMock(); vi.stubGlobal("fetch", fetchMock); render(); await screen.findByRole("heading", { level: 1, name: "订单 #101" }); fireEvent.click(screen.getByRole("button", { name: "审核通过" })); await waitFor(() => { expect(fetchMock).toHaveBeenCalledWith( "/api/reviews/101/submit", expect.objectContaining({ method: "POST", }), ); }); await waitFor(() => { expect(pushMock).toHaveBeenCalledWith("/reviews/workbench"); }); }); test("registers a manual revision asset from detail view", async () => { const fetchMock = createFetchMock(); vi.stubGlobal("fetch", fetchMock); render(); await screen.findByRole("heading", { level: 1, name: "订单 #101" }); fireEvent.change(screen.getByLabelText("修订稿 URI"), { target: { value: "mock://manual-revision-v1" }, }); fireEvent.change(screen.getByLabelText("修订说明"), { target: { value: "人工修订第一版" }, }); fireEvent.click(screen.getByRole("button", { name: "登记人工修订稿" })); await waitFor(() => { expect(fetchMock).toHaveBeenCalledWith( "/api/orders/101/revisions", expect.objectContaining({ method: "POST", }), ); }); }); test("confirms an uploaded manual revision and returns to the workbench list", async () => { const fetchMock = createFetchMock({ pendingManualConfirm: true }); vi.stubGlobal("fetch", fetchMock); render(); await screen.findByRole("heading", { level: 1, name: "订单 #101" }); fireEvent.click(screen.getByRole("button", { name: "确认继续流水线" })); await waitFor(() => { expect(fetchMock).toHaveBeenCalledWith( "/api/reviews/101/confirm-revision", expect.objectContaining({ method: "POST", }), ); }); await waitFor(() => { expect(pushMock).toHaveBeenCalledWith("/reviews/workbench"); }); });