1
This commit is contained in:
@@ -7,6 +7,7 @@ import { beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
||||
import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||||
import type { PuzzleRunSnapshot } from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
|
||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||
import type { RpgCreationResultView } from '../../../packages/shared/src/contracts/rpgCreationResultView';
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
getPuzzleGalleryDetail,
|
||||
listPuzzleGallery,
|
||||
} from '../../services/puzzle-gallery';
|
||||
import { startPuzzleRun } from '../../services/puzzle-runtime';
|
||||
import { listPuzzleWorks } from '../../services/puzzle-works';
|
||||
import {
|
||||
createRpgCreationSession,
|
||||
@@ -81,12 +83,12 @@ async function clickFirstAsyncButtonByName(
|
||||
|
||||
async function openCreationHub(user: ReturnType<typeof userEvent.setup>) {
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
expect(await screen.findByText('角色扮演 RPG')).toBeTruthy();
|
||||
expect(await screen.findByText('角色扮演')).toBeTruthy();
|
||||
}
|
||||
|
||||
async function openNewRpgCreation(user: ReturnType<typeof userEvent.setup>) {
|
||||
await openCreationHub(user);
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演 RPG/u }));
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演.*剧情演绎/u }));
|
||||
}
|
||||
|
||||
function getPlatformTabPanel(tab: string) {
|
||||
@@ -134,9 +136,20 @@ vi.mock('../../services/puzzle-gallery', () => ({
|
||||
listPuzzleGallery: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/puzzle-runtime', () => ({
|
||||
advanceLocalPuzzleNextLevel: vi.fn(),
|
||||
dragPuzzlePieceOrGroup: vi.fn(),
|
||||
startPuzzleRun: vi.fn(),
|
||||
swapPuzzlePieces: vi.fn(),
|
||||
submitPuzzleLeaderboard: vi.fn(),
|
||||
updatePuzzleRunPause: vi.fn(),
|
||||
usePuzzleRuntimeProp: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/rpg-entry/rpgEntryLibraryClient', () => ({
|
||||
deleteRpgEntryWorldProfile: vi.fn(),
|
||||
getRpgEntryWorldGalleryDetailByCode: vi.fn(),
|
||||
getRpgEntryWorldLibraryDetail: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/big-fish-creation', () => ({
|
||||
@@ -362,6 +375,7 @@ const mockAuthUser: AuthUser = {
|
||||
id: 'user-1',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
publicUserCode: 'user-tester',
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
@@ -369,6 +383,62 @@ const mockAuthUser: AuthUser = {
|
||||
wechatBound: false,
|
||||
};
|
||||
|
||||
function buildMockPuzzleRun(
|
||||
profileId: string,
|
||||
levelName: string,
|
||||
): PuzzleRunSnapshot {
|
||||
const gridSize = 3 as const;
|
||||
|
||||
return {
|
||||
runId: `run-${profileId}`,
|
||||
entryProfileId: profileId,
|
||||
clearedLevelCount: 0,
|
||||
currentLevelIndex: 1,
|
||||
currentGridSize: gridSize,
|
||||
playedProfileIds: [profileId],
|
||||
previousLevelTags: ['机关'],
|
||||
recommendedNextProfileId: null,
|
||||
leaderboardEntries: [],
|
||||
currentLevel: {
|
||||
runId: `run-${profileId}`,
|
||||
levelIndex: 1,
|
||||
gridSize,
|
||||
profileId,
|
||||
levelName,
|
||||
authorDisplayName: '拼图作者',
|
||||
themeTags: ['机关'],
|
||||
coverImageSrc: null,
|
||||
status: 'playing',
|
||||
startedAtMs: 1_000,
|
||||
clearedAtMs: null,
|
||||
elapsedMs: null,
|
||||
timeLimitMs: 300_000,
|
||||
remainingMs: 300_000,
|
||||
pausedAccumulatedMs: 0,
|
||||
pauseStartedAtMs: null,
|
||||
freezeAccumulatedMs: 0,
|
||||
freezeStartedAtMs: null,
|
||||
freezeUntilMs: null,
|
||||
leaderboardEntries: [],
|
||||
board: {
|
||||
rows: 3,
|
||||
cols: 3,
|
||||
selectedPieceId: null,
|
||||
allTilesResolved: false,
|
||||
mergedGroups: [],
|
||||
pieces: Array.from({ length: 9 }, (_, index) => ({
|
||||
pieceId: `piece-${index}`,
|
||||
correctRow: Math.floor(index / 3),
|
||||
correctCol: index % 3,
|
||||
currentRow: Math.floor(index / 3),
|
||||
currentCol: index % 3,
|
||||
mergedGroupId: null,
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const compiledAgentDraftSession: CustomWorldAgentSessionSnapshot = {
|
||||
...mockSession,
|
||||
stage: 'object_refining',
|
||||
@@ -540,14 +610,14 @@ function buildResultViewForSession(
|
||||
session,
|
||||
profile,
|
||||
profileSource: profile ? 'result_preview' : 'none',
|
||||
targetStage: profile && isResultStage
|
||||
? 'custom-world-result'
|
||||
: session.stage === 'error'
|
||||
? 'custom-world-generating'
|
||||
: 'agent-workspace',
|
||||
generationViewSource: session.stage === 'error'
|
||||
? 'agent-draft-foundation'
|
||||
: null,
|
||||
targetStage:
|
||||
profile && isResultStage
|
||||
? 'custom-world-result'
|
||||
: session.stage === 'error'
|
||||
? 'custom-world-generating'
|
||||
: 'agent-workspace',
|
||||
generationViewSource:
|
||||
session.stage === 'error' ? 'agent-draft-foundation' : null,
|
||||
resultViewSource: profile && isResultStage ? 'agent-draft' : null,
|
||||
canAutosaveLibrary: Boolean(profile && isResultStage),
|
||||
canSyncResultProfile:
|
||||
@@ -558,11 +628,12 @@ function buildResultViewForSession(
|
||||
publishReady: Boolean(session.resultPreview?.publishReady),
|
||||
canEnterWorld: Boolean(session.resultPreview?.canEnterWorld),
|
||||
blockerCount: session.resultPreview?.blockers?.length ?? 0,
|
||||
recoveryAction: profile && isResultStage
|
||||
? 'open_result'
|
||||
: session.stage === 'error'
|
||||
? 'resume_generation'
|
||||
: 'continue_agent',
|
||||
recoveryAction:
|
||||
profile && isResultStage
|
||||
? 'open_result'
|
||||
: session.stage === 'error'
|
||||
? 'resume_generation'
|
||||
: 'continue_agent',
|
||||
recoveryReason: null,
|
||||
};
|
||||
}
|
||||
@@ -574,6 +645,7 @@ type TestAuthValue = {
|
||||
requireAuth: (action: () => void) => void;
|
||||
openSettingsModal: (section?: PlatformSettingsSection) => void;
|
||||
openAccountModal: () => void;
|
||||
setCurrentUser: (user: AuthUser) => void;
|
||||
logout: () => Promise<void>;
|
||||
musicVolume: number;
|
||||
setMusicVolume: (value: number) => void;
|
||||
@@ -594,6 +666,7 @@ function createAuthValue(
|
||||
requireAuth: (action) => action(),
|
||||
openSettingsModal: () => {},
|
||||
openAccountModal: () => {},
|
||||
setCurrentUser: () => {},
|
||||
logout: async () => {},
|
||||
musicVolume: 0.42,
|
||||
setMusicVolume: () => {},
|
||||
@@ -1160,7 +1233,7 @@ test('create hub exposes direct template entry, keeps AIRP and visual novel lock
|
||||
expect((airpButton as HTMLButtonElement).disabled).toBe(true);
|
||||
expect((visualNovelButton as HTMLButtonElement).disabled).toBe(true);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演 RPG/u }));
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演.*剧情演绎/u }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createRpgCreationSession).toHaveBeenCalledTimes(1);
|
||||
@@ -1183,7 +1256,7 @@ test('platform create hub does not prefetch hidden big fish platform data', asyn
|
||||
await openCreationHub(user);
|
||||
|
||||
expect(
|
||||
await screen.findByRole('button', { name: /角色扮演 RPG/u }),
|
||||
await screen.findByRole('button', { name: /角色扮演.*剧情演绎/u }),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: /大鱼吃小鱼/u })).toBeNull();
|
||||
expect(listBigFishWorks).not.toHaveBeenCalled();
|
||||
@@ -1406,7 +1479,9 @@ test('opening a compiled draft with a missing agent session falls back to create
|
||||
code: 'NOT_FOUND',
|
||||
});
|
||||
vi.mocked(getRpgCreationSession).mockRejectedValueOnce(missingSessionError);
|
||||
vi.mocked(getRpgCreationResultView).mockRejectedValueOnce(missingSessionError);
|
||||
vi.mocked(getRpgCreationResultView).mockRejectedValueOnce(
|
||||
missingSessionError,
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -1569,7 +1644,7 @@ test('creation hub clears all private work shelves immediately after logout stat
|
||||
expect(within(createPanel).getByText('还没有作品')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('published puzzle works appear on home and category public shelves', async () => {
|
||||
test('published puzzle works appear on home and mobile game category channel', async () => {
|
||||
const user = userEvent.setup();
|
||||
const publishedPuzzleWork = {
|
||||
workId: 'puzzle-work-public-1',
|
||||
@@ -1596,6 +1671,12 @@ test('published puzzle works appear on home and category public shelves', async
|
||||
vi.mocked(getPuzzleGalleryDetail).mockResolvedValue({
|
||||
item: publishedPuzzleWork,
|
||||
});
|
||||
vi.mocked(startPuzzleRun).mockResolvedValue({
|
||||
run: buildMockPuzzleRun(
|
||||
publishedPuzzleWork.profileId,
|
||||
publishedPuzzleWork.levelName,
|
||||
),
|
||||
});
|
||||
|
||||
render(<TestWrapper />);
|
||||
|
||||
@@ -1603,18 +1684,18 @@ test('published puzzle works appear on home and category public shelves', async
|
||||
expect(screen.getAllByText('星桥机关').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '分类' }));
|
||||
await user.click(screen.getByRole('button', { name: '游戏分类' }));
|
||||
|
||||
const categoryPanel = getPlatformTabPanel('category');
|
||||
expect(within(categoryPanel).getAllByText('星桥机关').length).toBeGreaterThan(
|
||||
0,
|
||||
);
|
||||
const homePanel = getPlatformTabPanel('home');
|
||||
expect(within(homePanel).getAllByText('星桥机关').length).toBeGreaterThan(0);
|
||||
expect(
|
||||
within(categoryPanel).getAllByRole('button', { name: /机关/u }).length,
|
||||
within(homePanel).getAllByRole('button', { name: /机关/u }).length,
|
||||
).toBeGreaterThan(0);
|
||||
expect(screen.queryByRole('button', { name: 'PC游戏' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '即点即玩' })).toBeNull();
|
||||
});
|
||||
|
||||
test('published big fish works stay hidden from platform home and category shelves', async () => {
|
||||
test('published big fish works stay hidden from platform home and game category channel', async () => {
|
||||
const user = userEvent.setup();
|
||||
const publishedBigFishWork: BigFishWorkSummary = {
|
||||
workId: 'big-fish-work-public-1',
|
||||
@@ -1644,16 +1725,16 @@ test('published big fish works stay hidden from platform home and category shelv
|
||||
});
|
||||
expect(screen.queryByText('机械深海 大鱼吃小鱼')).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '分类' }));
|
||||
await user.click(screen.getByRole('button', { name: '游戏分类' }));
|
||||
|
||||
const categoryPanel = getPlatformTabPanel('category');
|
||||
expect(within(categoryPanel).queryByText('机械深海 大鱼吃小鱼')).toBeNull();
|
||||
const homePanel = getPlatformTabPanel('home');
|
||||
expect(within(homePanel).queryByText('机械深海 大鱼吃小鱼')).toBeNull();
|
||||
expect(
|
||||
within(categoryPanel).queryAllByRole('button', { name: /大鱼/u }).length,
|
||||
within(homePanel).queryAllByRole('button', { name: /大鱼/u }).length,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('published puzzle detail returns to the source platform tab', async () => {
|
||||
test('published puzzle detail returns to the ranking platform tab', async () => {
|
||||
const user = userEvent.setup();
|
||||
const publishedPuzzleWork = {
|
||||
workId: 'puzzle-work-public-1',
|
||||
@@ -1680,37 +1761,48 @@ test('published puzzle detail returns to the source platform tab', async () => {
|
||||
vi.mocked(getPuzzleGalleryDetail).mockResolvedValue({
|
||||
item: publishedPuzzleWork,
|
||||
});
|
||||
vi.mocked(startPuzzleRun).mockResolvedValue({
|
||||
run: buildMockPuzzleRun(
|
||||
publishedPuzzleWork.profileId,
|
||||
publishedPuzzleWork.levelName,
|
||||
),
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: '分类' }));
|
||||
await user.click(await screen.findByRole('button', { name: '排行' }));
|
||||
await waitFor(() => {
|
||||
expect(document.getElementById('platform-tab-panel-category')).toBeTruthy();
|
||||
});
|
||||
await waitFor(() => {
|
||||
const categoryPanel = getPlatformTabPanel('category');
|
||||
const rankingPanel = getPlatformTabPanel('category');
|
||||
expect(
|
||||
within(categoryPanel).getAllByText('星桥机关').length,
|
||||
within(rankingPanel).getAllByText('星桥机关').length,
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
const categoryPanel = getPlatformTabPanel('category');
|
||||
const rankingPanel = getPlatformTabPanel('category');
|
||||
|
||||
await user.click(
|
||||
within(categoryPanel).getByRole('button', {
|
||||
name: /拼图关卡.*星桥机关/u,
|
||||
within(rankingPanel).getByRole('button', {
|
||||
name: /星桥机关/u,
|
||||
}),
|
||||
);
|
||||
expect(
|
||||
await screen.findByRole('button', { name: '进入第 1 关' }),
|
||||
).toBeTruthy();
|
||||
await user.click(await screen.findByRole('button', { name: '启动' }));
|
||||
expect(await screen.findByTestId('puzzle-board')).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '返回上一页' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: '启动' })).toBeTruthy();
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '返回' }));
|
||||
|
||||
await waitFor(() => {
|
||||
const returnedCategoryPanel = getPlatformTabPanel('category');
|
||||
expect(returnedCategoryPanel.getAttribute('aria-hidden')).toBe('false');
|
||||
const returnedRankingPanel = getPlatformTabPanel('category');
|
||||
expect(returnedRankingPanel.getAttribute('aria-hidden')).toBe('false');
|
||||
expect(
|
||||
within(returnedCategoryPanel).getAllByText('星桥机关').length,
|
||||
within(returnedRankingPanel).getAllByText('星桥机关').length,
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -1854,7 +1946,7 @@ test('new creation entry maps raw bearer token errors to user-facing auth copy',
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreationHub(user);
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演 RPG/u }));
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演.*剧情演绎/u }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -1892,7 +1984,7 @@ test('puzzle creation timeout exits busy state and shows a readable error', asyn
|
||||
|
||||
await openCreationHub(user);
|
||||
|
||||
const button = screen.getByRole('button', { name: /拼图玩法/u });
|
||||
const button = screen.getByRole('button', { name: /拼图.*创意礼物/u });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -2787,7 +2879,7 @@ test('agent draft result back button returns to creation hub without syncing res
|
||||
await user.click(screen.getByRole('button', { name: /返回创作/u }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('角色扮演 RPG')).toBeTruthy();
|
||||
expect(screen.getByText('角色扮演')).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -2905,23 +2997,25 @@ test('agent draft result auto-save syncs result profile before persisting backen
|
||||
vi.mocked(getRpgCreationResultView).mockResolvedValue(
|
||||
buildResultViewForSession(syncedSession),
|
||||
);
|
||||
vi.mocked(executeRpgCreationAction).mockImplementation(async (_, payload) => ({
|
||||
operation: {
|
||||
operationId:
|
||||
payload.action === 'sync_result_profile'
|
||||
? 'operation-sync-result-profile-1'
|
||||
: 'operation-draft-foundation-1',
|
||||
type: payload.action,
|
||||
status: 'queued',
|
||||
phaseLabel: '已接收请求',
|
||||
phaseDetail:
|
||||
payload.action === 'sync_result_profile'
|
||||
? '正在同步结果页档案。'
|
||||
: '正在准备生成世界底稿。',
|
||||
progress: 10,
|
||||
error: null,
|
||||
},
|
||||
}));
|
||||
vi.mocked(executeRpgCreationAction).mockImplementation(
|
||||
async (_, payload) => ({
|
||||
operation: {
|
||||
operationId:
|
||||
payload.action === 'sync_result_profile'
|
||||
? 'operation-sync-result-profile-1'
|
||||
: 'operation-draft-foundation-1',
|
||||
type: payload.action,
|
||||
status: 'queued',
|
||||
phaseLabel: '已接收请求',
|
||||
phaseDetail:
|
||||
payload.action === 'sync_result_profile'
|
||||
? '正在同步结果页档案。'
|
||||
: '正在准备生成世界底稿。',
|
||||
progress: 10,
|
||||
error: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(getRpgCreationOperation).mockResolvedValue({
|
||||
operationId: 'operation-sync-result-profile-1',
|
||||
type: 'sync_result_profile',
|
||||
@@ -3070,13 +3164,13 @@ test('manual tab switch is preserved after platform bootstrap requests finish',
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
expect(await screen.findByText('角色扮演 RPG')).toBeTruthy();
|
||||
expect(await screen.findByText('角色扮演')).toBeTruthy();
|
||||
|
||||
resolveGalleryRequest([]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
within(getPlatformTabPanel('create')).getByText('角色扮演 RPG'),
|
||||
within(getPlatformTabPanel('create')).getByText('角色扮演'),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user