feat: bootstrap auto virtual tryon admin frontend
This commit is contained in:
314
tests/features/reviews/review-workbench-detail.test.tsx
Normal file
314
tests/features/reviews/review-workbench-detail.test.tsx
Normal file
@@ -0,0 +1,314 @@
|
||||
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(<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");
|
||||
});
|
||||
});
|
||||
62
tests/features/reviews/review-workbench-list.test.tsx
Normal file
62
tests/features/reviews/review-workbench-list.test.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { afterEach, expect, test, vi } from "vitest";
|
||||
|
||||
import { ReviewWorkbenchListScreen } from "@/features/reviews/review-workbench-list";
|
||||
|
||||
function createPendingPayload() {
|
||||
return {
|
||||
mode: "proxy",
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
reviewTaskId: 301,
|
||||
orderId: 101,
|
||||
workflowId: "wf-101",
|
||||
workflowType: "mid_end",
|
||||
currentStep: "review",
|
||||
currentStepLabel: "人工审核",
|
||||
createdAt: "2026-03-27T00:00:00Z",
|
||||
status: "waiting_review",
|
||||
statusMeta: {
|
||||
label: "待审核",
|
||||
tone: "warning",
|
||||
},
|
||||
hasMockAssets: true,
|
||||
failureCount: 2,
|
||||
},
|
||||
],
|
||||
state: {
|
||||
kind: "ready",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
test("renders medium-density list rows that link into independent review detail pages", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue(
|
||||
new Response(JSON.stringify(createPendingPayload()), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
render(<ReviewWorkbenchListScreen />);
|
||||
|
||||
expect(await screen.findByText("审核目标 #101")).toBeInTheDocument();
|
||||
expect(screen.getByText(/工作流 wf-101/)).toBeInTheDocument();
|
||||
expect(screen.getByText("Mock 资产")).toBeInTheDocument();
|
||||
expect(screen.getByText("失败 2")).toBeInTheDocument();
|
||||
expect(screen.queryByText("审核动作面板")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("link", { name: /审核目标 #101/ })).toHaveAttribute(
|
||||
"href",
|
||||
"/reviews/workbench/101",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user