Resolves the remaining review threads: - Reset to auto-detect across all three layers (Greptile): store passes language:null through as null-as-delete; dashboard gains an Auto option (clearLanguage + hasExplicitChoice in useLanguage, re-detects from navigator, syncs cross-tab); CLI accepts 'fn settings set language auto'. Catalog keys added for all five locales; tests at every layer. - CLI i18n test singleton: afterEach locale restore so zh-CN/fr switches can't leak across cases (CodeRabbit nitpick) - useLocaleFormat memoized per locale for stable formatter identities (CodeRabbit nitpick) - settings-reference.md documents the auto reset path Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
import { SUPPORTED_LOCALES } from "@fusion/core";
|
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { useLanguage } from "../../hooks/useLanguage";
|
|
|
|
vi.mock("../../hooks/useLanguage", () => ({ useLanguage: vi.fn() }));
|
|
vi.mock("react-i18next", () => ({
|
|
useTranslation: () => ({ t: (key: string, def?: string) => def ?? key }),
|
|
}));
|
|
|
|
const { LanguageSelector } = await import("../LanguageSelector");
|
|
const mockUseLanguage = vi.mocked(useLanguage);
|
|
const setLanguage = vi.fn();
|
|
const clearLanguage = vi.fn();
|
|
|
|
describe("LanguageSelector", () => {
|
|
beforeEach(() => {
|
|
setLanguage.mockClear();
|
|
clearLanguage.mockClear();
|
|
mockUseLanguage.mockReturnValue({
|
|
language: "en",
|
|
supportedLocales: SUPPORTED_LOCALES,
|
|
setLanguage,
|
|
clearLanguage,
|
|
hasExplicitChoice: true,
|
|
});
|
|
});
|
|
|
|
it("renders all five language endonyms", () => {
|
|
render(<LanguageSelector />);
|
|
for (const name of ["English", "简体中文", "繁體中文", "Français", "Español"]) {
|
|
expect(screen.getByText(name)).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
it("marks the active locale as pressed", () => {
|
|
render(<LanguageSelector />);
|
|
expect(screen.getByText("English").getAttribute("aria-pressed")).toBe("true");
|
|
expect(screen.getByText("Français").getAttribute("aria-pressed")).toBe("false");
|
|
});
|
|
|
|
it("calls setLanguage with the selected locale code", () => {
|
|
render(<LanguageSelector />);
|
|
fireEvent.click(screen.getByText("简体中文"));
|
|
expect(setLanguage).toHaveBeenCalledWith("zh-CN");
|
|
});
|
|
|
|
it("offers an Auto option that clears the explicit choice", () => {
|
|
render(<LanguageSelector />);
|
|
const auto = screen.getByText("Auto");
|
|
expect(auto.getAttribute("aria-pressed")).toBe("false");
|
|
fireEvent.click(auto);
|
|
expect(clearLanguage).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("marks Auto as pressed (and no locale) when no explicit choice exists", () => {
|
|
mockUseLanguage.mockReturnValue({
|
|
language: "fr", // detected, not chosen
|
|
supportedLocales: SUPPORTED_LOCALES,
|
|
setLanguage,
|
|
clearLanguage,
|
|
hasExplicitChoice: false,
|
|
});
|
|
render(<LanguageSelector />);
|
|
expect(screen.getByText("Auto").getAttribute("aria-pressed")).toBe("true");
|
|
expect(screen.getByText("Français").getAttribute("aria-pressed")).toBe("false");
|
|
});
|
|
});
|