feat(assets): add asset metadata views and enforce assignment transitions

This commit is contained in:
2026-06-19 17:14:22 +02:00
parent c1763ed007
commit f32d55a7b0
17 changed files with 1573 additions and 70 deletions
@@ -0,0 +1,123 @@
import { createElement } from "react"
import { renderToStaticMarkup } from "react-dom/server"
import { beforeEach, describe, expect, it, vi } from "vitest"
const mocks = vi.hoisted(() => ({
getI18n: vi.fn(),
findById: vi.fn(),
}))
vi.mock("@/i18n/server", () => ({
getI18n: mocks.getI18n,
}))
vi.mock("@/services/asset.service", () => ({
AssetService: {
findById: mocks.findById,
},
}))
vi.mock("@/components/common/pageheader", () => ({
default: ({ title }: { title: string }) =>
createElement("header", null, title),
}))
vi.mock("@/components/ui/button", () => ({
Button: ({ children }: { children: React.ReactNode }) =>
createElement("button", null, children),
}))
vi.mock("next/link", () => ({
default: ({ href, children }: { href: string; children: React.ReactNode }) =>
createElement("a", { href }, children),
}))
describe("asset detail page", () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.getI18n.mockResolvedValue({
dictionary: {
inventory: {
assets: {
detail: {
title: "Asset Details",
notFound: "Asset not found",
actions: { edit: "Edit asset" },
labels: {
item: "Item",
serialNumber: "Serial Number",
assetTag: "Asset Tag",
manufacturer: "Manufacturer",
model: "Model",
purchaseDate: "Purchase Date",
purchasePrice: "Purchase Price",
warrantyEndsAt: "Warranty Ends At",
deliveryNote: "Delivery Note",
notes: "Notes",
status: "Status",
person: "Person",
},
},
list: {
title: "Assets",
addLabel: "Add Asset",
empty: "No assets found.",
columns: {},
actions: {},
},
new: { title: "New Asset" },
edit: { title: "Edit Asset", notFound: "Asset not found" },
form: {},
status: { AVAILABLE: "Available" },
fallback: { unknownStatus: "Unknown status" },
actions: {},
schema: {},
},
},
common: { submitButton: {} },
},
locale: "en",
})
})
it("renders the asset operational metadata in the detail view", async () => {
mocks.findById.mockResolvedValue({
id: "asset-1",
itemId: "item-1",
serialNumber: "SERIAL-1",
assetTag: "IT-000777",
manufacturer: "Lenovo",
model: "ThinkPad X1",
purchaseDate: new Date("2026-01-15T00:00:00.000Z"),
purchasePrice: 1249.99,
warrantyEndsAt: new Date("2028-01-15T00:00:00.000Z"),
deliveryNote: "DN-1",
notes: "Ready",
status: "AVAILABLE",
item: { name: "Laptop" },
assignment: {
person: {
id: "person-1",
firstName: "Ada",
lastName: "Lovelace",
},
},
})
const { default: AssetDetailPage } = await import(
"@/app/(dashboard)/inventory/assets/[assetId]/page"
)
const html = renderToStaticMarkup(
await AssetDetailPage({ params: Promise.resolve({ assetId: "asset-1" }) }),
)
expect(html).toContain("Asset Details")
expect(html).toContain("Asset Tag")
expect(html).toContain("IT-000777")
expect(html).toContain("Manufacturer")
expect(html).toContain("Lenovo")
expect(html).toContain("Ada Lovelace")
expect(html).toContain("Purchase Price")
})
})
@@ -0,0 +1,119 @@
import { createElement } from "react"
import { renderToStaticMarkup } from "react-dom/server"
import { beforeEach, describe, expect, it, vi } from "vitest"
const mocks = vi.hoisted(() => ({
getI18n: vi.fn(),
findAllWithItemAndCategory: vi.fn(),
}))
vi.mock("@/i18n/server", () => ({
getI18n: mocks.getI18n,
}))
vi.mock("@/services/asset.service", () => ({
AssetService: {
findAllWithItemAndCategory: mocks.findAllWithItemAndCategory,
},
}))
vi.mock("@/components/common/pageheader", () => ({
default: ({ title, link }: { title: string; link: string }) =>
createElement("header", null, title, link),
}))
vi.mock("@/components/common/pagination", () => ({
default: ({ totalPages }: { totalPages: number }) =>
createElement("div", null, `pages:${totalPages}`),
}))
vi.mock("@/components/ui/button", () => ({
Button: ({ children }: { children: React.ReactNode }) =>
createElement("button", null, children),
}))
vi.mock("next/link", () => ({
default: ({ href, children }: { href: string; children: React.ReactNode }) =>
createElement("a", { href }, children),
}))
describe("assets page", () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.getI18n.mockResolvedValue({
dictionary: {
inventory: {
assets: {
list: {
title: "Assets",
addLabel: "Add Asset",
empty: "No assets found.",
columns: {
item: "Item",
category: "Category",
serialNumber: "Serial Number",
assetTag: "Asset Tag",
manufacturer: "Manufacturer",
model: "Model",
purchaseDate: "Purchase Date",
purchasePrice: "Purchase Price",
warrantyEndsAt: "Warranty Ends At",
status: "Status",
actions: "Actions",
},
actions: { view: "View asset", edit: "Edit asset" },
},
new: { title: "New Asset" },
edit: { title: "Edit Asset", notFound: "Asset not found" },
form: {},
status: { AVAILABLE: "Available" },
fallback: { unknownStatus: "Unknown status" },
actions: {},
schema: {},
detail: {
title: "Asset Details",
notFound: "Asset not found",
labels: {},
},
},
},
common: { submitButton: {} },
},
locale: "en",
})
})
it("renders asset operational columns in the list", async () => {
mocks.findAllWithItemAndCategory.mockResolvedValue({
data: [
{
id: "asset-1",
item: { name: "Laptop", category: { name: "Devices" } },
serialNumber: "SERIAL-1",
assetTag: "IT-000777",
manufacturer: "Lenovo",
model: "ThinkPad X1",
purchaseDate: new Date("2026-01-15T00:00:00.000Z"),
purchasePrice: 1249.99,
warrantyEndsAt: new Date("2028-01-15T00:00:00.000Z"),
status: "AVAILABLE",
},
],
totalPages: 1,
})
const { default: AssetsPage } = await import(
"@/app/(dashboard)/inventory/assets/page"
)
const html = renderToStaticMarkup(
await AssetsPage({ searchParams: Promise.resolve({}) }),
)
expect(html).toContain("Asset Tag")
expect(html).toContain("Manufacturer")
expect(html).toContain("Model")
expect(html).toContain("IT-000777")
expect(html).toContain("ThinkPad X1")
})
})