feat(ui): redesign job workspace sections
This commit is contained in:
@@ -27,7 +27,8 @@ jest.mock("./components/ApplicationAssets", () => ({
|
||||
</div>
|
||||
),
|
||||
ApplicationCvSection: () => <div>CV section</div>,
|
||||
ApplicationPackageDraftsSection: () => <div>Application drafts section</div>,
|
||||
ApplicationPackageDraftsSection: ({ onDirtyChange }: { onDirtyChange?: (dirty: boolean) => void }) => <div>Application drafts section<button onClick={() => onDirtyChange?.(true)}>Make answers dirty</button></div>,
|
||||
SubmittedPackagesSection: () => <div>Frozen packages section</div>,
|
||||
}));
|
||||
jest.mock("./components/InterviewPrep", () => ({ ApplicationInterviewPrep: () => <div>Interview section</div> }));
|
||||
jest.mock("./components/ApplicationWorkflowAssist", () => ({
|
||||
@@ -174,10 +175,10 @@ test("opens the dedicated workspace from the whole row and preserves list state
|
||||
expect(await screen.findByText("Backend Developer")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42");
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Analysis" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Match" }));
|
||||
expect(await screen.findByText("Analysis section")).toBeInTheDocument();
|
||||
expect(await screen.findByText("Match section")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=analysis");
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=match");
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /back to applications/i }));
|
||||
await waitFor(() => expect(screen.getByTestId("location")).toHaveTextContent("/jobs?q=backend"));
|
||||
@@ -190,7 +191,7 @@ test("opens a direct workspace URL and returns to applications", async () => {
|
||||
|
||||
expect(await screen.findByText("Analysis section")).toBeInTheDocument();
|
||||
expect(await screen.findByText("Match section")).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: "Analysis" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(screen.getByRole("tab", { name: "Match" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(screen.getByLabelText(/application progress: waiting/i)).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /back to applications/i }));
|
||||
@@ -219,16 +220,16 @@ test("warns before section navigation would discard application edits", async ()
|
||||
renderTable("/jobs/42?section=cover-letter");
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Make cover letter dirty" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Analysis" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Match" }));
|
||||
|
||||
expect(await screen.findByRole("dialog", { name: /Unsaved application changes/i })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Keep editing" }));
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=cover-letter");
|
||||
await waitFor(() => expect(screen.queryByRole("dialog", { name: /Unsaved application changes/i })).not.toBeInTheDocument());
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Analysis" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Match" }));
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Discard and leave" }));
|
||||
await waitFor(() => expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=analysis"));
|
||||
await waitFor(() => expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=match"));
|
||||
});
|
||||
|
||||
test("renders shared workspace navigation and progress in Norwegian Bokmål", async () => {
|
||||
@@ -248,8 +249,8 @@ test("renders shared workspace navigation and progress in Norwegian Bokmål", as
|
||||
renderTable("/jobs/42");
|
||||
|
||||
expect(await screen.findByRole("tab", { name: "Oversikt" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(screen.getByRole("tab", { name: "Analyse" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: "Søknadsbrev" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: "Samsvar" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: "Søknad" })).toBeInTheDocument();
|
||||
expect(await screen.findByLabelText(/søknadsprogresjon/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /tilbake til søknader/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Forbered en CV for denne stillingen" })).toBeInTheDocument();
|
||||
@@ -323,17 +324,17 @@ test("uses recorded dates in the summary and keeps absent dates unknown", async
|
||||
test("section changes retain follow-up query context and expose the selected labelled panel", async () => {
|
||||
renderTable("/jobs/42?section=communication&followMode=waiting-update");
|
||||
await screen.findByRole("heading", { name: "Backend Developer" });
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Analysis" }));
|
||||
expect(await screen.findByRole("tabpanel", { name: "Analysis" })).toHaveTextContent("Match section");
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=analysis&followMode=waiting-update");
|
||||
const tab = screen.getByRole("tab", { name: "Analysis" });
|
||||
expect(document.getElementById(tab.getAttribute("aria-controls")!)).toBe(screen.getByRole("tabpanel", { name: "Analysis" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Match" }));
|
||||
expect(await screen.findByRole("tabpanel", { name: "Match" })).toHaveTextContent("Match section");
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("/jobs/42?section=match&followMode=waiting-update");
|
||||
const tab = screen.getByRole("tab", { name: "Match" });
|
||||
expect(document.getElementById(tab.getAttribute("aria-controls")!)).toBe(screen.getByRole("tabpanel", { name: "Match" }));
|
||||
});
|
||||
|
||||
test("accordion controls identify exactly one labelled content region", async () => {
|
||||
renderTable("/jobs/42");
|
||||
await screen.findByRole("heading", { name: "Backend Developer" });
|
||||
const trigger = screen.getByRole("button", { name: "Documents" });
|
||||
const trigger = screen.getByRole("button", { name: "Job details" });
|
||||
fireEvent.click(trigger);
|
||||
const target = trigger.getAttribute("aria-controls")!;
|
||||
expect(document.querySelectorAll(`[id='${target}']`)).toHaveLength(1);
|
||||
@@ -341,12 +342,11 @@ test("accordion controls identify exactly one labelled content region", async ()
|
||||
expect(document.getElementById(target)).toHaveAttribute("aria-labelledby", trigger.id);
|
||||
});
|
||||
|
||||
test("legacy document links reveal their existing content and focus the disclosure", async () => {
|
||||
test("legacy document links reveal current attachments and frozen history in the named section", async () => {
|
||||
renderTable("/jobs/42?section=documents");
|
||||
const trigger = await screen.findByRole("button", { name: "Documents" });
|
||||
await waitFor(() => expect(trigger).toHaveAttribute("aria-expanded", "true"));
|
||||
expect(trigger).toHaveFocus();
|
||||
expect(screen.getByRole("region", { name: "Documents" })).toHaveTextContent("Documents section");
|
||||
expect(await screen.findByRole("tab", { name: "Documents" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(screen.getByRole("tabpanel", { name: "Documents" })).toHaveTextContent("Documents section");
|
||||
expect(screen.getByRole("tabpanel", { name: "Documents" })).toHaveTextContent("Frozen packages section");
|
||||
});
|
||||
|
||||
test("the missing-profile next task opens the existing canonical profile entry", async () => {
|
||||
@@ -357,3 +357,29 @@ test("the missing-profile next task opens the existing canonical profile entry",
|
||||
expect(mockedApi.post).not.toHaveBeenCalled();
|
||||
expect(mockedApi.put).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("application subsection navigation retains metadata and protects unsaved answers", async () => {
|
||||
renderTable("/jobs/42?section=application&sub=answers&followMode=waiting-update");
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Make answers dirty" }));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Submission" }));
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Keep editing" }));
|
||||
await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
|
||||
expect(screen.getByRole("tabpanel", { name: "Answers & message draft" })).toHaveTextContent("Application drafts section");
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Submission" }));
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Discard and leave" }));
|
||||
expect(await screen.findByRole("tabpanel", { name: "Submission" })).toHaveTextContent("Frozen packages section");
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("section=application&sub=submission&followMode=waiting-update");
|
||||
expect(mockedApi.post).not.toHaveBeenCalled();
|
||||
expect(mockedApi.put).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("legacy correspondence and notes reveal distinct domain content", async () => {
|
||||
mockedApi.get.mockResolvedValue({ data: { ...overview, notes: "Human notes, not application answers" } } as any);
|
||||
renderTable("/jobs/42?section=communication&followMode=waiting-update");
|
||||
expect(await screen.findByRole("tabpanel", { name: "Communication" })).toHaveTextContent("Communication section");
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Notes & tasks" }));
|
||||
expect(await screen.findByRole("region", { name: "Notes" })).toHaveTextContent("Human notes, not application answers");
|
||||
expect(screen.getByRole("tabpanel", { name: "Notes & tasks" })).toHaveTextContent("Checklist section");
|
||||
expect(screen.queryByText("Application drafts section")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("followMode=waiting-update");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user