mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
The help dialog carried its 13 shortcut labels, its getting-started paragraph and its version line as hardcoded English, while fully translated strings for exactly those labels sat unused in all 21 locale files. Every non-English user read English there. The translations did not need writing, only reading: t.help.keyboardShortcuts already had focusSearchBar, goToTools, processFile and the rest, in every locale. Labels now index into t.help.keyboardShortcuts by key rather than carrying text. Getting-started reads t.help.gettingStarted.description, which drops the inline Kbd chip the hardcoded copy had, matching what all 21 locales already say. The version line goes through t.help.versionLabel. Also adds the type-to-search row that #644 left out, keyed help.keyboardShortcuts.typeToSearch, translated into all 21 locales, and regenerates the two darwin help-dialog baselines for the extra row. Nothing caught the original bug because the i18n context defaults to en, so asserting on English text passes whether or not the component reads i18n at all. The new test mocks the context with sentinel values instead: putting the hardcoded labels back fails 15 of its 19 cases. Verified: 19 new unit tests, full unit suite 7576 passed, help-dialog visual 3/3 against regenerated baselines, help accessibility e2e 7/7, typecheck and lint clean, all 18 CI checks green.
120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { cleanup, render, screen } from "@testing-library/react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { HelpDialog } from "@/components/help/help-dialog";
|
|
|
|
/**
|
|
* The help dialog once carried its shortcut labels and its getting-started copy
|
|
* as hardcoded English, while fully translated strings for exactly those labels
|
|
* sat unused in all 21 locale files. No test caught it, because the i18n context
|
|
* defaults to `en`: asserting on English text passes either way.
|
|
*
|
|
* So this mocks the context with sentinel values instead. Any label that goes
|
|
* back to hardcoded English stops rendering its sentinel and fails here.
|
|
*/
|
|
const SHORTCUT_KEYS = [
|
|
"focusSearchBar",
|
|
"typeToSearch",
|
|
"goToTools",
|
|
"processFile",
|
|
"downloadResult",
|
|
"toggleTheme",
|
|
"goToResize",
|
|
"goToCrop",
|
|
"goToCompress",
|
|
"goToConvert",
|
|
"goToRemoveBackground",
|
|
"goToWatermarkText",
|
|
"goToStripMetadata",
|
|
"goToImageInfo",
|
|
] as const;
|
|
|
|
vi.mock("@/contexts/i18n-context", () => ({
|
|
useTranslation: () => ({
|
|
locale: "xx",
|
|
setLocale: () => {},
|
|
t: {
|
|
a11y: { closeHelp: "SENTINEL_closeHelp" },
|
|
help: {
|
|
heading: "SENTINEL_heading",
|
|
gettingStarted: {
|
|
heading: "SENTINEL_gettingStartedHeading",
|
|
description: "SENTINEL_gettingStartedDescription",
|
|
},
|
|
keyboardShortcuts: {
|
|
heading: "SENTINEL_shortcutsHeading",
|
|
focusSearchBar: "SENTINEL_focusSearchBar",
|
|
typeToSearch: "SENTINEL_typeToSearch",
|
|
goToTools: "SENTINEL_goToTools",
|
|
processFile: "SENTINEL_processFile",
|
|
downloadResult: "SENTINEL_downloadResult",
|
|
toggleTheme: "SENTINEL_toggleTheme",
|
|
goToResize: "SENTINEL_goToResize",
|
|
goToCrop: "SENTINEL_goToCrop",
|
|
goToCompress: "SENTINEL_goToCompress",
|
|
goToConvert: "SENTINEL_goToConvert",
|
|
goToRemoveBackground: "SENTINEL_goToRemoveBackground",
|
|
goToWatermarkText: "SENTINEL_goToWatermarkText",
|
|
goToStripMetadata: "SENTINEL_goToStripMetadata",
|
|
goToImageInfo: "SENTINEL_goToImageInfo",
|
|
},
|
|
resources: {
|
|
heading: "SENTINEL_resourcesHeading",
|
|
githubLink: "SENTINEL_githubLink",
|
|
reportIssueLink: "SENTINEL_reportIssueLink",
|
|
docsLink: "SENTINEL_docsLink",
|
|
apiRefLink: "SENTINEL_apiRefLink",
|
|
},
|
|
versionLabel: "SENTINEL_version {version}",
|
|
},
|
|
},
|
|
}),
|
|
}));
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("HelpDialog i18n wiring", () => {
|
|
it.each(SHORTCUT_KEYS)("renders the translated label for %s", (key) => {
|
|
render(<HelpDialog open={true} onClose={() => {}} />);
|
|
|
|
expect(screen.getByText(`SENTINEL_${key}`)).toBeDefined();
|
|
});
|
|
|
|
it("renders the translated getting-started copy", () => {
|
|
render(<HelpDialog open={true} onClose={() => {}} />);
|
|
|
|
expect(screen.getByText("SENTINEL_gettingStartedDescription")).toBeDefined();
|
|
});
|
|
|
|
it("renders the version through the translated label, not a hardcoded prefix", () => {
|
|
render(<HelpDialog open={true} onClose={() => {}} />);
|
|
|
|
expect(screen.getByText(/^SENTINEL_version /)).toBeDefined();
|
|
});
|
|
|
|
it("renders the translated section headings and close label", () => {
|
|
render(<HelpDialog open={true} onClose={() => {}} />);
|
|
|
|
expect(screen.getByText("SENTINEL_heading")).toBeDefined();
|
|
expect(screen.getByText("SENTINEL_shortcutsHeading")).toBeDefined();
|
|
expect(screen.getByText("SENTINEL_resourcesHeading")).toBeDefined();
|
|
expect(screen.getByLabelText("SENTINEL_closeHelp")).toBeDefined();
|
|
});
|
|
|
|
it("renders one row per shortcut and no others", () => {
|
|
render(<HelpDialog open={true} onClose={() => {}} />);
|
|
|
|
const rendered = SHORTCUT_KEYS.filter((k) => screen.queryByText(`SENTINEL_${k}`) !== null);
|
|
expect(rendered).toHaveLength(SHORTCUT_KEYS.length);
|
|
});
|
|
|
|
it("renders nothing when closed", () => {
|
|
const { container } = render(<HelpDialog open={false} onClose={() => {}} />);
|
|
|
|
expect(container.firstChild).toBeNull();
|
|
});
|
|
});
|