fix(ux): preserve localized save errors and current language retries

This commit is contained in:
cesnimda
2026-10-11 03:05:33 +02:00
parent a94ea0a4a4
commit c135d03602
15 changed files with 485 additions and 11 deletions
+51 -1
View File
@@ -1,9 +1,10 @@
import React from "react";
import "@testing-library/jest-dom";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { api } from "./api";
import { I18nProvider, useI18n } from "./i18n/I18nProvider";
import { ToastProvider } from "./toast";
function Probe() {
const { language, setLanguage, hydrateLanguage, t } = useI18n();
@@ -44,3 +45,52 @@ test("hydrates an authenticated server preference without writing it back", () =
expect(window.localStorage.getItem("uiLanguage")).toBe("nb");
expect(api.put).not.toHaveBeenCalled();
});
test.each([
["Norsk", "nb", "nb-NO", "Språket er endret på denne enheten, men kunne ikke lagres på kontoen din.", "Prøv igjen"],
["English", "en", "en", "The language changed on this device, but your account preference could not be saved.", "Retry"],
])("a failed account language save keeps the local choice and offers a localized retry: %s", async (button, local, remote, message, retry) => {
(api.put as jest.Mock).mockRejectedValueOnce({ response: { status: 500, data: "Internal database detail" } });
render(<ToastProvider><I18nProvider><Probe /></I18nProvider></ToastProvider>);
fireEvent.click(screen.getByRole("button", { name: button }));
expect(screen.getByLabelText("language")).toHaveTextContent(local);
expect(window.localStorage.getItem("uiLanguage")).toBe(local);
expect(document.documentElement.lang).toBe(remote);
const alert = await screen.findByRole("alert");
expect(alert).toHaveTextContent(message);
expect(alert).not.toHaveTextContent("Internal database detail");
expect(api.put).toHaveBeenCalledTimes(1);
expect(api.put).toHaveBeenLastCalledWith("/auth/preferences/language", { language: remote });
fireEvent.click(within(alert).getByRole("button", { name: retry }));
await waitFor(() => expect(api.put).toHaveBeenCalledTimes(2));
expect(api.put).toHaveBeenLastCalledWith("/auth/preferences/language", { language: remote });
await waitFor(() => expect(screen.queryByRole("alert")).not.toBeInTheDocument());
expect(screen.getByLabelText("language")).toHaveTextContent(local);
});
test("a delayed language-save failure cannot offer a retry for a newer successful choice", async () => {
let rejectOld!: (error: Error) => void;
(api.put as jest.Mock).mockImplementationOnce(() => new Promise((_, reject) => { rejectOld = reject; }));
render(<ToastProvider><I18nProvider><Probe /></I18nProvider></ToastProvider>);
fireEvent.click(screen.getByRole("button", { name: "Norsk" }));
fireEvent.click(screen.getByRole("button", { name: "English" }));
await act(async () => { rejectOld(new Error("offline")); });
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
expect(screen.getByLabelText("language")).toHaveTextContent("en");
expect(api.put).toHaveBeenCalledTimes(2);
});
test("a previously delivered language retry cannot reverse a newer choice", async () => {
(api.put as jest.Mock).mockRejectedValueOnce(new Error("offline"));
render(<ToastProvider><I18nProvider><Probe /></I18nProvider></ToastProvider>);
fireEvent.click(screen.getByRole("button", { name: "Norsk" }));
const alert = await screen.findByRole("alert");
const retry = within(alert).getByRole("button", { name: "Prøv igjen" });
fireEvent.click(screen.getByRole("button", { name: "English" }));
fireEvent.click(retry);
expect(screen.getByLabelText("language")).toHaveTextContent("en");
expect(window.localStorage.getItem("uiLanguage")).toBe("en");
expect(api.put).toHaveBeenCalledTimes(2);
});