1
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-20 09:54:17 +08:00
parent 67c584b4df
commit 50759f3c1e
159 changed files with 16938 additions and 16925 deletions

View File

@@ -13,6 +13,7 @@ import {
getCustomWorldAgentSession,
streamCustomWorldAgentMessage,
} from '../../services/aiService';
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
import type { AuthUser } from '../../services/authService';
import {
clearProfileBrowseHistory,
@@ -29,14 +30,30 @@ import {
} from '../../services/storageService';
import type { GameState } from '../../types';
import {
type PlatformSettingsSection,
AuthUiContext,
type PlatformSettingsSection,
} from '../auth/AuthUiContext';
import {
PreGameSelectionFlow,
type SelectionStage,
} from './PreGameSelectionFlow';
async function clickFirstButtonByName(
user: ReturnType<typeof userEvent.setup>,
name: string | RegExp,
) {
const buttons = screen.getAllByRole('button', { name });
await user.click(buttons[0]!);
}
async function clickFirstAsyncButtonByName(
user: ReturnType<typeof userEvent.setup>,
name: string | RegExp,
) {
const buttons = await screen.findAllByRole('button', { name });
await user.click(buttons[0]!);
}
vi.mock('../../services/aiService', () => ({
createCustomWorldAgentSession: vi.fn(),
executeCustomWorldAgentAction: vi.fn(),
@@ -204,6 +221,26 @@ type TestAuthValue = {
settingsError: string | null;
};
function createAuthValue(overrides: Partial<TestAuthValue> = {}): TestAuthValue {
return {
user: mockAuthUser,
openLoginModal: () => {},
requireAuth: (action) => action(),
openSettingsModal: () => {},
openAccountModal: () => {},
logout: async () => {},
setGlobalAccountActionsVisible: () => {},
musicVolume: 0.42,
setMusicVolume: () => {},
platformTheme: 'light',
setPlatformTheme: () => {},
isHydratingSettings: false,
isPersistingSettings: false,
settingsError: null,
...overrides,
};
}
function TestWrapper({
withAuth = false,
authValue,
@@ -211,7 +248,7 @@ function TestWrapper({
}: {
withAuth?: boolean;
authValue?: TestAuthValue;
onContinueGame?: (snapshot?: unknown) => void;
onContinueGame?: (snapshot?: HydratedSavedGameSnapshot | null) => void;
} = {}) {
const [selectionStage, setSelectionStage] =
useState<SelectionStage>('platform');
@@ -235,24 +272,7 @@ function TestWrapper({
return (
<AuthUiContext.Provider
value={
authValue ?? {
user: mockAuthUser,
openLoginModal: () => {},
requireAuth: (action) => action(),
openSettingsModal: () => {},
openAccountModal: () => {},
logout: async () => {},
setGlobalAccountActionsVisible: () => {},
musicVolume: 0.42,
setMusicVolume: () => {},
platformTheme: 'light',
setPlatformTheme: () => {},
isHydratingSettings: false,
isPersistingSettings: false,
settingsError: null,
}
}
value={authValue ?? createAuthValue()}
>
{content}
</AuthUiContext.Provider>
@@ -292,7 +312,7 @@ beforeEach(() => {
bottomTab: 'adventure',
currentStory: null,
gameState: {} as GameState,
},
} as HydratedSavedGameSnapshot,
});
vi.mocked(upsertProfileBrowseHistory).mockResolvedValue([]);
vi.mocked(clearProfileBrowseHistory).mockResolvedValue([]);
@@ -351,8 +371,8 @@ test('create tab opens game type modal, keeps AIRP and visual novel locked, and
render(<TestWrapper />);
await user.click(screen.getByRole('button', { name: '创作' }));
await user.click(screen.getByRole('button', { name: //u }));
await clickFirstButtonByName(user, '创作');
await clickFirstButtonByName(user, //u);
expect(screen.getByText('选择创作类型')).toBeTruthy();
@@ -399,14 +419,11 @@ test('clicking a public work while logged out routes through requireAuth', async
render(
<TestWrapper
authValue={{
authValue={createAuthValue({
user: null,
openLoginModal: () => {},
requireAuth,
openAccountModal: () => {},
logout: async () => {},
setGlobalAccountActionsVisible: () => {},
}}
})}
/>,
);
@@ -425,19 +442,16 @@ test('selecting RPG creation while logged out routes through requireAuth', async
render(
<TestWrapper
authValue={{
authValue={createAuthValue({
user: null,
openLoginModal: () => {},
requireAuth,
openAccountModal: () => {},
logout: async () => {},
setGlobalAccountActionsVisible: () => {},
}}
})}
/>,
);
await user.click(screen.getByRole('button', { name: '创作' }));
await user.click(screen.getByRole('button', { name: //u }));
await clickFirstButtonByName(user, '创作');
await clickFirstButtonByName(user, //u);
await user.click(screen.getByRole('button', { name: / RPG/u }));
expect(requireAuth).toHaveBeenCalledTimes(1);
@@ -449,8 +463,8 @@ test('starting draft generation leaves the agent workspace and shows the generat
render(<TestWrapper />);
await user.click(screen.getByRole('button', { name: '创作' }));
await user.click(screen.getByRole('button', { name: //u }));
await clickFirstButtonByName(user, '创作');
await clickFirstButtonByName(user, //u);
await user.click(screen.getByRole('button', { name: / RPG/u }));
expect(
@@ -582,8 +596,8 @@ test('existing draft sessions enter the legacy result layout directly', async ()
render(<TestWrapper />);
await user.click(screen.getByRole('button', { name: '创作' }));
await user.click(screen.getByRole('button', { name: //u }));
await clickFirstButtonByName(user, '创作');
await clickFirstButtonByName(user, //u);
await user.click(screen.getByRole('button', { name: / RPG/u }));
await waitFor(
@@ -608,8 +622,6 @@ test('existing draft sessions enter the legacy result layout directly', async ()
});
test('authenticated users with save archives default into the saves tab', async () => {
const user = userEvent.setup();
vi.mocked(listProfileSaveArchives).mockResolvedValue([
{
worldKey: 'custom:world-1',
@@ -626,9 +638,9 @@ test('authenticated users with save archives default into the saves tab', async
render(<TestWrapper withAuth />);
expect(await screen.findByText('全部存档')).toBeTruthy();
expect(await screen.findByText('潮雾列岛')).toBeTruthy();
expect(screen.getByText('最近更新时间排序')).toBeTruthy();
expect((await screen.findAllByText('全部存档')).length).toBeGreaterThan(0);
expect((await screen.findAllByText('潮雾列岛')).length).toBeGreaterThan(0);
expect(screen.getAllByText('最近更新时间排序').length).toBeGreaterThan(0);
});
test('save tab can resume a selected archive directly into the game', async () => {
@@ -668,12 +680,12 @@ test('save tab can resume a selected archive directly into the game', async () =
gameState: {
worldType: 'CUSTOM',
} as GameState,
},
} as HydratedSavedGameSnapshot,
});
render(<TestWrapper withAuth onContinueGame={handleContinueGame} />);
await user.click(await screen.findByRole('button', { name: //u }));
await clickFirstAsyncButtonByName(user, //u);
await waitFor(() => {
expect(resumeProfileSaveArchive).toHaveBeenCalledWith('custom:world-1');
@@ -719,8 +731,8 @@ test('owned world detail can delete a work and return to the create tab list', a
render(<TestWrapper withAuth />);
await user.click(screen.getByRole('button', { name: '创作' }));
await user.click(await screen.findByRole('button', { name: //u }));
await clickFirstButtonByName(user, '创作');
await clickFirstAsyncButtonByName(user, //u);
await user.click(await screen.findByRole('button', { name: '删除作品' }));
await waitFor(() => {
@@ -731,6 +743,7 @@ test('owned world detail can delete a work and return to the create tab list', a
expect(screen.queryByRole('button', { name: '删除作品' })).toBeNull();
});
expect(
screen.getByText('你还没有保存任何自定义世界,先创建一个草稿开始吧。'),
screen.getAllByText('你还没有保存任何自定义世界,先创建一个草稿开始吧。')
.length,
).toBeTruthy();
});