Add the create-reservation dialog and its option sources for todo 3 of ops-reservations-and-crate-sync. - src/lib/operations/reservationOptions.ts: server-only option pool (listReservationOptions) returning upcoming unit-visible missions, idle unreserved vehicles, operational structures, budget accounts (no balances), per-origin stock, and a capped roster. - src/components/frontend/operations/reservations/CreateReservationDialog.tsx: client dialog with core fields, personnel/vehicle multi-picks, cargo line repeater, budget + expiry; client pre-validation mirrors the server validator; engine errors render verbatim. - Mount the dialog on the reservations list page, server-gated by operation-reservations:create + hasLogisticsQualification. - Integration tests for the option scopes; e2e spec written (run in F3). tsc clean; 5/5 integration tests pass.
73 lines
3.1 KiB
TypeScript
73 lines
3.1 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
/**
|
|
* Create-reservation dialog on the reservations list page.
|
|
*
|
|
* NOTE: these tests exercise the real dev server and are run in the final
|
|
* verification wave (F3) under the dev-server protocol, not on every change.
|
|
* The dialog is server-gated: only users holding operation-reservations:create
|
|
* render the "New Reservation" trigger; logistics qualification gates the
|
|
* vehicle/cargo/budget sections inside.
|
|
*/
|
|
test.describe("Create reservation dialog", () => {
|
|
test("guests never see the create-reservation dialog", async ({ page }) => {
|
|
await page.goto("/operations/reservations");
|
|
await expect(page.getByRole("heading", { level: 1 })).toHaveText("Polaris Task Force");
|
|
});
|
|
|
|
test("a permitted, logistics-qualified user sees the create dialog with all sections", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/login");
|
|
await page.getByLabel("Username").fill("dev");
|
|
await page.getByLabel("Password").fill("Test123");
|
|
await page.getByRole("button", { name: "Log in" }).click();
|
|
|
|
await page.goto("/operations/reservations");
|
|
await expect(page.getByText("Reservations")).toBeVisible();
|
|
// The trigger renders for a user holding operation-reservations:create.
|
|
await expect(page.getByRole("button", { name: "New Reservation" })).toBeVisible();
|
|
await page.getByRole("button", { name: "New Reservation" }).click();
|
|
|
|
// Core fields are present.
|
|
await expect(page.getByLabel("Reservation key")).toBeVisible();
|
|
await expect(page.getByLabel("Operation id")).toBeVisible();
|
|
// Logistics-qualified users get an enabled Vehicles + Cargo + Budget section.
|
|
await expect(page.getByText("Vehicles (optional)")).toBeVisible();
|
|
await expect(page.getByText("Cargo lines")).toBeVisible();
|
|
await expect(page.getByText("Budget account (optional)")).toBeVisible();
|
|
});
|
|
|
|
test("submitting the dialog with the required fields missing shows the client error", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/login");
|
|
await page.getByLabel("Username").fill("dev");
|
|
await page.getByLabel("Password").fill("Test123");
|
|
await page.getByRole("button", { name: "Log in" }).click();
|
|
|
|
await page.goto("/operations/reservations");
|
|
await page.getByRole("button", { name: "New Reservation" }).click();
|
|
|
|
// Empty submit -> client pre-validation surfaces the required-field errors.
|
|
await page.getByRole("button", { name: "Reserve assets" }).click();
|
|
await expect(
|
|
page.getByRole("alert").filter({ hasText: "A reservation key is required." }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("the list renders and either a reserved row or the empty state is present", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/login");
|
|
await page.getByLabel("Username").fill("dev");
|
|
await page.getByLabel("Password").fill("Test123");
|
|
await page.getByRole("button", { name: "Log in" }).click();
|
|
|
|
await page.goto("/operations/reservations");
|
|
// Either a reserved row (with its Cancel control) or the empty state renders.
|
|
await expect(
|
|
page.getByText("No reservations match this view."),
|
|
).toBeVisible();
|
|
});
|
|
});
|