This commit is contained in:
2026-05-11 20:27:41 +08:00
parent e30b733b17
commit 481a27fc53
60 changed files with 6357 additions and 1100 deletions

View File

@@ -1,6 +1,6 @@
/* @vitest-environment jsdom */
import { render, screen, waitFor, within } from '@testing-library/react';
import { act, render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useState } from 'react';
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
@@ -11,18 +11,14 @@ import type {
CustomWorldAgentSessionSnapshot,
CustomWorldWorkSummary,
} from '../../../packages/shared/src/contracts/customWorldAgent';
import type {
Match3DAgentSessionSnapshot,
} from '../../../packages/shared/src/contracts/match3dAgent';
import type { Match3DAgentSessionSnapshot } from '../../../packages/shared/src/contracts/match3dAgent';
import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/match3dRuntime';
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type {
PuzzleAnchorPack,
PuzzleResultDraft,
} from '../../../packages/shared/src/contracts/puzzleAgentDraft';
import type {
PuzzleAgentSessionSnapshot,
} from '../../../packages/shared/src/contracts/puzzleAgentSession';
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
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';
@@ -33,10 +29,6 @@ import type {
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
import { ApiClientError } from '../../services/apiClient';
import type { AuthUser } from '../../services/authService';
import {
fetchCreationEntryConfig,
type CreationEntryConfig,
} from '../../services/creationEntryConfigService';
import {
createBigFishCreationSession,
getBigFishCreationSession,
@@ -48,6 +40,10 @@ import {
submitBigFishInput,
} from '../../services/big-fish-runtime';
import { listBigFishWorks } from '../../services/big-fish-works';
import {
type CreationEntryConfig,
fetchCreationEntryConfig,
} from '../../services/creationEntryConfigService';
import {
cancelCreativeAgentSession,
confirmCreativePuzzleTemplate,
@@ -155,7 +151,7 @@ async function clickFirstButtonByName(
user: ReturnType<typeof userEvent.setup>,
name: string | RegExp,
) {
const buttons = screen.getAllByRole('button', { name });
const buttons = await screen.findAllByRole('button', { name });
await user.click(buttons[0]!);
}
@@ -169,9 +165,7 @@ async function clickFirstAsyncButtonByName(
async function openCreateTemplateHub(user: ReturnType<typeof userEvent.setup>) {
await clickFirstButtonByName(user, '创作');
expect(
await screen.findByRole('tablist', { name: '选择模板' }),
).toBeTruthy();
expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy();
expect(screen.getByRole('tab', { name: '拼图' })).toBeTruthy();
expect(screen.getByText('拼图工作区missing-session')).toBeTruthy();
}
@@ -194,14 +188,14 @@ async function openDiscoverHub(user: ReturnType<typeof userEvent.setup>) {
expect(panel.getAttribute('aria-hidden')).toBe('false');
});
expect(
await within(panel).findByPlaceholderText(
'搜索作品号、名称、作者、描述',
),
await within(panel).findByPlaceholderText('搜索作品号、名称、作者、描述'),
).toBeTruthy();
return panel;
}
async function openProfilePlayedWorks(user: ReturnType<typeof userEvent.setup>) {
async function openProfilePlayedWorks(
user: ReturnType<typeof userEvent.setup>,
) {
await clickFirstButtonByName(user, '我的');
await user.click(await screen.findByRole('button', { name: //u }));
expect(await screen.findByText('可继续')).toBeTruthy();
@@ -348,7 +342,8 @@ vi.mock('../../services/rpg-creation/index', () => ({
vi.mock('../../services/rpg-entry', () => ({
clearRpgProfileBrowseHistory: vi.fn(),
deleteRpgEntryWorldProfile: rpgEntryLibraryServiceMocks.deleteRpgEntryWorldProfile,
deleteRpgEntryWorldProfile:
rpgEntryLibraryServiceMocks.deleteRpgEntryWorldProfile,
getRpgEntryWorldGalleryDetail:
rpgEntryLibraryServiceMocks.getRpgEntryWorldGalleryDetail,
getRpgProfileDashboard: vi.fn(),
@@ -435,6 +430,7 @@ vi.mock('../../services/match3d-works', () => ({
getMatch3DWorkDetail: vi.fn(),
listMatch3DGallery: vi.fn(),
listMatch3DWorks: vi.fn(),
updateMatch3DGeneratedItemAssets: vi.fn(),
}));
vi.mock('../../services/match3d-runtime', () => ({
@@ -700,13 +696,22 @@ vi.mock('../match3d-creation/Match3DAgentWorkspace', () => ({
vi.mock('../match3d-runtime/Match3DRuntimeShell', () => ({
Match3DRuntimeShell: ({
run,
generatedItemAssets = [],
onBack,
}: {
run: Match3DRunSnapshot | null;
generatedItemAssets?: Match3DWorkSummary['generatedItemAssets'];
onBack: () => void;
}) => (
<div className="match3d-runtime-shell-mock">
<div>{run?.runId ?? 'missing-run'}</div>
<div data-testid="match3d-runtime-generated-model-count">
{
generatedItemAssets.filter(
(asset) => asset.modelSrc?.trim() || asset.modelObjectKey?.trim(),
).length
}
</div>
<button type="button" onClick={onBack}>
</button>
@@ -847,7 +852,9 @@ function buildMockCreativeAgentSession(
}
function buildMockSquareHoleAgentSession(
overrides: Partial<Parameters<typeof buildMockSquareHoleAgentSessionImpl>[0]> = {},
overrides: Partial<
Parameters<typeof buildMockSquareHoleAgentSessionImpl>[0]
> = {},
) {
return buildMockSquareHoleAgentSessionImpl(overrides);
}
@@ -856,7 +863,13 @@ function buildMockSquareHoleAgentSessionImpl(
overrides: Partial<{
sessionId: string;
stage: string;
messages: Array<{ id: string; role: string; kind: string; text: string; createdAt: string }>;
messages: Array<{
id: string;
role: string;
kind: string;
text: string;
createdAt: string;
}>;
updatedAt: string;
}> = {},
) {
@@ -1549,7 +1562,9 @@ beforeEach(() => {
'genarrative.puzzle-onboarding.first-visit.v1',
'1',
);
vi.mocked(fetchCreationEntryConfig).mockResolvedValue(testCreationEntryConfig);
vi.mocked(fetchCreationEntryConfig).mockResolvedValue(
testCreationEntryConfig,
);
vi.mocked(getProfileDashboard).mockResolvedValue({
walletBalance: 0,
totalPlayTimeMs: 0,
@@ -2167,12 +2182,8 @@ beforeEach(() => {
vi.mocked(deleteMatch3DWork).mockResolvedValue({
items: [],
});
vi.mocked(startMatch3DRun).mockRejectedValue(
new Error('未启动抓大鹅运行态'),
);
vi.mocked(clickMatch3DItem).mockRejectedValue(
new Error('未执行抓大鹅点击'),
);
vi.mocked(startMatch3DRun).mockRejectedValue(new Error('未启动抓大鹅运行态'));
vi.mocked(clickMatch3DItem).mockRejectedValue(new Error('未执行抓大鹅点击'));
vi.mocked(restartMatch3DRun).mockRejectedValue(
new Error('未重新开始抓大鹅运行态'),
);
@@ -2522,13 +2533,52 @@ test('create tab switches match3d into the embedded entry form', async () => {
expect(
screen.getByRole('tab', { name: '抓大鹅' }).getAttribute('aria-selected'),
).toBe('true');
expect(
await screen.findByText('抓大鹅工作区missing-session'),
).toBeTruthy();
expect(await screen.findByText('抓大鹅工作区missing-session')).toBeTruthy();
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
});
test('running match3d form generation can return to draft tab and reopen progress', async () => {
const user = userEvent.setup();
const runningSession = buildMockMatch3DAgentSession({
draft: null,
stage: 'collecting_config',
});
let resolveCompile!: (value: {
session: Match3DAgentSessionSnapshot;
}) => void;
vi.mocked(match3dCreationClient.createSession).mockResolvedValue({
session: runningSession,
});
vi.mocked(match3dCreationClient.executeAction).mockReturnValue(
new Promise((resolve) => {
resolveCompile = resolve;
}),
);
render(<TestWrapper withAuth />);
await openCreateTemplateHub(user);
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
await user.click(screen.getByRole('button', { name: '生成抓大鹅草稿' }));
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
await openDraftHub(user);
expect(await screen.findByText('抓大鹅草稿')).toBeTruthy();
expect(screen.getAllByText('生成中').length).toBeGreaterThan(0);
await user.click(
screen.getByRole('button', { name: /稿/u }),
);
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
await act(async () => {
resolveCompile({ session: buildMockMatch3DAgentSession() });
});
});
test('embedded puzzle form routes through requireAuth while logged out', async () => {
const user = userEvent.setup();
const requireAuth = vi.fn();
@@ -3178,9 +3228,8 @@ test('logged out public detail gates big fish start before local runtime', async
);
await openDiscoverHub(user);
const searchInput = await screen.findByPlaceholderText(
'搜索作品号、名称、作者、描述',
);
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'BF-NPUBLIC1');
await user.click(screen.getByRole('button', { name: '搜索' }));
@@ -3225,9 +3274,8 @@ test('public code search blocks edutainment work when entry switch is disabled',
render(<TestWrapper withAuth />);
await openDiscoverHub(user);
const searchInput = await screen.findByPlaceholderText(
'搜索作品号、名称、作者、描述',
);
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'PZ-TMENT1');
await user.click(screen.getByRole('button', { name: '搜索' }));
@@ -3373,9 +3421,9 @@ test('published puzzle works appear on home and mobile game category channel', a
await user.click(screen.getByRole('button', { name: '分类' }));
const discoverPanel = getPlatformTabPanel('category');
expect(
within(discoverPanel).getAllByText('星桥机关').length,
).toBeGreaterThan(0);
expect(within(discoverPanel).getAllByText('星桥机关').length).toBeGreaterThan(
0,
);
expect(
within(discoverPanel).getAllByRole('button', { name: //u }).length,
).toBeGreaterThan(0);
@@ -3423,6 +3471,74 @@ test('home recommendation starts embedded puzzle without global auth reset on lo
});
});
test('home recommendation Match3D runtime keeps profile generated models when card summary is stale', async () => {
const match3dCard: Match3DWorkSummary = {
workId: 'match3d-work-card-1',
profileId: 'match3d-profile-card-1',
ownerUserId: 'user-2',
sourceSessionId: 'match3d-session-card-1',
gameName: '果园抓大鹅',
themeText: '果园',
summary: '消除果园模型。',
tags: ['果园', '抓大鹅'],
coverImageSrc: null,
referenceImageSrc: null,
clearCount: 3,
difficulty: 5,
publicationStatus: 'published',
playCount: 3,
updatedAt: '2026-04-25T10:30:00.000Z',
publishedAt: '2026-04-25T10:30:00.000Z',
publishReady: true,
generatedItemAssets: [],
};
const match3dDetail: Match3DWorkSummary = {
...match3dCard,
generatedItemAssets: [
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc:
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
imageObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
modelSrc: null,
modelObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-1-item/model/model.glb',
modelFileName: 'strawberry.glb',
taskUuid: 'task-strawberry',
subscriptionKey: 'sub-strawberry',
status: 'model_ready',
error: null,
},
],
};
vi.mocked(listMatch3DGallery).mockResolvedValue({
items: [match3dCard],
});
vi.mocked(getMatch3DWorkDetail).mockResolvedValue({
item: match3dDetail,
});
vi.mocked(startMatch3DRun).mockResolvedValue({
run: buildMockMatch3DRun(match3dCard.profileId),
});
render(<TestWrapper withAuth />);
await waitFor(() => {
expect(startMatch3DRun).toHaveBeenCalledWith(
'match3d-profile-card-1',
ISOLATED_RUNTIME_AUTH_OPTIONS,
);
});
await waitFor(() => {
expect(
screen.getByTestId('match3d-runtime-generated-model-count'),
).toHaveProperty('textContent', '1');
});
});
test('home recommendation surfaces start failure instead of staying in loading state', async () => {
const publishedPuzzleWork = {
workId: 'puzzle-work-public-1',
@@ -3705,9 +3821,7 @@ test('embedded puzzle form maps raw bearer token errors to user-facing auth copy
expect(createPuzzleAgentSession).toHaveBeenCalledTimes(1);
expect(createCreativeAgentSession).not.toHaveBeenCalled();
expect(
await screen.findByText(
'当前登录状态已失效,请重新登录后继续。',
),
await screen.findByText('当前登录状态已失效,请重新登录后继续。'),
).toBeTruthy();
expect(screen.queryByText('缺少 Authorization Bearer Token')).toBeNull();
});
@@ -3812,10 +3926,10 @@ test('puzzle draft result back button returns to creation hub', async () => {
await user.click(screen.getByRole('button', { name: '返回' }));
expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy();
expect(
await screen.findByRole('tablist', { name: '选择模板' }),
screen.getByText('雨夜里有一只会发光的猫站在遗迹台阶上。'),
).toBeTruthy();
expect(screen.getByText('雨夜里有一只会发光的猫站在遗迹台阶上。')).toBeTruthy();
expect(screen.queryByText('拼图结果页')).toBeNull();
});
@@ -3887,9 +4001,7 @@ test('first launch puzzle onboarding can be skipped from top right', async () =>
expect(screen.queryByText('待定待定待定')).toBeNull();
});
expect(
window.localStorage.getItem(
'genarrative.puzzle-onboarding.first-visit.v1',
),
window.localStorage.getItem('genarrative.puzzle-onboarding.first-visit.v1'),
).toBe('1');
expect(generatePuzzleOnboardingWork).not.toHaveBeenCalled();
});
@@ -3933,9 +4045,7 @@ test('first launch puzzle onboarding falls back to local run when generate route
expect(screen.queryByText('资源不存在')).toBeNull();
expect(startPuzzleRun).not.toHaveBeenCalled();
expect(
window.localStorage.getItem(
'genarrative.puzzle-onboarding.first-visit.v1',
),
window.localStorage.getItem('genarrative.puzzle-onboarding.first-visit.v1'),
).toBe('1');
});
@@ -4059,9 +4169,8 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa
render(<TestWrapper withAuth />);
await openDiscoverHub(user);
const searchInput = await screen.findByPlaceholderText(
'搜索作品号、名称、作者、描述',
);
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'PZ-EPUBLIC1');
await user.click(screen.getByRole('button', { name: '搜索' }));
@@ -4123,9 +4232,11 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa
);
});
expect(
(await screen.findAllByText('星桥机关', undefined, {
timeout: 3000,
})).length,
(
await screen.findAllByText('星桥机关', undefined, {
timeout: 3000,
})
).length,
).toBeGreaterThan(0);
});
@@ -4170,10 +4281,7 @@ test('formal puzzle similar work keeps current run level progression', async ()
entryProfileId: clearedThirdLevel.entryProfileId,
currentLevelIndex: 4,
currentGridSize: 5 as const,
playedProfileIds: [
'puzzle-profile-public-1',
'puzzle-profile-similar-2',
],
playedProfileIds: ['puzzle-profile-public-1', 'puzzle-profile-similar-2'],
currentLevel: {
...buildMockPuzzleRun('puzzle-profile-similar-2', '风塔试炼')
.currentLevel!,
@@ -4258,9 +4366,8 @@ test('formal puzzle similar work keeps current run level progression', async ()
render(<TestWrapper withAuth />);
await openDiscoverHub(user);
const searchInput = await screen.findByPlaceholderText(
'搜索作品号、名称、作者、描述',
);
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'PZ-EPUBLIC1');
await user.click(screen.getByRole('button', { name: '搜索' }));
@@ -4361,9 +4468,8 @@ test('first puzzle runtime back click can open remix result page', async () => {
render(<TestWrapper withAuth />);
await openDiscoverHub(user);
const searchInput = await screen.findByPlaceholderText(
'搜索作品号、名称、作者、描述',
);
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'PZ-EPUBLIC1');
await user.click(screen.getByRole('button', { name: '搜索' }));
await user.click(await screen.findByRole('button', { name: '启动' }));
@@ -4415,9 +4521,8 @@ test('public code search opens a published puzzle by PZ code', async () => {
render(<TestWrapper withAuth />);
await openDiscoverHub(user);
const searchInput = await screen.findByPlaceholderText(
'搜索作品号、名称、作者、描述',
);
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'PZ-EPUBLIC1');
await user.click(screen.getByRole('button', { name: '搜索' }));
@@ -4507,9 +4612,8 @@ test('public code search opens a published big fish work by BF code', async () =
render(<TestWrapper withAuth />);
await openDiscoverHub(user);
const searchInput = await screen.findByPlaceholderText(
'搜索作品号、名称、作者、描述',
);
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'BF-NPUBLIC1');
await user.click(screen.getByRole('button', { name: '搜索' }));
@@ -4517,9 +4621,7 @@ test('public code search opens a published big fish work by BF code', async () =
await user.click(screen.getByRole('button', { name: '启动' }));
await waitFor(() => {
expect(startBigFishRun).toHaveBeenCalledWith(
'big-fish-session-public-1',
);
expect(startBigFishRun).toHaveBeenCalledWith('big-fish-session-public-1');
});
expect(await screen.findByText('Lv.1/8 · 进行中')).toBeTruthy();
expect(getBigFishCreationSession).not.toHaveBeenCalledWith(
@@ -4548,6 +4650,81 @@ test('public code search opens a published Match3D work by M3 code and starts ru
updatedAt: '2026-04-25T10:30:00.000Z',
publishedAt: '2026-04-25T10:30:00.000Z',
publishReady: true,
generatedItemAssets: [],
};
vi.mocked(listMatch3DGallery).mockResolvedValue({
items: [match3dWork],
});
vi.mocked(getMatch3DWorkDetail).mockResolvedValue({
item: match3dWork,
});
vi.mocked(startMatch3DRun).mockResolvedValue({
run: buildMockMatch3DRun(match3dWork.profileId),
});
render(<TestWrapper withAuth />);
await openDiscoverHub(user);
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'M3-EPUBLIC1');
await user.click(screen.getByRole('button', { name: '搜索' }));
expect(await screen.findByText('详情')).toBeTruthy();
expect(screen.getByText('水果抓大鹅')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '启动' }));
await waitFor(() => {
expect(startMatch3DRun).toHaveBeenCalledWith('match3d-profile-public-1');
});
expect(
await screen.findByText(
'抓大鹅运行态match3d-run-match3d-profile-public-1',
),
).toBeTruthy();
expect(getRpgEntryWorldGalleryDetailByCode).not.toHaveBeenCalled();
});
test('published Match3D runtime receives persisted generated models', async () => {
const user = userEvent.setup();
const match3dWork: Match3DWorkSummary = {
workId: 'match3d-work-model-1',
profileId: 'match3d-profile-model-1',
ownerUserId: 'user-2',
sourceSessionId: 'match3d-session-model-1',
gameName: '果园抓大鹅',
themeText: '果园',
summary: '消除果园里的水果模型。',
tags: ['果园', '抓大鹅'],
coverImageSrc: null,
referenceImageSrc: null,
clearCount: 3,
difficulty: 5,
publicationStatus: 'published',
playCount: 3,
updatedAt: '2026-04-25T10:30:00.000Z',
publishedAt: '2026-04-25T10:30:00.000Z',
publishReady: true,
generatedItemAssets: [
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc:
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
imageObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
modelSrc:
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/model/model.glb',
modelObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-1-item/model/model.glb',
modelFileName: 'strawberry.glb',
taskUuid: 'task-strawberry',
subscriptionKey: 'sub-strawberry',
status: 'model_ready',
error: null,
},
],
};
vi.mocked(listMatch3DGallery).mockResolvedValue({
@@ -4560,23 +4737,16 @@ test('public code search opens a published Match3D work by M3 code and starts ru
render(<TestWrapper withAuth />);
await openDiscoverHub(user);
const searchInput = await screen.findByPlaceholderText(
'搜索作品号、名称、作者、描述',
);
await user.type(searchInput, 'M3-EPUBLIC1');
const searchInput =
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
await user.type(searchInput, 'M3-LEMODEL1');
await user.click(screen.getByRole('button', { name: '搜索' }));
expect(await screen.findByText('详情')).toBeTruthy();
expect(screen.getByText('水果抓大鹅')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '启动' }));
await user.click(await screen.findByRole('button', { name: '启动' }));
await waitFor(() => {
expect(startMatch3DRun).toHaveBeenCalledWith('match3d-profile-public-1');
});
expect(
await screen.findByText('抓大鹅运行态:match3d-run-match3d-profile-public-1'),
).toBeTruthy();
expect(getRpgEntryWorldGalleryDetailByCode).not.toHaveBeenCalled();
await screen.findByTestId('match3d-runtime-generated-model-count'),
).toHaveProperty('textContent', '1');
});
test('starting draft generation leaves the agent workspace and shows the generation progress view', async () => {
@@ -4643,6 +4813,41 @@ test('starting draft generation leaves the agent workspace and shows the generat
expect(screen.queryByText('先告诉我你想做一个怎样的 RPG 世界。')).toBeNull();
});
test('running custom world draft generation can return to creation center with shelf badge', async () => {
const user = userEvent.setup();
vi.mocked(listRpgCreationWorks).mockResolvedValue([
buildExistingRpgDraftWork({
stage: 'clarifying',
stageLabel: '补齐关键锚点',
playableNpcCount: 0,
landmarkCount: 0,
}),
]);
vi.mocked(getRpgCreationResultView).mockResolvedValue({
...buildResultViewForSession(mockSession),
targetStage: 'agent-workspace',
resultViewSource: null,
});
render(<TestWrapper withAuth />);
await openExistingRpgDraft(user, //u);
expect(
await screen.findByText('Agent工作区custom-world-agent-session-1'),
).toBeTruthy();
await user.click(screen.getByRole('button', { name: '开始生成草稿' }));
expect(await screen.findByText('世界草稿生成进度')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy();
await openDraftHub(user);
expect(await screen.findByText('潮雾列岛')).toBeTruthy();
expect(screen.getAllByText('生成中').length).toBeGreaterThan(0);
});
test('refresh restores running draft generation progress instead of agent workspace', async () => {
window.history.replaceState(
null,
@@ -5087,11 +5292,7 @@ test('agent draft result test button enters current draft without publish gate',
await openExistingRpgDraft(user, //u);
await screen.findByText('世界档案', {}, { timeout: 5000 });
await user.click(
await screen.findByRole(
'button',
{ name: '作品测试' },
{ timeout: 5000 },
),
await screen.findByRole('button', { name: '作品测试' }, { timeout: 5000 }),
);
await waitFor(() => {
@@ -5729,9 +5930,7 @@ test('manual tab switch is preserved after platform bootstrap requests finish',
render(<TestWrapper withAuth />);
await clickFirstButtonByName(user, '创作');
expect(
await screen.findByRole('tablist', { name: '选择模板' }),
).toBeTruthy();
expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy();
resolveGalleryRequest([]);
@@ -6114,11 +6313,7 @@ test('creation hub published work card keeps delete action guarded by detail flo
expect(dialog.className).toContain('platform-modal-shell');
expect(dialog.className).toContain('platform-remap-surface');
expect(dialog.className).toContain('rounded-[1.75rem]');
expect(
within(dialog).getByText('确认删除《潮雾列岛》吗?'),
).toBeTruthy();
expect(
within(dialog).getByRole('button', { name: '确认删除' }),
).toBeTruthy();
expect(within(dialog).getByText('确认删除《潮雾列岛》吗?')).toBeTruthy();
expect(within(dialog).getByRole('button', { name: '确认删除' })).toBeTruthy();
expect(deleteRpgEntryWorldProfile).not.toHaveBeenCalled();
});