Problem Description
Hey folks! I've been writing Vitest browser-mode tests for React components that use Lingui for i18n. I saw you already document a simple test wrapper that loads a catalog so translated strings actually render, that's great!
But it introduces a new problem: any copy change in my .po files can now break my tests.
Why data-testid isn't the answer
The obvious workaround is data-testid, but that defeats the purpose of accessible testing:
- It's an invisible, meaningless attribute, a user can't see or interact with
data-testid="save-btn". If the button loses its accessible name or role, your test still passes but the component is broken for real users.
- It creates a parallel "test-only" contract that drifts from the actual UX contract. You end up testing that a
div exists, not that a button exists.
- You get zero accessibility coverage for free. You can ship a completely inaccessible component with a green test suite.
A concrete example
Here's a simple component:
// ProfileForm.tsx
import { Trans } from "@lingui/react/macro";
export function ProfileForm() {
return (
<form>
<label htmlFor="name">
<Trans>Full name</Trans>
</label>
<input id="name" type="text" />
<button type="submit">
<Trans>Update profile</Trans>
</button>
</form>
);
}
And a well-written, accessible test:
// ProfileForm.test.tsx
it("should submit the form when clicking the save button", async () => {
const screen = await render(<ProfileForm />);
// ✅ Queries by role + accessible name — asserts real UX contract
const saveButton = page.getByRole("button", { name: "Update profile" });
await saveButton.click();
// ... assert form submission
});
This test is correct, it validates what a real user (and assistive technology) sees.
But now a copywriter changes the .po file:
- msgstr "Update profile"
+ msgstr "Save changes"
Test breaks. Not because behavior changed, not because accessibility regressed, just because the label copy was updated. The test was asserting the right thing, but was coupled to a specific snapshot of the translation.
Proposed Solution
I can't shake the feeling that this is something that the i18n library should be concerned about.
Ideally lingui exposes a way to reference message descriptors in test selectors, so the expected string is resolved from the same catalog the component uses. Like this:
import { messages } from "./ProfileForm"; // or wherever descriptors live
it("should submit the form when clicking the save button", async () => {
const screen = await render(<ProfileForm />);
// ✅ Still asserts role + accessible name (real UX contract)
// ✅ But the expected string comes from the catalog, not a hardcoded copy
const saveButton = page.getByRole("button", {
name: t(messages.updateProfile),
});
await saveButton.click();
});
Now when the .po file changes from "Update profile" → "Save changes", both the component and the test resolve the new string from the same source of truth. No test breakage, no loss of accessibility coverage.
I think Lingui could provide an opinionated testing utility for this. Additionally this would allow for some very sleek tests setup that generates test specs for each .po file! Automatically testing for each language the project defines, instead of hardcoding for a single language!
Alternatives Considered
Not applicable
Additional Context
No response
Problem Description
Hey folks! I've been writing Vitest browser-mode tests for React components that use Lingui for i18n. I saw you already document a simple test wrapper that loads a catalog so translated strings actually render, that's great!
But it introduces a new problem: any copy change in my
.pofiles can now break my tests.Why
data-testidisn't the answerThe obvious workaround is
data-testid, but that defeats the purpose of accessible testing:data-testid="save-btn". If the button loses its accessible name or role, your test still passes but the component is broken for real users.divexists, not that abuttonexists.A concrete example
Here's a simple component:
And a well-written, accessible test:
This test is correct, it validates what a real user (and assistive technology) sees.
But now a copywriter changes the
.pofile:Test breaks. Not because behavior changed, not because accessibility regressed, just because the label copy was updated. The test was asserting the right thing, but was coupled to a specific snapshot of the translation.
Proposed Solution
I can't shake the feeling that this is something that the i18n library should be concerned about.
Ideally lingui exposes a way to reference message descriptors in test selectors, so the expected string is resolved from the same catalog the component uses. Like this:
Now when the
.pofile changes from "Update profile" → "Save changes", both the component and the test resolve the new string from the same source of truth. No test breakage, no loss of accessibility coverage.I think Lingui could provide an opinionated testing utility for this. Additionally this would allow for some very sleek tests setup that generates test specs for each .po file! Automatically testing for each language the project defines, instead of hardcoding for a single language!
Alternatives Considered
Not applicable
Additional Context
No response