Files
auto-virtual-tryon-frontend/tests/features/reviews/review-workbench-detail.test.tsx
2026-03-28 00:22:08 +08:00

326 lines
8.8 KiB
TypeScript

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("renders a sticky summary with grouped decision panels", async () => {
const fetchMock = createFetchMock();
vi.stubGlobal("fetch", fetchMock);
render(<ReviewWorkbenchDetailScreen orderId={101} />);
expect(await screen.findByRole("link", { name: "返回审核列表" })).toBeInTheDocument();
expect(screen.getByText("审核动作")).toBeInTheDocument();
expect(screen.getByText("人工修订")).toBeInTheDocument();
});
test("requires a comment before rerun_face submission in detail view", async () => {
const fetchMock = createFetchMock();
vi.stubGlobal("fetch", fetchMock);
render(<ReviewWorkbenchDetailScreen orderId={101} />);
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(<ReviewWorkbenchDetailScreen orderId={101} />);
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(<ReviewWorkbenchDetailScreen orderId={101} />);
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(<ReviewWorkbenchDetailScreen orderId={101} />);
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");
});
});