Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/__tests__/api-client.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-require-imports */
import { api } from '@/lib/api';
import axios from 'axios';

Expand Down
67 changes: 67 additions & 0 deletions frontend/__tests__/asset-detail-page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { fireEvent, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import AssetDetailPage from "@/app/(dashboard)/assets/[id]/page";

const push = jest.fn();
const asset = {
id: "asset-1", assetId: "AST-001", name: "MacBook Pro", status: "ACTIVE", condition: "GOOD",
createdAt: "2026-01-01", updatedAt: "2026-01-02", imageUrls: [], tags: [],
};
let mockAsset: typeof asset | undefined = asset;

jest.mock("next/navigation", () => ({
useParams: () => ({ id: "asset-1" }),
useRouter: () => ({ push }),
}));

jest.mock("@/lib/query/hooks/useAsset", () => ({
useAsset: () => ({ data: mockAsset, isLoading: false }),
useAssetHistory: () => ({ data: [], isLoading: false }),
useAssetDocuments: () => ({ data: [], isLoading: false }),
useMaintenanceRecords: () => ({ data: [], isLoading: false }),
useAssetNotes: () => ({ data: [], isLoading: false }),
useDeleteAsset: () => ({ mutate: jest.fn(), isPending: false }),
useUploadDocument: () => ({ mutate: jest.fn(), isPending: false }),
useDeleteDocument: () => ({ mutate: jest.fn(), isPending: false }),
useCreateMaintenanceRecord: () => ({ mutate: jest.fn(), isPending: false }),
useUpdateMaintenanceStatus: () => ({ mutate: jest.fn(), isPending: false }),
useCreateNote: () => ({ mutate: jest.fn(), isPending: false }),
useDeleteNote: () => ({ mutate: jest.fn(), isPending: false }),
}));

jest.mock("@/components/assets/status-badge", () => ({ StatusBadge: () => <span>Active</span> }));
jest.mock("@/components/assets/condition-badge", () => ({ ConditionBadge: () => <span>Good</span> }));
jest.mock("@/components/assets/inline-edit", () => ({ InlineEdit: ({ display }: { display: React.ReactNode }) => <>{display}</> }));
jest.mock("@/components/assets/photo-gallery", () => ({ PhotoGallery: () => <div>Photos</div> }));
jest.mock("@/components/assets/edit-asset-modal", () => ({ EditAssetModal: () => <div role="dialog">Edit Asset Form</div> }));

describe("AssetDetailPage", () => {
beforeEach(() => {
jest.clearAllMocks();
mockAsset = asset;
global.fetch = jest.fn().mockResolvedValue({ ok: false });
});

it("loads the asset and switches across core tabs", () => {
render(<AssetDetailPage />);
expect(screen.getByRole("heading", { name: "MacBook Pro" })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "History" }));
expect(screen.getByText("Change History")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Notes" }));
expect(screen.getByRole("heading", { name: "Add Note" })).toBeInTheDocument();
});

it("exposes edit, transfer, and status-change actions", () => {
render(<AssetDetailPage />);
expect(screen.getByRole("button", { name: /transfer/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /update status/i })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Edit" }));
expect(screen.getByRole("dialog")).toHaveTextContent("Edit Asset Form");
});

it("renders a not-found state for an invalid asset id", () => {
mockAsset = undefined;
render(<AssetDetailPage />);
expect(screen.getByText("Asset not found.")).toBeInTheDocument();
});
});
51 changes: 51 additions & 0 deletions frontend/__tests__/licenses-page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { fireEvent, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import LicensesPage from "@/app/(dashboard)/licenses/page";

const license = {
id: "lic-1",
name: "Design Suite",
vendorId: "Acme",
type: "SUBSCRIPTION",
billingPeriod: "YEARLY",
seatsTotal: 5,
seatsUsed: 2,
cost: 1200,
currency: "USD",
expiryDate: "2026-09-01T00:00:00.000Z",
renewsSoon: true,
};

jest.mock("@/lib/query/hooks/useLicenses", () => ({
useLicenses: () => ({ data: [license], isLoading: false }),
useCreateLicense: () => ({ mutateAsync: jest.fn(), isPending: false }),
useUpdateLicense: () => ({ mutateAsync: jest.fn(), isPending: false }),
useDeleteLicense: () => ({ mutateAsync: jest.fn(), isPending: false }),
useRevealLicenseKey: () => ({ mutateAsync: jest.fn(), isPending: false }),
useLicenseAssignments: () => ({ data: [], isLoading: false }),
useAssignSeat: () => ({ mutateAsync: jest.fn(), isPending: false }),
useUnassignSeat: () => ({ mutate: jest.fn(), isPending: false }),
}));

jest.mock("@/lib/query/hooks/useAssets", () => ({
useUsers: () => ({ data: [{ id: "user-1", name: "Jane User" }] }),
}));

describe("LicensesPage", () => {
it("renders licenses and highlights an upcoming renewal", () => {
render(<LicensesPage />);

expect(screen.getByText("Design Suite")).toBeInTheDocument();
expect(screen.getAllByText(/renewal/i).length).toBeGreaterThan(0);
expect(screen.getByText(/9\/1\/2026|09\/01\/2026/)).toBeInTheDocument();
});

it("opens the seat assignment UI from a license row", () => {
render(<LicensesPage />);
fireEvent.click(screen.getByText("Design Suite"));

expect(screen.getByText("Seat Assignments")).toBeInTheDocument();
expect(screen.getByRole("option", { name: "Jane User" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Assign" })).toBeInTheDocument();
});
});
46 changes: 46 additions & 0 deletions frontend/__tests__/locations-page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { fireEvent, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import LocationsPage from "@/app/(dashboard)/locations/page";

jest.mock("next/navigation", () => ({
useRouter: () => ({ push: jest.fn() }),
}));

const createLocation = jest.fn();

jest.mock("@/lib/query/hooks/useLocations", () => ({
useLocations: () => ({
data: [
{ id: "root", name: "HQ", code: "HQ", type: "BUILDING", totalAssetCount: 2 },
{ id: "child", name: "Floor 1", code: "HQ-1", type: "FLOOR", parentLocationId: "root", totalAssetCount: 1 },
],
isLoading: false,
}),
useCreateLocation: () => ({ mutateAsync: createLocation, isPending: false }),
useUpdateLocation: () => ({ mutateAsync: jest.fn(), isPending: false }),
useDeleteLocation: () => ({ mutateAsync: jest.fn(), isPending: false }),
}));

describe("LocationsPage", () => {
it("renders the location tree and expands child locations", () => {
render(<LocationsPage />);

expect(screen.getByRole("heading", { name: "Locations" })).toBeInTheDocument();
expect(screen.getAllByText("HQ").length).toBeGreaterThan(0);
expect(screen.queryByText("Floor 1")).not.toBeInTheDocument();

fireEvent.click(screen.getByRole("button", { name: "Expand" }));
expect(screen.getByText("Floor 1")).toBeInTheDocument();
});

it("opens create and edit entry points", () => {
render(<LocationsPage />);

fireEvent.click(screen.getByRole("button", { name: "Add Root Location" }));
expect(screen.getByRole("heading", { name: "New Location" })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));

fireEvent.click(screen.getByTitle("Edit location"));
expect(screen.getByRole("heading", { name: "Edit Location" })).toBeInTheDocument();
});
});
49 changes: 49 additions & 0 deletions frontend/__tests__/maintenance-page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { fireEvent, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import MaintenancePage from "@/app/(dashboard)/maintenance/page";

const updateStatus = jest.fn();

jest.mock("@dnd-kit/core", () => ({
DndContext: ({ children }: { children: React.ReactNode }) => <>{children}</>,
DragOverlay: ({ children }: { children: React.ReactNode }) => <>{children}</>,
PointerSensor: class {},
useDroppable: () => ({ setNodeRef: jest.fn(), isOver: false }),
useDraggable: () => ({ attributes: {}, listeners: {}, setNodeRef: jest.fn(), transform: null, isDragging: false }),
useSensor: jest.fn(),
useSensors: jest.fn(),
}));

jest.mock("@/lib/query/hooks/useMaintenance", () => ({
useMaintenanceRecords: () => ({
data: [{ id: "mnt-1", assetId: "asset-1", type: "SCHEDULED", status: "SCHEDULED", title: "Quarterly service", scheduledDate: "2099-01-15", cost: 0, currency: "USD" }],
isLoading: false,
}),
useCreateMaintenanceRecord: () => ({ mutateAsync: jest.fn(), isPending: false }),
useUpdateMaintenanceStatus: () => ({ mutate: updateStatus, isPending: false }),
}));

jest.mock("@/lib/query/hooks/useAssets", () => ({
useAssets: () => ({ data: { data: [{ id: "asset-1", name: "Laptop" }] } }),
useDepartmentsList: () => ({ data: [] }),
}));

describe("MaintenancePage", () => {
it("renders maintenance records and switches to the calendar", () => {
render(<MaintenancePage />);

expect(screen.getByRole("heading", { name: "Maintenance" })).toBeInTheDocument();
expect(screen.getByText("Quarterly service")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Calendar" }));
expect(screen.getByText("Sun")).toBeInTheDocument();
});

it("opens scheduling from the page and exposes completion entry point", () => {
render(<MaintenancePage />);
fireEvent.click(screen.getByRole("button", { name: "New Maintenance" }));
expect(screen.getByRole("heading", { name: "New Maintenance" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Schedule" })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
expect(screen.getByText("Quarterly service")).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions frontend/jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// jest.setup.js
import "@testing-library/jest-dom";
import '@testing-library/jest-dom';
Loading