feat(assignments): partial return action and ReturnButton modal
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
// @vitest-environment jsdom
|
||||
import "@testing-library/jest-dom/vitest"
|
||||
import { cleanup, render, screen } from "@testing-library/react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
returnAssignment: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
toastError: vi.fn(),
|
||||
toastSuccess: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("@/actions/assignment.actions", () => ({
|
||||
returnAssignment: mocks.returnAssignment,
|
||||
}))
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ refresh: mocks.refresh }),
|
||||
}))
|
||||
|
||||
vi.mock("sonner", () => ({
|
||||
toast: { error: mocks.toastError, success: mocks.toastSuccess },
|
||||
}))
|
||||
|
||||
import ReturnButton from "@/app/(dashboard)/assignments/_components/return.button"
|
||||
|
||||
const mockCopy = {
|
||||
title: "Devolver artículo",
|
||||
quantity: "Cantidad",
|
||||
quantityPlaceholder: "1",
|
||||
notes: "Notas",
|
||||
notesPlaceholder: "Notas opcionales",
|
||||
submit: "Devolver",
|
||||
cancel: "Cancelar",
|
||||
maxQuantity: "Máximo: {max}",
|
||||
errorConcurrent:
|
||||
"La devolución fue modificada por otro usuario. Recarga e inténtalo de nuevo.",
|
||||
errorGeneric: "Error al procesar la devolución",
|
||||
}
|
||||
|
||||
describe("ReturnButton", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it("opens the dialog and submits a partial return", async () => {
|
||||
mocks.returnAssignment.mockResolvedValue({
|
||||
success: true,
|
||||
message: "Returned",
|
||||
})
|
||||
|
||||
render(
|
||||
<ReturnButton
|
||||
assignmentId="assignment-1"
|
||||
ariaLabel="Return assignment"
|
||||
assignmentLineId="line-1"
|
||||
remainingQuantity={5}
|
||||
copy={mockCopy}
|
||||
/>,
|
||||
)
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", { name: "Return assignment" }),
|
||||
)
|
||||
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole("heading", { name: mockCopy.title }),
|
||||
).toBeInTheDocument()
|
||||
|
||||
const quantityInput = screen.getByRole("spinbutton", {
|
||||
name: mockCopy.quantity,
|
||||
})
|
||||
await userEvent.clear(quantityInput)
|
||||
await userEvent.type(quantityInput, "2")
|
||||
|
||||
const notesInput = screen.getByRole("textbox", { name: mockCopy.notes })
|
||||
await userEvent.type(notesInput, "Slightly damaged")
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: mockCopy.submit }))
|
||||
|
||||
expect(mocks.returnAssignment).toHaveBeenCalledWith({
|
||||
id: "assignment-1",
|
||||
returns: [
|
||||
{
|
||||
assignmentLineId: "line-1",
|
||||
quantity: 2,
|
||||
notes: "Slightly damaged",
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith("Returned")
|
||||
})
|
||||
|
||||
it("disables submit and shows max quantity when quantity exceeds remaining", async () => {
|
||||
render(
|
||||
<ReturnButton
|
||||
assignmentId="assignment-1"
|
||||
ariaLabel="Return assignment"
|
||||
assignmentLineId="line-1"
|
||||
remainingQuantity={3}
|
||||
copy={mockCopy}
|
||||
/>,
|
||||
)
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", { name: "Return assignment" }),
|
||||
)
|
||||
|
||||
const quantityInput = screen.getByRole("spinbutton", {
|
||||
name: mockCopy.quantity,
|
||||
})
|
||||
await userEvent.clear(quantityInput)
|
||||
await userEvent.type(quantityInput, "4")
|
||||
|
||||
expect(screen.getByRole("button", { name: mockCopy.submit })).toBeDisabled()
|
||||
expect(screen.getByText("Máximo: 3")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("shows concurrency error when the action returns one", async () => {
|
||||
mocks.returnAssignment.mockResolvedValue({
|
||||
success: false,
|
||||
errors: { error: ["errorConcurrent"] },
|
||||
message: "Error returning assignment",
|
||||
})
|
||||
|
||||
render(
|
||||
<ReturnButton
|
||||
assignmentId="assignment-1"
|
||||
ariaLabel="Return assignment"
|
||||
assignmentLineId="line-1"
|
||||
remainingQuantity={5}
|
||||
copy={mockCopy}
|
||||
/>,
|
||||
)
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", { name: "Return assignment" }),
|
||||
)
|
||||
|
||||
const quantityInput = screen.getByRole("spinbutton", {
|
||||
name: mockCopy.quantity,
|
||||
})
|
||||
await userEvent.clear(quantityInput)
|
||||
await userEvent.type(quantityInput, "2")
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: mockCopy.submit }))
|
||||
|
||||
expect(screen.getByText(mockCopy.errorConcurrent)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("opens confirm dialog and submits legacy full return without quantity input", async () => {
|
||||
mocks.returnAssignment.mockResolvedValue({
|
||||
success: true,
|
||||
message: "Returned",
|
||||
})
|
||||
|
||||
render(
|
||||
<ReturnButton
|
||||
assignmentId="assignment-1"
|
||||
ariaLabel="Return assignment"
|
||||
copy={mockCopy}
|
||||
/>,
|
||||
)
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", { name: "Return assignment" }),
|
||||
)
|
||||
|
||||
expect(screen.queryByRole("spinbutton")).not.toBeInTheDocument()
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: mockCopy.submit }))
|
||||
|
||||
expect(mocks.returnAssignment).toHaveBeenCalledWith({ id: "assignment-1" })
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user