import { act, render, screen, waitFor } from '@testing-library/react';
import { browser } from 'wxt/browser';
import { ThemeModeProvider, useThemeMode } from '@/providers/ThemeModeProvider';
import { storageUtil } from '@/utils/chromeStorage';
import { THEME_MODE_SNAPSHOT_KEY, THEME_MODE_STORAGE_KEY } from '@/utils/themeSnapshot';
vi.mock('@/utils/chromeStorage', () => ({
storageUtil: {
get: vi.fn(),
set: vi.fn(() => Promise.resolve()),
},
}));
const TestComponent = () => {
const { mode, resolvedMode, setMode } = useThemeMode();
return (
{mode}
{resolvedMode}
);
};
describe('ThemeModeProvider', () => {
beforeEach(() => {
vi.clearAllMocks();
localStorage.clear();
document.documentElement.classList.remove('dark');
(storageUtil.get as ReturnType).mockImplementation(
(_key: string, defaultValue: unknown) => Promise.resolve(defaultValue),
);
});
it('应从 localStorage 快照初始化主题', () => {
localStorage.setItem(THEME_MODE_SNAPSHOT_KEY, JSON.stringify('dark'));
render(
,
);
expect(screen.getByTestId('mode')).toHaveTextContent('dark');
expect(screen.getByTestId('resolved-mode')).toHaveTextContent('dark');
expect(document.documentElement.classList.contains('dark')).toBe(true);
expect(storageUtil.get).not.toHaveBeenCalled();
});
it('异步恢复 storage 后应写回 snapshot', async () => {
(storageUtil.get as ReturnType).mockResolvedValue('dark');
render(
,
);
await waitFor(() => {
expect(localStorage.getItem(THEME_MODE_SNAPSHOT_KEY)).toBe(JSON.stringify('dark'));
});
});
it('用户已切换主题时不应被较慢的 storage 恢复覆盖', async () => {
let resolveStorage: (value: 'light' | 'dark' | 'system') => void;
const storagePromise = new Promise<'light' | 'dark' | 'system'>((resolve) => {
resolveStorage = resolve;
});
(storageUtil.get as ReturnType).mockImplementation(() => storagePromise);
render(
,
);
await act(async () => {
screen.getByTestId('set-dark').click();
});
expect(screen.getByTestId('mode')).toHaveTextContent('dark');
await act(async () => {
resolveStorage!('light');
await storagePromise;
});
expect(screen.getByTestId('mode')).toHaveTextContent('dark');
});
it('storage.onChanged 同步时应更新 mode 与 snapshot', async () => {
(storageUtil.get as ReturnType).mockResolvedValue('system');
render(
,
);
await waitFor(() => {
expect(browser.storage.onChanged.addListener).toHaveBeenCalled();
});
const storageChangeHandler = vi.mocked(browser.storage.onChanged.addListener).mock
.calls[0][0] as (changes: Record) => void;
await act(async () => {
storageChangeHandler({
[THEME_MODE_STORAGE_KEY]: { newValue: 'dark' },
});
});
expect(screen.getByTestId('mode')).toHaveTextContent('dark');
expect(localStorage.getItem(THEME_MODE_SNAPSHOT_KEY)).toBe(JSON.stringify('dark'));
expect(document.documentElement.classList.contains('dark')).toBe(true);
});
});