feat(assignments): remaining quantity display and partial return i18n

This commit is contained in:
2026-06-25 21:42:09 +02:00
parent 2c03cd4d66
commit b401f254ec
7 changed files with 298 additions and 14 deletions
@@ -11,6 +11,7 @@ const mocks = vi.hoisted(() => ({
returnAssignment: vi.fn(),
toastError: vi.fn(),
toastSuccess: vi.fn(),
ReturnButton: vi.fn(),
}))
vi.mock("@/i18n/server", () => ({
@@ -27,6 +28,10 @@ vi.mock("@/actions/assignment.actions", () => ({
returnAssignment: mocks.returnAssignment,
}))
vi.mock("@/app/(dashboard)/assignments/_components/return.button", () => ({
default: mocks.ReturnButton,
}))
vi.mock("next/navigation", () => ({
useRouter: () => ({
refresh: mocks.refresh,
@@ -58,6 +63,27 @@ describe("assignment pages localization", () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.getI18n.mockResolvedValue({ dictionary: es, locale: "es" })
mocks.ReturnButton.mockImplementation(
({
assignmentId,
ariaLabel,
assignmentLineId,
remainingQuantity,
}: {
assignmentId: string
ariaLabel: string
assignmentLineId?: string
remainingQuantity?: number
}) =>
createElement("button", {
type: "button",
"data-assignment-id": assignmentId,
"data-aria-label": ariaLabel,
"data-assignment-line-id": assignmentLineId,
"data-remaining-quantity": remainingQuantity,
"aria-label": ariaLabel,
}),
)
})
it("renders the assignment list in Spanish with localized action labels and unchanged assignment data", async () => {
@@ -70,6 +96,7 @@ describe("assignment pages localization", () => {
{
id: "assignment-1",
quantity: 2,
status: "OPEN",
person: {
id: "person-1",
firstName: "Ada",
@@ -102,7 +129,85 @@ describe("assignment pages localization", () => {
expect(html).toContain("Laptop")
expect(html).toContain("No disponible")
expect(html).toContain('aria-label="Editar asignación"')
expect(html).toContain('aria-label="Devolver asignación"')
expect(html).toContain('data-aria-label="Devolver asignación"')
expect(html).toContain('data-assignment-id="assignment-1"')
})
it("renders the full quantity for OPEN rows and omits partial-return props", async () => {
const { default: AssignmentsPage } = await import(
"@/app/(dashboard)/assignments/page"
)
mocks.findAllWithPersonPaginated.mockResolvedValue({
data: [
{
id: "assignment-1",
quantity: 5,
status: "OPEN",
assignmentLineId: "line-1",
remainingQuantity: 5,
person: {
id: "person-1",
firstName: "Ada",
lastName: "Lovelace",
},
item: {
id: "item-1",
name: "Laptop",
},
asset: {
serialNumber: null,
},
},
],
totalPages: 1,
})
const html = renderToStaticMarkup(
await AssignmentsPage({ searchParams: Promise.resolve({}) }),
)
expect(html).toContain('data-assignment-line-id="line-1"')
expect(html).toContain('data-remaining-quantity="5"')
expect(html).not.toContain("Restantes")
})
it("renders the remaining quantity for PARTIALLY_RETURNED rows", async () => {
const { default: AssignmentsPage } = await import(
"@/app/(dashboard)/assignments/page"
)
mocks.findAllWithPersonPaginated.mockResolvedValue({
data: [
{
id: "assignment-1",
quantity: 5,
status: "PARTIALLY_RETURNED",
assignmentLineId: "line-1",
remainingQuantity: 3,
person: {
id: "person-1",
firstName: "Ada",
lastName: "Lovelace",
},
item: {
id: "item-1",
name: "Laptop",
},
asset: {
serialNumber: null,
},
},
],
totalPages: 1,
})
const html = renderToStaticMarkup(
await AssignmentsPage({ searchParams: Promise.resolve({}) }),
)
expect(html).toContain("Restantes")
expect(html).toContain("3 de 5")
})
it("renders the localized assignment empty state when no assignments exist", async () => {
+34
View File
@@ -479,6 +479,23 @@ describe("i18n dictionaries", () => {
assetIdRequired: "Asset ID is required when item ID is provided",
idRequired: "Assignment ID is required",
},
partialReturn: {
title: "Return item",
quantity: "Quantity",
quantityPlaceholder: "1",
notes: "Notes",
notesPlaceholder: "Optional notes",
submit: "Return",
cancel: "Cancel",
maxQuantity: "Maximum: {max}",
errorConcurrent:
"This return was modified by another user. Reload and try again.",
errorGeneric: "Error processing the return",
},
remaining: {
label: "Remaining",
value: "{remaining} of {total}",
},
})
expect(getDictionary("es").inventory.assignments).toEqual({
@@ -544,6 +561,23 @@ describe("i18n dictionaries", () => {
"El activo es obligatorio cuando se especifica el artículo",
idRequired: "El ID de asignación es obligatorio",
},
partialReturn: {
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",
},
remaining: {
label: "Restantes",
value: "{remaining} de {total}",
},
})
})