9edcbfc5de
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.
110 lines
4.9 KiB
TypeScript
110 lines
4.9 KiB
TypeScript
import React from "react";
|
|
import "@testing-library/jest-dom";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
|
|
import { cvBuilderApi } from "./cvBuilder";
|
|
import CareerWorkspaceOverview, { CareerWorkspaceImportRun } from "./views/career/CareerWorkspaceOverview";
|
|
import { I18nProvider } from "./i18n/I18nProvider";
|
|
|
|
jest.mock("./cvBuilder", () => ({
|
|
cvBuilderApi: { list: jest.fn() },
|
|
}));
|
|
|
|
const mockedList = cvBuilderApi.list as jest.MockedFunction<typeof cvBuilderApi.list>;
|
|
|
|
function renderOverview({
|
|
percent = 0,
|
|
missing = [],
|
|
runs = [],
|
|
loading = false,
|
|
loadError = null,
|
|
}: {
|
|
percent?: number;
|
|
missing?: string[];
|
|
runs?: CareerWorkspaceImportRun[];
|
|
loading?: boolean;
|
|
loadError?: string | null;
|
|
} = {}) {
|
|
return render(
|
|
<I18nProvider>
|
|
<CareerWorkspaceOverview
|
|
completeness={{ percent, missing }}
|
|
runs={runs}
|
|
loading={loading}
|
|
loadError={loadError}
|
|
/>
|
|
</I18nProvider>,
|
|
);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
window.localStorage.removeItem("uiLanguage");
|
|
mockedList.mockResolvedValue([]);
|
|
});
|
|
|
|
afterEach(() => {
|
|
window.localStorage.removeItem("uiLanguage");
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
test("first-run workspace presents concise profile, import, general CV and job-specific actions", async () => {
|
|
renderOverview();
|
|
|
|
expect(screen.getByRole("heading", { name: "Overview", level: 2 })).toBeInTheDocument();
|
|
expect(await screen.findByText(/start by adding career information manually or importing a cv/i)).toBeInTheDocument();
|
|
expect(screen.queryByText(/job-specific CVs stay separate and never overwrite/i)).not.toBeInTheDocument();
|
|
expect(screen.getByRole("link", { name: "Edit career profile" })).toHaveAttribute("href", "/career?section=profile");
|
|
expect(screen.getAllByRole("link", { name: "Import a CV" })[0]).toHaveAttribute("href", "/career?section=import");
|
|
expect(screen.getByRole("link", { name: "Create a general CV" })).toHaveAttribute("href", "/career/builder");
|
|
expect(screen.getByRole("link", { name: "Choose a job" })).toHaveAttribute("href", "/jobs");
|
|
expect(await screen.findByText("No CV documents yet.")).toBeInTheDocument();
|
|
});
|
|
|
|
test("career workspace overview and import state render in Bokmål", async () => {
|
|
window.localStorage.setItem("uiLanguage", "nb");
|
|
renderOverview({ runs: [{ id: 7, status: "pending_review" }] });
|
|
|
|
expect(screen.getByRole("heading", { name: "Oversikt", level: 2 })).toBeInTheDocument();
|
|
expect(screen.getAllByText("Se gjennom importert CV", { selector: "a" })).toHaveLength(2);
|
|
expect(screen.getByText("Én import venter på godkjenning.")).toBeInTheDocument();
|
|
expect(screen.getByRole("navigation", { name: "Handlinger i karriereområdet" })).toBeInTheDocument();
|
|
expect(await screen.findByText("Ingen CV-dokumenter ennå.")).toBeInTheDocument();
|
|
});
|
|
|
|
test("returning workspace shows missing profile information and recent general and job-specific CVs", async () => {
|
|
mockedList.mockResolvedValue([
|
|
{ id: 8, name: "Backend CV", themeId: "modern", publicSlug: "backend", isPublic: false, version: 2, jobApplicationId: 42, updatedAtUtc: "2026-08-08T10:00:00Z" },
|
|
{ id: 7, name: "General CV", themeId: "modern", publicSlug: "general", isPublic: false, version: 1, jobApplicationId: null, updatedAtUtc: "2026-08-07T10:00:00Z" },
|
|
]);
|
|
|
|
renderOverview({ percent: 70, missing: ["Projects", "Education", "Languages", "Interests"] });
|
|
|
|
expect(screen.getByText("70%")).toBeInTheDocument();
|
|
expect(screen.getByText("Add next: Projects, Education, Languages")).toBeInTheDocument();
|
|
expect(await screen.findByText("Backend CV")).toBeInTheDocument();
|
|
expect(screen.getAllByText("General CV").length).toBeGreaterThan(1);
|
|
expect(screen.getByText("Job-specific")).toBeInTheDocument();
|
|
expect(screen.getAllByRole("link", { name: /^Open$/ })[0]).toHaveAttribute("href", "/career/builder/8");
|
|
});
|
|
|
|
test.each([
|
|
["pending_review", "Review imported CV", /waiting for your approval/i],
|
|
["queued", "View CV processing", /is processing/i],
|
|
["failed", "Resolve CV import", /needs attention/i],
|
|
])("workspace exposes the %s import state as a resumable action", async (status, label, message) => {
|
|
renderOverview({ runs: [{ id: 13, status }] });
|
|
|
|
expect(screen.getAllByRole("link", { name: label })[0]).toHaveAttribute("href", "/career?section=import");
|
|
expect(screen.getByText(message)).toBeInTheDocument();
|
|
await waitFor(() => expect(mockedList).toHaveBeenCalledTimes(1));
|
|
});
|
|
|
|
test("workspace represents loading and profile-load errors without inventing status", async () => {
|
|
renderOverview({ loading: true, loadError: "offline" });
|
|
|
|
expect(screen.getByRole("progressbar", { name: "Career profile completeness" })).toBeInTheDocument();
|
|
expect(screen.getByText(/workspace status is unavailable/i)).toBeInTheDocument();
|
|
expect(screen.queryByText(/start by adding career information/i)).not.toBeInTheDocument();
|
|
await waitFor(() => expect(mockedList).toHaveBeenCalledTimes(1));
|
|
});
|