import React from "react"; import "@testing-library/jest-dom"; import { fireEvent, render, screen, within } from "@testing-library/react"; import { I18nProvider } from "./i18n/I18nProvider"; import { translations } from "./i18n/translations"; import ProfileCompleteness, { CareerVersionView } from "./views/career/ProfileCompleteness"; type HistoryProps = React.ComponentProps & { versionsLoading?: boolean; versionsError?: string | null; onRetryVersions?: () => void; restoreDisabled?: boolean; restoreError?: string | null; }; type HistoryKey = "careerHistoryLoading" | "careerHistoryLoadFailed" | "careerHistoryEmpty" | "careerSaveBeforeRestore" | "careerRestoreVersion" | "careerRestoreFailed"; const en = translations.en as typeof translations.en & Record; const nb = translations.nb as typeof translations.nb & Record; const versions: CareerVersionView[] = [ { version: 4, source: "manual", createdAtUtc: "2026-10-12T01:00:00Z", isCurrent: true }, { version: 3, source: "import", createdAtUtc: "2026-10-11T01:00:00Z", isCurrent: false }, ]; function historyTitle(count: number, language: "en" | "nb" = "en") { return translations[language].profileCvVersionHistory.replace("{count}", String(count)); } function view(props: Partial = {}) { const defaults: HistoryProps = { completeness: null, versions, loading: false, onRestore: jest.fn(), showSummary: false, }; return ; } function expand(count = 2) { fireEvent.click(screen.getByRole("button", { name: historyTitle(count) })); } beforeEach(() => window.localStorage.removeItem("uiLanguage")); afterEach(() => window.localStorage.removeItem("uiLanguage")); test("the first history read announces progress without claiming an empty history", () => { const restore = jest.fn(); render(view({ versions: [], versionsLoading: true, onRestore: restore })); expect(screen.getByRole("progressbar", { name: en.careerHistoryLoading })).toBeInTheDocument(); expect(screen.getByRole("heading", { name: historyTitle(0), level: 2 })).toBeInTheDocument(); expect(screen.queryByText(en.careerHistoryEmpty)).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: /^Restore/ })).not.toBeInTheDocument(); expect(restore).not.toHaveBeenCalled(); }); test("a failed history read retains known rows and retries only through its history owner", () => { const retry = jest.fn(); const restore = jest.fn(); const rendered = render(view({ versionsError: "History service unavailable", onRetryVersions: retry, onRestore: restore })); expand(); const failure = screen.getByRole("alert"); expect(failure).toHaveTextContent(en.careerHistoryLoadFailed); expect(failure).toHaveTextContent("History service unavailable"); expect(screen.getByText(/^v4 · manual ·/)).toBeVisible(); expect(screen.getByText(/^v3 · import ·/)).toBeVisible(); const retryControl = within(failure).getByRole("button", { name: en.retry }); retryControl.focus(); fireEvent.click(retryControl); expect(screen.getByRole("group", { name: historyTitle(2) })).toHaveFocus(); expect(retry).toHaveBeenCalledTimes(1); expect(restore).not.toHaveBeenCalled(); rendered.rerender(view({ versionsLoading: true, versionsError: "History service unavailable", onRetryVersions: retry, onRestore: restore })); expect(screen.getByRole("alert")).toHaveTextContent("History service unavailable"); expect(screen.getByRole("button", { name: en.retry })).toBeDisabled(); expect(screen.getByRole("progressbar", { name: en.careerHistoryLoading })).toBeInTheDocument(); expect(screen.getByText(/^v3 · import ·/)).toBeVisible(); expect(retry).toHaveBeenCalledTimes(1); }); test("a settled zero-version history is a named empty section rather than a hidden capability", () => { render(view({ versions: [] })); const history = screen.getByRole("region", { name: historyTitle(0) }); expect(within(history).getByRole("heading", { name: historyTitle(0), level: 2 })).toBeInTheDocument(); expect(within(history).getByText(en.careerHistoryEmpty)).toBeVisible(); expect(within(history).queryByRole("progressbar")).not.toBeInTheDocument(); expect(within(history).queryByRole("button", { name: /^Restore/ })).not.toBeInTheDocument(); }); test("one current version remains visible in Bokmål without offering a restore of the current source", () => { window.localStorage.setItem("uiLanguage", "nb"); render(view({ versions: [versions[0]] })); const row = screen.getByText(/^v4 · manual ·/); expect(row).toBeVisible(); expect(row).toHaveTextContent(nb.profileCvVersionCurrent); expect(screen.getByRole("region", { name: historyTitle(1, "nb") })).toBeInTheDocument(); expect(screen.queryByRole("button", { name: /^Gjenopprett/ })).not.toBeInTheDocument(); }); test("the collapsed history keeps twelve rows and contextual restore actions pass the exact selected version", () => { const restore = jest.fn(); const many = Array.from({ length: 13 }, (_, index) => ({ version: 13 - index, source: "manual", createdAtUtc: "2026-10-12T01:00:00Z", isCurrent: index === 0, })); render(view({ versions: many, onRestore: restore })); const disclosure = screen.getByRole("button", { name: historyTitle(13) }); expect(disclosure).toHaveAttribute("aria-expanded", "false"); expect(screen.queryByRole("button", { name: /^Restore v/ })).not.toBeInTheDocument(); fireEvent.click(disclosure); expect(disclosure).toHaveAttribute("aria-expanded", "true"); expect(screen.getAllByText(/^v\d+ · manual ·/)).toHaveLength(12); expect(screen.queryByText(/^v1 · manual ·/)).not.toBeInTheDocument(); expect(screen.getAllByRole("button", { name: /^Restore v/ })).toHaveLength(11); expect(screen.queryByRole("button", { name: "Restore v13" })).not.toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: "Restore v12" })); expect(restore).toHaveBeenCalledTimes(1); expect(restore).toHaveBeenCalledWith(12); }); test("dirty source state explains the restore guard and re-enables the same version after the parent clears it", () => { const restore = jest.fn(); const rendered = render(view({ restoreDisabled: true, onRestore: restore })); expand(); const guarded = screen.getByRole("button", { name: /^Restore/ }); expect(guarded).toBeDisabled(); expect(screen.getByText(en.careerSaveBeforeRestore)).toBeVisible(); fireEvent.click(guarded); expect(restore).not.toHaveBeenCalled(); rendered.rerender(view({ restoreDisabled: false, onRestore: restore })); expect(screen.queryByText(en.careerSaveBeforeRestore)).not.toBeInTheDocument(); const available = screen.getByRole("button", { name: "Restore v3" }); expect(available).toBeEnabled(); fireEvent.click(available); expect(restore).toHaveBeenCalledWith(3); expect(restore).toHaveBeenCalledTimes(1); }); test("the existing overall loading guard still blocks history restores", () => { const restore = jest.fn(); render(view({ loading: true, onRestore: restore })); expand(); const action = screen.getByRole("button", { name: /^Restore/ }); expect(action).toBeDisabled(); fireEvent.click(action); expect(restore).not.toHaveBeenCalled(); }); test("restore failure stays visible with the known history until the parent clears it and retry is explicit", () => { const restore = jest.fn(); const rendered = render(view({ restoreError: "Restore service unavailable", onRestore: restore })); expand(); expect(screen.getByRole("alert")).toHaveTextContent(en.careerRestoreFailed); expect(screen.getByRole("alert")).toHaveTextContent("Restore service unavailable"); expect(screen.getByText(/^v3 · import ·/)).toBeVisible(); expect(restore).not.toHaveBeenCalled(); fireEvent.click(screen.getByRole("button", { name: "Restore v3" })); expect(restore).toHaveBeenCalledTimes(1); expect(restore).toHaveBeenCalledWith(3); expect(screen.getByRole("alert")).toBeInTheDocument(); rendered.rerender(view({ restoreError: null, onRestore: restore })); expect(screen.queryByRole("alert")).not.toBeInTheDocument(); expect(screen.getByText(/^v4 · manual ·/)).toBeVisible(); expect(restore).toHaveBeenCalledTimes(1); });