mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-03 19:03:15 +00:00
Wire the backend's GET uplifts/can-approve tier decision into the pending row and detail modal actions. Approve/Reject now stay disabled with the plain 'Requires Tier N role' tooltip unless the backend says the current user may decide at that tier, so roles the backend refuses (Dispatcher) never get a clickable action or a refusal toast.
163 lines
5.8 KiB
TypeScript
163 lines
5.8 KiB
TypeScript
import { fireEvent, screen } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { UpliftQueueItem, UpliftsQueueResult } from "@/domain/uplifts/types/uplift";
|
|
import { renderWithProviders } from "@/test/test-utils";
|
|
|
|
const rejectMutate = vi.fn();
|
|
const approveMutate = vi.fn();
|
|
const revokeMutate = vi.fn();
|
|
const refetch = vi.fn();
|
|
|
|
const queueState = vi.hoisted(() => ({
|
|
data: null as UpliftsQueueResult | null,
|
|
}));
|
|
const canApproveState = vi.hoisted(() => ({ data: true as boolean | undefined }));
|
|
|
|
const pendingItem: UpliftQueueItem = {
|
|
id: 41,
|
|
status: "Pending",
|
|
currentNTE: 100,
|
|
requestedNTE: 250,
|
|
delta: 150,
|
|
vendorReason: "Extra parts",
|
|
requestedAt: "2026-01-15T10:00:00Z",
|
|
requestedByVendorName: "Gateway",
|
|
decidedAt: "",
|
|
decidedByName: "",
|
|
decisionNote: "",
|
|
requiredTier: 1,
|
|
canDecide: true,
|
|
expiresAt: "",
|
|
notificationStatus: "",
|
|
notificationError: "",
|
|
evidenceDocumentId: null,
|
|
evidenceFileName: "",
|
|
evidenceContentType: "",
|
|
evidenceSizeBytes: null,
|
|
dispatchNumber: "DSP-41",
|
|
poNumber: "PO-41",
|
|
vendorCompanyName: "Gateway Plumbing",
|
|
workOrderId: 99,
|
|
dispatchId: 7,
|
|
woNumber: "WO-99",
|
|
site: "Site A",
|
|
serviceName: "Plumbing repair",
|
|
attachmentCount: null,
|
|
approvedOnWoAuto: null,
|
|
approvedOnWoAdmin: null,
|
|
approvedOnWoTotal: 100,
|
|
workOrderClosed: false,
|
|
};
|
|
|
|
vi.mock("@/domain/uplifts/use-cases/use-uplifts-queue", () => ({
|
|
useUpliftsQueue: () => ({
|
|
data: queueState.data,
|
|
isLoading: false,
|
|
isFetching: false,
|
|
error: null,
|
|
refetch,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/domain/uplifts/use-cases/use-uplift-actions", () => ({
|
|
useApproveUplift: () => ({ isPending: false, mutate: approveMutate }),
|
|
useRejectUplift: () => ({ isPending: false, mutate: rejectMutate }),
|
|
useRevokeUplift: () => ({ isPending: false, mutate: revokeMutate }),
|
|
}));
|
|
|
|
vi.mock("@/providers/auth-context", async (importOriginal) => ({
|
|
...(await importOriginal<object>()),
|
|
useAuthContext: () => ({ user: { userRoles: "Admin" } }),
|
|
}));
|
|
|
|
vi.mock("@/domain/uplifts/use-cases/use-uplifts-can-approve", () => ({
|
|
useUpliftsCanApprove: () => ({ data: canApproveState.data }),
|
|
}));
|
|
|
|
describe("Uplift Approvals decision flow", () => {
|
|
beforeEach(() => {
|
|
rejectMutate.mockReset();
|
|
approveMutate.mockReset();
|
|
revokeMutate.mockReset();
|
|
refetch.mockReset();
|
|
canApproveState.data = true;
|
|
queueState.data = {
|
|
items: [pendingItem],
|
|
totalCount: 1,
|
|
page: 1,
|
|
pageSize: 25,
|
|
pendingExposureTotal: 250,
|
|
};
|
|
});
|
|
|
|
it("shows the Uplift Approvals heading, count pill and exposure total", async () => {
|
|
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
|
|
renderWithProviders(<UpliftQueuePage />);
|
|
|
|
expect(screen.getByRole("heading", { name: "Uplift Approvals" })).toBeInTheDocument();
|
|
expect(screen.getByLabelText("1 pending uplifts")).toBeInTheDocument();
|
|
expect(screen.getByText("Pending exposure: $250.00")).toBeInTheDocument();
|
|
});
|
|
|
|
it("approves in one click without a confirmation dialog", async () => {
|
|
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
|
|
renderWithProviders(<UpliftQueuePage />);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Approve" }));
|
|
expect(approveMutate).toHaveBeenCalledWith({ id: 41, amount: 150, woNumber: "WO-99" });
|
|
expect(screen.queryByRole("button", { name: "Confirm" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("opens RejectDialog with the dispatcher copy and does not reject on Cancel", async () => {
|
|
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
|
|
renderWithProviders(<UpliftQueuePage />);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Reject" }));
|
|
expect(screen.getByRole("heading", { name: /reject uplift of \$250/i })).toBeInTheDocument();
|
|
expect(screen.getByText("The dispatcher sees this reason on WO #WO-99.")).toBeInTheDocument();
|
|
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
expect(rejectMutate).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("shows the pending empty copy when nothing is waiting", async () => {
|
|
queueState.data = {
|
|
items: [],
|
|
totalCount: 0,
|
|
page: 1,
|
|
pageSize: 25,
|
|
pendingExposureTotal: null,
|
|
};
|
|
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
|
|
renderWithProviders(<UpliftQueuePage />);
|
|
|
|
expect(screen.getByText("No uplifts waiting for approval.")).toBeInTheDocument();
|
|
});
|
|
|
|
it("disables Approve and Reject for a Dispatcher the backend refuses, without dialogs or toasts", async () => {
|
|
canApproveState.data = false;
|
|
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
|
|
renderWithProviders(<UpliftQueuePage />);
|
|
|
|
const approve = screen.getByRole("button", { name: "Approve" });
|
|
const reject = screen.getByRole("button", { name: "Reject" });
|
|
expect(approve).toBeDisabled();
|
|
expect(reject).toBeDisabled();
|
|
fireEvent.mouseOver(approve);
|
|
expect(await screen.findByText("Requires Tier 1 role")).toBeInTheDocument();
|
|
fireEvent.click(approve);
|
|
fireEvent.click(reject);
|
|
expect(approveMutate).not.toHaveBeenCalled();
|
|
expect(rejectMutate).not.toHaveBeenCalled();
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("heading", { name: /reject uplift of/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("keeps Approve and Reject enabled when the backend allows the tier", async () => {
|
|
const { default: UpliftQueuePage } = await import("@/app/(protected)/uplifts/index");
|
|
renderWithProviders(<UpliftQueuePage />);
|
|
|
|
expect(screen.getByRole("button", { name: "Approve" })).toBeEnabled();
|
|
expect(screen.getByRole("button", { name: "Reject" })).toBeEnabled();
|
|
});
|
|
});
|