テストする
@k8ordo/color-schemeは、localStorageと<html>のクラス、matchMediaを使います。どれもブラウザにしか無いので、テストもVitestのブラウザモードのような本物のブラウザで動かします。このページでは、テストの間で状態を戻す方法と、プロバイダの下でフックを描いて確かめる方法を説明します。
このページの内容
テストの間で状態を戻す
プロバイダは、localStorageの行と<html>のクラス、@k8ordo/stateのストアを読み書きします。どれもテストをまたいで残るので、各テストの前に3つとも戻します。
color-scheme.test.tsximport { resetStateRegistry } from '@k8ordo/state';
import { beforeEach } from 'vitest';
beforeEach(() => {
localStorage.clear();
document.documentElement.classList.remove('dark');
resetStateRegistry();
});localStorage.clear():前のテストで保存した選択を消します。残っていると、次のテストは何も選んでいない状態から始まりません。classList.remove('dark'):前のテストでプロバイダのeffectが付けたクラスを外します。resetStateRegistry():@k8ordo/stateのストアを捨てます。ストアはキーごとに1つ作られてテストをまたいで残るので、捨てないと前のテストの値を持ち続けます。
メモ
resetStateRegistry()を呼ぶときは、フックを使うコンポーネントがアンマウントされている必要があります。マウントされたままのフックは、捨てたはずの古いストアを持ち続けるからです。vitest-browser-reactを使っていれば、前のテストの描画は各テストの前に片付けられます。
プロバイダの下でフックを描く
useColorScheme()はプロバイダの外では例外を投げるので、プロバイダをrenderHookのwrapperに渡して描きます。
color-scheme.test.tsximport {
ColorSchemeProvider,
colorSchemeState,
useColorScheme,
} from '@k8ordo/color-scheme';
import type { ReactNode } from 'react';
import { expect, it, vi } from 'vitest';
import { renderHook } from 'vitest-browser-react';
const wrapper = ({ children }: { children: ReactNode }) => (
<ColorSchemeProvider>{children}</ColorSchemeProvider>
);
it('starts from a stored preference', async () => {
localStorage.setItem(
colorSchemeState.storageKey,
JSON.stringify({ preference: 'dark' }),
);
const { result } = await renderHook(() => useColorScheme(), {
wrapper,
});
expect(result.current.preference).toBe('dark');
expect(result.current.scheme).toBe('dark');
expect(
document.documentElement.classList.contains('dark'),
).toBe(true);
});localStorageのキーは、文字列で書かずにcolorSchemeState.storageKeyから読みます。キーの組み立て方を決めているのは@k8ordo/stateだからです。
選択を変えて確かめる
setPreferenceで選択を変えたら、vi.waitForで描き直しを待ってから確かめます。
color-scheme.test.tsxit('stores a choice, and stores none for system', async () => {
const { result } = await renderHook(() => useColorScheme(), {
wrapper,
});
result.current.setPreference('dark');
await vi.waitFor(() => {
expect(result.current.scheme).toBe('dark');
});
expect(
document.documentElement.classList.contains('dark'),
).toBe(true);
expect(localStorage.getItem(colorSchemeState.storageKey)).toBe(
'{"preference":"dark"}',
);
result.current.setPreference('system');
await vi.waitFor(() => {
expect(result.current.preference).toBe('system');
});
expect(
localStorage.getItem(colorSchemeState.storageKey),
).toBe('{}');
});setPreferenceを呼んでも、その場では描き直されません。localStorageへの書き込みも、マイクロタスクにまとめてから行われます。描き直しが済めば書き込みも済んでいるので、schemeやpreferenceが変わるのを待ってから、行とクラスを確かめます。
'system'に戻したときは行が消えるのではなく、preferenceの無い{}が残ります。
OSがダークのブラウザで確かめる
'system'がどちらに決まるかは、テストを動かすブラウザのprefers-color-schemeしだいです。Playwrightで起動したブラウザは、指定しなければライトを好みます。
既定値から始まることを確かめるだけなら、defaultPreference="dark"を渡したプロバイダをwrapperにすれば足ります。OSの設定に従うことそのものを確かめたいときは、ブラウザのほうをダークにします。
vite.config.tsimport { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
browser: {
enabled: true,
provider: playwright({
contextOptions: { colorScheme: 'dark' },
}),
instances: [{ browser: 'chromium' }],
},
},
});この設定は、そのブラウザで動くすべてのテストにかかります。
インラインスクリプトは走らない
テストのようにブラウザの中だけで描くと、プロバイダが描くインラインスクリプトは実行されません。Reactは、ブラウザで自分が作ったインラインの<script>を実行しないからです。
開発ビルドのReactは、そのことをコンソールにエラーとして出します。Encountered a script tag while rendering React componentで始まるメッセージですが、テストが失敗したわけではありません。
そのため、テストで確かめる<html>のクラスは、プロバイダのeffectが付けたものです。スクリプトが最初の描画の前にクラスを付けることは、このパッケージ自身のテストで確かめています。