Replaces the hardcoded PROJECT_CODES array with an admin-managed `ProjectCode` model, mirroring the Delivery Locations pattern (PR #100): - ProjectCode model (unique `code` + isActive) + migration seeding the five previously-hardcoded codes; PO.projectCode stays a free-text snapshot (no FK) so history/exports/imports are unchanged. - manage_project_codes permission (Manager + SuperUser + Admin). - /admin/project-codes CRUD screen (table + Add/Edit + activate/delete) and an Administration sidebar link. - ProjectCodeField now takes `options` from the active codes; the three PO forms + pages fetch them from the DB. Static list removed. - Unit test reworked to the options API; CRUD integration test added; documented in App/CLAUDE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
55 lines
2.4 KiB
TypeScript
55 lines
2.4 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { ProjectCodeField } from "@/components/po/project-code-field";
|
|
|
|
const OPTIONS = ["Petronet LNG Cochin", "Haldia Reach", "COMACOE Mandvi"];
|
|
|
|
function options(container: HTMLElement) {
|
|
return Array.from(container.querySelectorAll("option")).map((o) => ({
|
|
value: o.getAttribute("value"),
|
|
text: o.textContent,
|
|
}));
|
|
}
|
|
|
|
describe("ProjectCodeField", () => {
|
|
it("renders a select named projectCode with an empty option + every supplied code", () => {
|
|
const { container } = render(<ProjectCodeField options={OPTIONS} />);
|
|
const select = container.querySelector("select");
|
|
expect(select?.getAttribute("name")).toBe("projectCode");
|
|
|
|
const opts = options(container);
|
|
// empty "none" option first, then exactly the supplied codes
|
|
expect(opts[0].value).toBe("");
|
|
expect(opts.slice(1).map((o) => o.value)).toEqual(OPTIONS);
|
|
});
|
|
|
|
it("selects a current value that is one of the options (no duplicate option)", () => {
|
|
const { container } = render(<ProjectCodeField options={OPTIONS} current="Haldia Reach" />);
|
|
const select = container.querySelector("select") as HTMLSelectElement;
|
|
expect(select.value).toBe("Haldia Reach");
|
|
// only the options + empty option — no extra "(current)" entry
|
|
expect(container.querySelectorAll("option")).toHaveLength(OPTIONS.length + 1);
|
|
});
|
|
|
|
it("preserves a legacy current value not in the list as a leading (current) option", () => {
|
|
const { container } = render(<ProjectCodeField options={OPTIONS} current="Legacy Project X" />);
|
|
const select = container.querySelector("select") as HTMLSelectElement;
|
|
expect(select.value).toBe("Legacy Project X");
|
|
expect(screen.getByText("Legacy Project X (current)")).toBeInTheDocument();
|
|
// empty + (current) + options
|
|
expect(container.querySelectorAll("option")).toHaveLength(OPTIONS.length + 2);
|
|
});
|
|
|
|
it("defaults to the empty option when no current value is given", () => {
|
|
const { container } = render(<ProjectCodeField options={OPTIONS} current={null} />);
|
|
const select = container.querySelector("select") as HTMLSelectElement;
|
|
expect(select.value).toBe("");
|
|
});
|
|
|
|
it("renders just the empty option when no codes are configured", () => {
|
|
const { container } = render(<ProjectCodeField options={[]} />);
|
|
const opts = options(container);
|
|
expect(opts).toHaveLength(1);
|
|
expect(opts[0].value).toBe("");
|
|
});
|
|
});
|