import { describe, expect, it, vi } from "vitest"; import type { Page } from "playwright"; import { fillCargoStep } from "@/workers/rpa/kernel/steps/cargo-step"; vi.mock("@/workers/rpa/fill-cargo-in-drawer", () => ({ fillCargoInDrawer: vi.fn().mockResolvedValue({ weightDisplay: { displayValue: "500", unit: "lb", weightLbPerPallet: 500, }, expected: { palletCount: 2, weight: { displayValue: "500", unit: "lb", weightLbPerPallet: 500, palletCount: 2, }, }, }), })); vi.mock("@/workers/rpa/cargo-lock", () => ({ assertBothAddressesLocked: vi.fn().mockResolvedValue(undefined), settleAddressFieldsForCargo: vi.fn().mockResolvedValue(undefined), waitForCargoCommittedState: vi.fn().mockResolvedValue({ hasSummaryChip: true, visibleEditableCargoInputs: 0, editorOpen: false, internalPalletCount: 2, internalWeight: 500, widgetTextPreview: "", }), })); const stateCalls: string[] = []; function mockCtx() { const page = { waitForTimeout: vi.fn().mockResolvedValue(undefined), locator: vi.fn().mockReturnValue({ last: vi.fn().mockReturnValue({ locator: vi.fn().mockReturnValue({ first: vi.fn() }), }), filter: vi.fn().mockReturnValue({ nth: vi.fn() }), first: vi.fn(), }), url: vi.fn().mockReturnValue("https://example.com"), } as unknown as Page; return { page, selectors: { specs: vi.fn().mockReturnValue(["input"]), optionalEnv: vi.fn().mockReturnValue(undefined), resolve: vi.fn().mockReturnValue("#widget"), widget: vi.fn(), }, exec: { evaluateOnLocator: vi.fn().mockResolvedValue(false), }, runtime: { evaluateOnLocator: vi.fn(), }, getWidgetScrollAt: vi.fn().mockReturnValue(0), setWidgetScrollAt: vi.fn(), state: { triggerCargoSection: vi.fn().mockResolvedValue(undefined), settleAddressEditing: vi.fn(async () => { stateCalls.push("settle"); }), }, }; } describe("fillCargoStep UI flow", () => { it("顺序:fillCargoInDrawer → waitForCargoCommittedState", async () => { const { fillCargoInDrawer } = await import("@/workers/rpa/fill-cargo-in-drawer"); const { waitForCargoCommittedState } = await import("@/workers/rpa/cargo-lock"); const ctx = mockCtx() as never; await fillCargoStep(ctx, { cargoHash: "t", pickup: {} as never, delivery: {} as never, weightLb: 500, dimsIn: { l: 48, w: 40, h: 48 }, palletCount: 2, cargoType: "general_freight", }); expect(fillCargoInDrawer).toHaveBeenCalled(); expect(waitForCargoCommittedState).toHaveBeenCalled(); }); it("fillCargoInDrawer 失败时向上抛出", async () => { const { fillCargoInDrawer } = await import("@/workers/rpa/fill-cargo-in-drawer"); const { RpaError } = await import("@/modules/rpa/errors"); vi.mocked(fillCargoInDrawer).mockRejectedValueOnce( new RpaError("STRUCT_CHANGE", "货物 drawer 内未找到任何重量输入框", { retryable: true, }), ); const ctx = mockCtx() as never; await expect( fillCargoStep(ctx, { cargoHash: "t", pickup: {} as never, delivery: {} as never, weightLb: 500, dimsIn: { l: 48, w: 40, h: 48 }, palletCount: 2, cargoType: "general_freight", }), ).rejects.toMatchObject({ code: "STRUCT_CHANGE" }); }); });