Build Seth's changes C1 to C11 (Gitea issues 1, 8, 18, 23, 24, 25, 27, 31, 32, 33, 36)
- C1 (issue 1): over target follows VISUAL ticket hours, not the timer. - C2 (issue 24): a Q.C. reject always needs a reason; accept needs an NCR number after a reject without one. - C3 (issue 25): an incomplete unit ITP blocks filing. - C4 (issue 27): prints open the print dialog on the page, no new tab. - C5 (issue 31): filing is blocked while VISUAL shows a work order open; the feed sets vclosed on VISUAL packets. - C6 (issue 8): the data book leaves out the history pages. - C7 (issue 18): the supervisor approves each received short; accept and filing wait for it. - C8 (issue 23): per-department checklists, edited by the department supervisor or Admin, frozen at START. - C9 (issue 32): Q.C. and Clerical may run the stamp check. - C10 (issue 33): the PM screen and reports are for PS-ProjectMgmt and Admin only. - C11 (issue 36): the Stall screen lists the packets assigned to the welder, at the supervisor's stall. Migration 0011. Docs: PLAN (also issue 9 confirmed, issue 4 asked), POLICY, SECURITY, SCHEMA, FEED, README, CHANGELOG. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,43 +1,32 @@
|
||||
import { fireEvent, screen, waitFor, within } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ShopCard, StallAnswer, StationOption } from "../../api/shop";
|
||||
import type { StallCard, StallMine } from "../../api/stall";
|
||||
import { DEPARTMENTS, makeMe, renderApp } from "../../test/auth";
|
||||
import { mockFetch } from "../../test/fetchMock";
|
||||
import { makeCard } from "../../test/packets";
|
||||
import { memoryStorage } from "../supervisor/memoryStorage";
|
||||
import { NO_STATION, STALL_KEY } from "./StallHome";
|
||||
import { NONE_ASSIGNED } from "./StallHome";
|
||||
|
||||
const ANDY = makeMe({ roles: ["shop:03:worker"], screens: ["stall"], user: { sam_account: "weld.andy", display_name: "Andy Welder" } });
|
||||
|
||||
const STATIONS: StationOption[] = [
|
||||
{ dept_code: "03", dept_name: "Weld Parts", id: 1, name: "Stall 1" },
|
||||
{ dept_code: "03", dept_name: "Weld Parts", id: 2, name: "Stall 2" },
|
||||
];
|
||||
|
||||
function shopCard(id: number, over: Partial<ShopCard> = {}): ShopCard {
|
||||
function stallCard(id: number, over: Partial<StallCard> = {}): StallCard {
|
||||
const { open_cos: _ignored, ...base } = makeCard({ id, unit: { id: 100 + id, number: `L66200${id}` }, holder: "Stall 1" });
|
||||
return { ...base, open_cos: [], ...over };
|
||||
return { ...base, open_cos: [], station_id: null, station_name: null, ...over };
|
||||
}
|
||||
|
||||
function stall(id: number): StallAnswer {
|
||||
const st = STATIONS.find((s) => s.id === id) as StationOption;
|
||||
return {
|
||||
station: { id, name: st.name, dept_code: "03", dept_name: "Weld Parts" },
|
||||
mine:
|
||||
id === 1
|
||||
? [shopCard(1, { status: "in_progress", station: { id: 1, name: "Stall 1" }, clock: { elapsed_ms: 3_600_000, running: false }, target_hrs: 4.5 })]
|
||||
: [],
|
||||
submitted: id === 1 ? [shopCard(2, { status: "review", station: { id: 1, name: "Stall 1" } })] : [],
|
||||
};
|
||||
}
|
||||
const MINE: StallMine = {
|
||||
mine: [
|
||||
stallCard(1, { status: "in_progress", station: null, station_id: 1, station_name: "Stall 1", clock: { elapsed_ms: 3_600_000, running: false }, target_hrs: 4.5 }),
|
||||
stallCard(3, { status: "assigned", station: null, station_id: 7, station_name: "Bay 2", dept: { code: "14", name: "Electrical" } }),
|
||||
],
|
||||
submitted: [stallCard(2, { status: "review", station: { id: 1, name: "Stall 1" }, station_id: 1, station_name: "Stall 1" })],
|
||||
};
|
||||
|
||||
function base(stations: StationOption[] = STATIONS) {
|
||||
function base(mine: StallMine = MINE) {
|
||||
return {
|
||||
"/api/auth/me": { body: ANDY },
|
||||
"/api/departments": { body: DEPARTMENTS },
|
||||
"/api/shop/stations": { body: stations },
|
||||
"/api/shop/stall?station_id=1": { body: stall(1) },
|
||||
"/api/shop/stall?station_id=2": { body: stall(2) },
|
||||
"/api/stall/mine": { body: mine },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,50 +37,42 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe("StallHome", () => {
|
||||
it("shows the hero, the picker and the two boards", async () => {
|
||||
mockFetch(base());
|
||||
renderApp("/stall");
|
||||
expect(await screen.findByText("STALL 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("03 Weld Parts")).toHaveClass("eyebrow");
|
||||
expect(screen.getByText("Packets your supervisor sends to this station appear here.")).toBeInTheDocument();
|
||||
expect(screen.getByRole("combobox", { name: "Station" })).toHaveValue("1");
|
||||
|
||||
const mine = await waitFor(() => column("My packets"));
|
||||
const card = within(mine).getByRole("link", { name: /^L662001/ });
|
||||
expect(card).toHaveTextContent("01:00:00 paused · target 4.5 h");
|
||||
expect(card).toHaveTextContent("Working");
|
||||
expect(within(column("Submitted, waiting on supervisor")).getByRole("link", { name: /^L662002/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("switches station and remembers it", async () => {
|
||||
it("lists my packets across stations, each with its station, and has no station picker", async () => {
|
||||
const fetch = mockFetch(base());
|
||||
renderApp("/stall");
|
||||
const pick = await screen.findByRole("combobox", { name: "Station" });
|
||||
fireEvent.change(pick, { target: { value: "2" } });
|
||||
expect(await screen.findByText("STALL 2")).toBeInTheDocument();
|
||||
await waitFor(() => expect(fetch.mock.calls.some(([u]) => String(u) === "/api/shop/stall?station_id=2")).toBe(true));
|
||||
expect(window.localStorage.getItem(STALL_KEY)).toBe("2");
|
||||
expect(await screen.findByText("No work assigned to this stall right now.")).toBeInTheDocument();
|
||||
expect(screen.getByText("Nothing waiting.")).toBeInTheDocument();
|
||||
});
|
||||
const mine = await waitFor(() => column("My packets"));
|
||||
const one = within(mine).getByRole("link", { name: /^L662001/ });
|
||||
expect(one).toHaveTextContent("01:00:00 paused · target 4.5 h");
|
||||
expect(one).toHaveTextContent("Stall 1");
|
||||
expect(within(mine).getByRole("link", { name: /^L662003/ })).toHaveTextContent("Bay 2");
|
||||
expect(within(column("Submitted, waiting on supervisor")).getByRole("link", { name: /^L662002/ })).toBeInTheDocument();
|
||||
|
||||
it("starts on the remembered station", async () => {
|
||||
window.localStorage.setItem(STALL_KEY, "2");
|
||||
mockFetch(base());
|
||||
renderApp("/stall");
|
||||
expect(await screen.findByText("STALL 2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides the picker with one station", async () => {
|
||||
mockFetch(base([STATIONS[0] as StationOption]));
|
||||
renderApp("/stall");
|
||||
expect(await screen.findByText("STALL 1")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("combobox", { name: "Station" })).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Weld stall · Andy Welder")).toHaveClass("eyebrow");
|
||||
const urls = fetch.mock.calls.map(([u]) => String(u));
|
||||
expect(urls.some((u) => u.startsWith("/api/stall/mine"))).toBe(true);
|
||||
expect(urls.some((u) => u.includes("station_id") || u.startsWith("/api/shop/stations") || u.startsWith("/api/shop/stall"))).toBe(false);
|
||||
});
|
||||
|
||||
it("says so when the user has no station", async () => {
|
||||
mockFetch(base([]));
|
||||
it("says the supervisor assigns packets when none are assigned", async () => {
|
||||
mockFetch(base({ mine: [], submitted: [] }));
|
||||
renderApp("/stall");
|
||||
expect(await screen.findByText(NO_STATION)).toBeInTheDocument();
|
||||
expect(await screen.findByText(NONE_ASSIGNED)).toBeInTheDocument();
|
||||
expect(screen.queryByRole("heading", { name: /^My packets/ })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the board when only submitted packets are left", async () => {
|
||||
mockFetch(base({ mine: [], submitted: MINE.submitted }));
|
||||
renderApp("/stall");
|
||||
const mine = await waitFor(() => column("My packets"));
|
||||
expect(mine).toHaveTextContent(`Nothing to work on right now. ${NONE_ASSIGNED}`);
|
||||
});
|
||||
|
||||
it("sends the find box to the server without a station", async () => {
|
||||
const fetch = mockFetch(base());
|
||||
renderApp("/stall");
|
||||
await waitFor(() => column("My packets"));
|
||||
fireEvent.change(screen.getByRole("searchbox"), { target: { value: "L662003" } });
|
||||
await waitFor(() => expect(fetch.mock.calls.some(([u]) => String(u) === "/api/stall/mine?q=L662003")).toBe(true));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user