Testing
Which locale messages and getLocale() read depends on where the test runs. A test under Node sets it with locales.run, as a server would; a test in a browser sets it with the URL.
On this page
Tests under Node
With nothing naming a locale, a message returns the default locale’s text. To check another locale, call it inside locales.run.
messages/nav.test.tsimport { expect, it } from 'vitest';
import { locales } from '../i18n';
import * as nav from './nav';
it('renders in the default locale', () => {
expect(nav.home()).toBe('ホーム');
});
it('renders in the locale run names', () => {
expect(locales.run('en', () => nav.home())).toBe('Home');
});
it('keeps the locale across awaits', async () => {
const text = await locales.run('en', async () => {
await Promise.resolve();
return nav.home();
});
expect(text).toBe('Home');
});An async function keeps the locale across its awaits, and run calls running at the same time stay apart.
Test paramsSchema
When paramsSchema accepts a locale, it sets it for the rest of the async flow that called it. The framework gives each pattern a flow of its own; a test calling it directly gets none.
i18n.test.tsimport { expect, it } from 'vitest';
import { locales } from './i18n';
it('accepts only the listed locales', () => {
const { validate } = locales.paramsSchema['~standard'];
expect(
locales.run('ja', () => validate({ locale: 'en' })),
).toStrictEqual({ value: { locale: 'en' } });
expect(validate({ locale: 'fr' })).toMatchObject({
issues: [{ path: ['locale'] }],
});
});So wrap a call that accepts in run. The locale it sets goes away once run returns, and does not leak into the tests after it. A locale outside the list is refused, and sets nothing.
Tests in a browser
In a real browser, such as Vitest’s browser mode, the URL is the locale. Change the pathname with history.replaceState, and put it back afterwards.
messages/nav.browser.test.tsimport { afterEach, expect, it } from 'vitest';
import { locales } from '../i18n';
import * as nav from './nav';
const initial = location.pathname;
afterEach(() => {
history.replaceState(null, '', initial);
});
it('renders in the locale the URL names', () => {
history.replaceState(null, '', '/en/cart');
expect(locales.getLocale()).toBe('en');
expect(nav.home()).toBe('Home');
});run throws in a browser. An environment that defines document, such as jsdom or happy-dom, is a browser as far as this package is concerned, so set the locale with the URL there too.
Do not define a set in a test
Call defineLocales in a test, and every message after it reads that set: the last set defined is the one used.
Tests import the app’s locales. If some test defines a set of its own, define the app’s set again in beforeEach.
import { defineLocales } from '@k8ordo/i18n';
import { beforeEach } from 'vitest';
import { locales } from '../i18n';
beforeEach(() => {
defineLocales(locales.definitions, { default: locales.default });
});Keep the type guarantees in tests
A message’s argument types, and the type error for a missing locale, can be pinned with type tests.
messages/cart.test.tsimport { message } from '@k8ordo/i18n';
import { expectTypeOf, it } from 'vitest';
import * as cart from './cart';
it('types the arguments of a message', () => {
expectTypeOf(cart.added).parameters.toEqualTypeOf<
[name: string]
>();
});
it('refuses a message without every locale', () => {
// @ts-expect-error
message({ ja: '保存' });
});The declaration under @ts-expect-error passes as long as the missing locale is a type error. Should Register stop applying, so that it compiles, the type check fails.