1
This commit is contained in:
162
src/hooks/runtimeAuthGuards.test.tsx
Normal file
162
src/hooks/runtimeAuthGuards.test.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import { readSavedSettings } from '../persistence/gameSettingsStorage';
|
||||
import type { GameState, StoryMoment } from '../types';
|
||||
import type { BottomTab } from '../types/navigation';
|
||||
import { useGamePersistence } from './useGamePersistence';
|
||||
import { useGameSettings } from './useGameSettings';
|
||||
|
||||
const storageMocks = vi.hoisted(() => ({
|
||||
getSettings: vi.fn(),
|
||||
putSettings: vi.fn(),
|
||||
getSaveSnapshot: vi.fn(),
|
||||
putSaveSnapshot: vi.fn(),
|
||||
deleteSaveSnapshot: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../services/storageService', () => ({
|
||||
getSettings: storageMocks.getSettings,
|
||||
putSettings: storageMocks.putSettings,
|
||||
getSaveSnapshot: storageMocks.getSaveSnapshot,
|
||||
putSaveSnapshot: storageMocks.putSaveSnapshot,
|
||||
deleteSaveSnapshot: storageMocks.deleteSaveSnapshot,
|
||||
}));
|
||||
|
||||
vi.mock('./story/runtimeStoryCoordinator', () => ({
|
||||
resumeServerRuntimeStory: vi.fn(),
|
||||
}));
|
||||
|
||||
function SettingsHarness({ authenticatedUserId }: { authenticatedUserId: string | null }) {
|
||||
const settings = useGameSettings(authenticatedUserId);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="music-volume">{settings.musicVolume.toFixed(2)}</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
settings.setMusicVolume(0.6);
|
||||
}}
|
||||
>
|
||||
设置音量
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PersistenceHarness({
|
||||
authenticatedUserId,
|
||||
}: {
|
||||
authenticatedUserId: string | null;
|
||||
}) {
|
||||
const persistence = useGamePersistence({
|
||||
authenticatedUserId,
|
||||
gameState: {} as GameState,
|
||||
bottomTab: 'adventure' as BottomTab,
|
||||
currentStory: null as StoryMoment | null,
|
||||
isLoading: false,
|
||||
setGameState: () => {},
|
||||
setBottomTab: () => {},
|
||||
hydrateStoryState: () => {},
|
||||
resetStoryState: () => {},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="saved-game">{persistence.hasSavedGame ? 'yes' : 'no'}</div>
|
||||
<div data-testid="hydrating">
|
||||
{persistence.isHydratingSnapshot ? 'yes' : 'no'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.useRealTimers();
|
||||
window.localStorage.clear();
|
||||
storageMocks.getSettings.mockResolvedValue({
|
||||
musicVolume: 0.42,
|
||||
platformTheme: 'light',
|
||||
});
|
||||
storageMocks.putSettings.mockResolvedValue({
|
||||
musicVolume: 0.6,
|
||||
platformTheme: 'light',
|
||||
});
|
||||
storageMocks.getSaveSnapshot.mockResolvedValue(null);
|
||||
storageMocks.putSaveSnapshot.mockResolvedValue(null);
|
||||
storageMocks.deleteSaveSnapshot.mockResolvedValue({
|
||||
ok: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('unauthenticated settings use local cache and skip remote runtime settings requests', async () => {
|
||||
window.localStorage.setItem(
|
||||
'tavernrealms.settings.v1',
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
musicVolume: 0.33,
|
||||
platformTheme: 'dark',
|
||||
}),
|
||||
);
|
||||
|
||||
render(<SettingsHarness authenticatedUserId={null} />);
|
||||
|
||||
expect(screen.getByTestId('music-volume').textContent).toBe('0.33');
|
||||
expect(storageMocks.getSettings).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
screen.getByRole('button', { name: '设置音量' }).click();
|
||||
});
|
||||
|
||||
expect(storageMocks.putSettings).not.toHaveBeenCalled();
|
||||
expect(readSavedSettings().musicVolume).toBeCloseTo(0.6);
|
||||
});
|
||||
|
||||
test('authenticated settings hydrate from remote settings and sync later changes back to the server', async () => {
|
||||
storageMocks.getSettings.mockResolvedValue({
|
||||
musicVolume: 0.8,
|
||||
platformTheme: 'dark',
|
||||
});
|
||||
|
||||
render(<SettingsHarness authenticatedUserId="user-1" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(storageMocks.getSettings).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(screen.getByTestId('music-volume').textContent).toBe('0.80');
|
||||
|
||||
vi.useFakeTimers();
|
||||
|
||||
act(() => {
|
||||
screen.getByRole('button', { name: '设置音量' }).click();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(200);
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(storageMocks.putSettings).toHaveBeenCalledTimes(1);
|
||||
expect(storageMocks.putSettings).toHaveBeenCalledWith(
|
||||
{ musicVolume: 0.6, platformTheme: 'dark' },
|
||||
expect.objectContaining({
|
||||
signal: expect.any(AbortSignal),
|
||||
}),
|
||||
);
|
||||
expect(readSavedSettings().musicVolume).toBeCloseTo(0.6);
|
||||
});
|
||||
|
||||
test('unauthenticated runtime skips remote snapshot hydration', async () => {
|
||||
render(<PersistenceHarness authenticatedUserId={null} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('hydrating').textContent).toBe('no');
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('saved-game').textContent).toBe('no');
|
||||
expect(storageMocks.getSaveSnapshot).not.toHaveBeenCalled();
|
||||
});
|
||||
Reference in New Issue
Block a user