テストする
文言やgetLocale()がどのロケールを読むかは、テストが動く環境によって変わります。Nodeで動くテストではサーバーと同じくlocales.runで、ブラウザで動くテストではURLでロケールを決めます。
このページの内容
Nodeで動くテスト
何も指名しなければ、文言は既定のロケールの文を返します。別のロケールで確かめたいときは、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');
});asyncの関数を渡しても、awaitをまたいでロケールは保たれます。同時に動くrunどうしが混ざることもありません。
paramsSchemaを確かめる
paramsSchemaがロケールを受け付けると、呼んだ非同期の流れの残り全体に、そのロケールが設定されます。フレームワークはパターンごとに流れを分けますが、テストから直接呼ぶと分かれません。
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'] }],
});
});そのため、受け付ける呼び出しはrunで囲みます。設定したロケールはrunを抜けると元に戻るので、後に続くテストに漏れません。一覧に無いロケールは受け付けないので、何も設定しません。
ブラウザで動くテスト
Vitestのブラウザモードのような本物のブラウザでは、URLがロケールです。history.replaceStateでpathnameを変え、終わったら元に戻します。
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はエラーを投げます。jsdomやhappy-domのようにdocumentを定義する環境も、このパッケージにとってはブラウザなので、同じようにURLでロケールを決めます。
テストの中で集合を定義しない
テストの中でdefineLocalesを呼ぶと、それ以降の文言はその集合を読みます。最後に定義した集合が使われるためです。
テストでは、アプリのlocalesをimportして使います。別の集合を定義するテストがあるなら、beforeEachでアプリの集合を定義し直します。
import { defineLocales } from '@k8ordo/i18n';
import { beforeEach } from 'vitest';
import { locales } from '../i18n';
beforeEach(() => {
defineLocales(locales.definitions, { default: locales.default });
});型の保証をテストに残す
文言の引数の型や、ロケールが欠けたときの型エラーは、型のテストで固定できます。
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: '保存' });
});@ts-expect-errorを付けた宣言は、ロケールが欠けて型エラーになる限り通ります。Registerの登録が外れて型エラーにならなくなると、型チェックが失敗します。