diff --git a/apps/webapp/app/components/assets/actions-dropdown.tsx b/apps/webapp/app/components/assets/actions-dropdown.tsx index 4fc2138cc9..256c0ff19b 100644 --- a/apps/webapp/app/components/assets/actions-dropdown.tsx +++ b/apps/webapp/app/components/assets/actions-dropdown.tsx @@ -458,6 +458,7 @@ const ConditionalActionsDropdown = () => { 0 ) ?? 0) } + totalQuantity={asset.quantity} open={isAdjustQuantityDialogOpen} onOpenChange={setIsAdjustQuantityDialogOpen} /> diff --git a/apps/webapp/app/components/assets/quantity-overview-card.tsx b/apps/webapp/app/components/assets/quantity-overview-card.tsx index 5c101470c0..50b3670104 100644 --- a/apps/webapp/app/components/assets/quantity-overview-card.tsx +++ b/apps/webapp/app/components/assets/quantity-overview-card.tsx @@ -214,6 +214,7 @@ export function QuantityOverviewCard({ assetId={assetId} unitOfMeasure={unitOfMeasure} availableQuantity={custodyAvailableQuantity ?? available} + totalQuantity={quantity} trigger={ - - - + {hasTotal && activeTab === "total" ? ( + + ) : ( + <> + + + + + )} diff --git a/apps/webapp/test/components/assets/quick-adjust-dialog.test.tsx b/apps/webapp/test/components/assets/quick-adjust-dialog.test.tsx new file mode 100644 index 0000000000..ac53371865 --- /dev/null +++ b/apps/webapp/test/components/assets/quick-adjust-dialog.test.tsx @@ -0,0 +1,139 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { useFetcher } from "react-router"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { QuickAdjustDialog } from "~/components/assets/quick-adjust-dialog"; + +// why: providing stable Remix hooks for rendering QuickAdjustDialog in isolation +vi.mock("react-router", async () => { + const actual = + await vi.importActual("react-router"); + return { + ...actual, + useFetcher: vi.fn(), + useNavigation: vi.fn(() => ({ state: "idle" })), + }; +}); + +const useFetcherMock = vi.mocked(useFetcher); + +describe("QuickAdjustDialog", () => { + const submitMock = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + useFetcherMock.mockReturnValue({ + Form: ({ children, ...props }: any) =>
{children}
, + state: "idle", + data: null, + formData: null, + submit: submitMock, + } as any); + }); + + it("renders adjust by amount by default and shows tabs when totalQuantity is provided", () => { + render( + {}} + /> + ); + + expect(screen.getByText("Adjust by amount")).toBeInTheDocument(); + expect(screen.getByText("Set new total")).toBeInTheDocument(); + expect(screen.getByLabelText(/^Quantity \(/)).toBeInTheDocument(); + expect(screen.queryByLabelText(/New Total/)).not.toBeInTheDocument(); + }); + + it("switches to set new total tab when clicked", async () => { + const user = userEvent.setup(); + render( + {}} + /> + ); + + await user.click(screen.getByText("Set new total")); + + expect(screen.getByText("Current total:")).toBeInTheDocument(); + expect(screen.getByText("10 units")).toBeInTheDocument(); + expect(screen.getByLabelText(/New Total Quantity/)).toBeInTheDocument(); + }); + + it("submits the correct restock delta when setting a larger total", async () => { + const user = userEvent.setup(); + render( + {}} + /> + ); + + await user.click(screen.getByText("Set new total")); + const input = screen.getByPlaceholderText("Enter new total quantity"); + await user.type(input, "15"); + await user.click(screen.getByText("Set Total")); + + expect(submitMock).toHaveBeenCalled(); + const submittedData = submitMock.mock.calls[0][0]; + expect(submittedData.get("quantity")).toBe("5"); + expect(submittedData.get("direction")).toBe("add"); + expect(submittedData.get("category")).toBe("RESTOCK"); + }); + + it("submits the correct loss delta when setting a smaller total", async () => { + const user = userEvent.setup(); + render( + {}} + /> + ); + + await user.click(screen.getByText("Set new total")); + const input = screen.getByPlaceholderText("Enter new total quantity"); + await user.type(input, "8"); + await user.click(screen.getByText("Set Total")); + + expect(submitMock).toHaveBeenCalled(); + const submittedData = submitMock.mock.calls[0][0]; + expect(submittedData.get("quantity")).toBe("2"); + expect(submittedData.get("direction")).toBe("subtract"); + expect(submittedData.get("category")).toBe("LOSS"); + }); + + it("fails validation if trying to reduce total by more than available quantity", async () => { + const user = userEvent.setup(); + render( + {}} + /> + ); + + await user.click(screen.getByText("Set new total")); + const input = screen.getByPlaceholderText("Enter new total quantity"); + await user.type(input, "3"); // diff is 7, but available is 5 + await user.click(screen.getByText("Set Total")); + + expect(submitMock).not.toHaveBeenCalled(); + expect(screen.getByText(/Cannot reduce total to 3/)).toBeInTheDocument(); + }); +});