feat(workspace): refine career workflows

Make job/CV comparisons language-aware and filter recruitment noise. Improve responsive career navigation, shared spacing, dashboard priorities, settings, localized workspace controls, and portable browser tests.
This commit is contained in:
cesnimda
2026-08-29 16:44:44 +02:00
parent 4d4af47651
commit 9edcbfc5de
29 changed files with 827 additions and 236 deletions
@@ -4,6 +4,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import ApplicationChecklist from "./components/ApplicationChecklist";
import { api } from "./api";
import { I18nProvider } from "./i18n/I18nProvider";
jest.mock("./api", () => ({
api: {
@@ -19,6 +20,8 @@ jest.mock("./api", () => ({
const mockedApi = api as jest.Mocked<typeof api>;
const renderChecklist = () => render(<I18nProvider><ApplicationChecklist jobId={7} /></I18nProvider>);
const item = (over: Partial<any> = {}) => ({
id: 1,
systemKey: "prepare-cv",
@@ -46,6 +49,7 @@ const checklist = (items: any[]) => ({
beforeEach(() => {
jest.clearAllMocks();
window.localStorage.setItem("uiLanguage", "en");
mockedApi.get.mockResolvedValue({
data: checklist([
item(),
@@ -55,8 +59,17 @@ beforeEach(() => {
} as any);
});
test("localizes system checklist content without changing user-authored tasks", async () => {
window.localStorage.setItem("uiLanguage", "nb");
renderChecklist();
expect(await screen.findByText("Forbered en CV for denne stillingen")).toBeInTheDocument();
expect(screen.getByText("Dine egne oppgaver")).toBeInTheDocument();
expect(screen.getByText("Ask Sara for a referral")).toBeInTheDocument();
});
test("renders the checklist grouped by category with progress", async () => {
render(<ApplicationChecklist jobId={7} />);
renderChecklist();
expect(await screen.findByText("Prepare a CV for this role")).toBeInTheDocument();
expect(screen.getByText("Before applying")).toBeInTheDocument();
@@ -69,7 +82,7 @@ test("renders the checklist grouped by category with progress", async () => {
test("completing an item patches its status", async () => {
mockedApi.patch.mockResolvedValue({ data: item({ status: "done" }) } as any);
render(<ApplicationChecklist jobId={7} />);
renderChecklist();
fireEvent.click(await screen.findByRole("checkbox", { name: "Prepare a CV for this role" }));
await waitFor(() =>
@@ -79,7 +92,7 @@ test("completing an item patches its status", async () => {
test("un-ticking a completed item sends it back to pending", async () => {
mockedApi.patch.mockResolvedValue({ data: item({ id: 2, status: "pending" }) } as any);
render(<ApplicationChecklist jobId={7} />);
renderChecklist();
fireEvent.click(await screen.findByRole("checkbox", { name: "Create a cover letter" }));
await waitFor(() =>
@@ -89,7 +102,7 @@ test("un-ticking a completed item sends it back to pending", async () => {
test("adding a custom task posts the title", async () => {
mockedApi.post.mockResolvedValue({ data: item({ id: 9, systemKey: null, isSystemGenerated: false }) } as any);
render(<ApplicationChecklist jobId={7} />);
renderChecklist();
fireEvent.change(await screen.findByLabelText(/Add your own task/i), { target: { value: "Email the hiring manager" } });
fireEvent.click(screen.getByRole("button", { name: "Add" }));
@@ -102,7 +115,7 @@ test("adding a custom task posts the title", async () => {
test("reordering sends the new id order", async () => {
mockedApi.put.mockResolvedValue({ data: checklist([]) } as any);
render(<ApplicationChecklist jobId={7} />);
renderChecklist();
fireEvent.click(await screen.findByRole("button", { name: "Move down: Prepare a CV for this role" }));
await waitFor(() =>
@@ -112,7 +125,7 @@ test("reordering sends the new id order", async () => {
test("removing an item calls delete", async () => {
mockedApi.delete.mockResolvedValue({ data: undefined } as any);
render(<ApplicationChecklist jobId={7} />);
renderChecklist();
fireEvent.click(await screen.findByRole("button", { name: "Remove: Ask Sara for a referral" }));
await waitFor(() => expect(mockedApi.delete).toHaveBeenCalledWith("/jobapplications/7/checklist/3"));