Skip to main content

Testing

The library is easy to test because it's pure: a provider, a hook, a string out. Below is a small kit we use ourselves.

Render helper​

Wrap RTL's render with a provider so every test starts in the right locale:

test/render.tsx
import {
render as rtlRender,
type RenderOptions,
} from '@testing-library/react';
import { LocalizationProvider, type Translations } from 'localize-react';
import type { ReactElement, ReactNode } from 'react';

type Options = Omit<RenderOptions, 'wrapper'> & {
locale?: string;
translations?: Translations;
};

export function render(
ui: ReactElement,
{ locale = 'en', translations = {}, ...opts }: Options = {},
) {
const Wrapper = ({ children }: { children: ReactNode }) => (
<LocalizationProvider locale={locale} translations={translations}>
{children}
</LocalizationProvider>
);
return rtlRender(ui, { wrapper: Wrapper, ...opts });
}

Now every test reads cleanly:

import { render } from '../test/render';
import { Cart } from './Cart';

it('renders the cart summary', () => {
const { getByText } = render(<Cart count={3} />, {
locale: 'en',
translations: { en: { cart: { summary: '{{count}} items' } } },
});
expect(getByText('3 items')).toBeInTheDocument();
});

Isolate the module cache​

The translate cache is module-scoped. In Vitest, reset it between tests so a stale entry from a previous test can't leak:

test/setup.ts
import '@testing-library/jest-dom/vitest';
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';

// The library exports clearCache from src/helpers.ts internally; for tests
// against your own components, the cache clears on locale/translations
// change anyway, so usually you just need cleanup() here.
afterEach(() => cleanup());

Descriptor coverage​

Write a single test that proves every descriptor your app uses resolves in every locale. Easy if your descriptors are typed:

test/coverage.test.ts
import { translations } from '@/i18n';

const ALL_DESCRIPTORS: string[] = JSON.parse(
readFileSync('all-descriptors.json', 'utf8'),
);

describe('translation coverage', () => {
for (const locale of Object.keys(translations)) {
it(`every descriptor resolves in ${locale}`, () => {
const tree = translations[locale];
for (const d of ALL_DESCRIPTORS) {
const resolved = d
.split('.')
.reduce((cur: any, seg) => cur?.[seg], tree);
expect(typeof resolved).toBe('string');
}
});
}
});

The all-descriptors.json list can be generated by a tiny codemod over your codebase (ts-morph, recast, or grep -rE 'translate\\("([^"]+)"').

Snapshot-friendly fallbacks​

Use defaultMessage for components rendered in snapshot tests so they don't bake locale into the snapshot:

<Message descriptor="cta.subscribe" defaultMessage="Subscribe" />

Same component, deterministic snapshot, real translation in prod.