1
This commit is contained in:
@@ -6,14 +6,15 @@ import { useState } from 'react';
|
||||
import { beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
import {
|
||||
createCustomWorldAgentSession,
|
||||
executeCustomWorldAgentAction,
|
||||
getCustomWorldAgentOperation,
|
||||
getCustomWorldAgentSession,
|
||||
listCustomWorldWorks,
|
||||
streamCustomWorldAgentMessage,
|
||||
} from '../../services/aiService';
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
import type { AuthUser } from '../../services/authService';
|
||||
import {
|
||||
clearProfileBrowseHistory,
|
||||
@@ -54,12 +55,30 @@ async function clickFirstAsyncButtonByName(
|
||||
await user.click(buttons[0]!);
|
||||
}
|
||||
|
||||
async function openCreationHub(user: ReturnType<typeof userEvent.setup>) {
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
expect(await screen.findByText('创作中心')).toBeTruthy();
|
||||
}
|
||||
|
||||
async function openNewRpgCreation(
|
||||
user: ReturnType<typeof userEvent.setup>,
|
||||
) {
|
||||
await openCreationHub(user);
|
||||
const createButtons = await screen.findAllByRole('button', {
|
||||
name: /新建作品/u,
|
||||
});
|
||||
await user.click(createButtons.at(-1)!);
|
||||
expect(screen.getByText('选择创作类型')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演 RPG/u }));
|
||||
}
|
||||
|
||||
vi.mock('../../services/aiService', () => ({
|
||||
createCustomWorldAgentSession: vi.fn(),
|
||||
executeCustomWorldAgentAction: vi.fn(),
|
||||
generateCustomWorldProfile: vi.fn(),
|
||||
getCustomWorldAgentOperation: vi.fn(),
|
||||
getCustomWorldAgentSession: vi.fn(),
|
||||
listCustomWorldWorks: vi.fn(),
|
||||
streamCustomWorldAgentMessage: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -340,6 +359,7 @@ beforeEach(() => {
|
||||
vi.mocked(createCustomWorldAgentSession).mockResolvedValue({
|
||||
session: mockSession,
|
||||
});
|
||||
vi.mocked(listCustomWorldWorks).mockResolvedValue([]);
|
||||
vi.mocked(executeCustomWorldAgentAction).mockResolvedValue({
|
||||
operation: {
|
||||
operationId: 'operation-draft-foundation-1',
|
||||
@@ -369,8 +389,11 @@ test('create tab opens game type modal, keeps AIRP and visual novel locked, and
|
||||
|
||||
render(<TestWrapper />);
|
||||
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
await clickFirstButtonByName(user, /开启新的创作/u);
|
||||
await openCreationHub(user);
|
||||
const createButtons = await screen.findAllByRole('button', {
|
||||
name: /新建作品/u,
|
||||
});
|
||||
await user.click(createButtons.at(-1)!);
|
||||
|
||||
expect(screen.getByText('选择创作类型')).toBeTruthy();
|
||||
|
||||
@@ -393,6 +416,52 @@ test('create tab opens game type modal, keeps AIRP and visual novel locked, and
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('create tab uses unified creation hub and can resume an agent draft', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
vi.mocked(listCustomWorldWorks).mockResolvedValue([
|
||||
{
|
||||
workId: 'draft:custom-world-agent-session-1',
|
||||
sourceType: 'agent_session',
|
||||
status: 'draft',
|
||||
title: '潮雾列岛',
|
||||
subtitle: '精修对象',
|
||||
summary: '玩家是失职返乡的守灯人。',
|
||||
coverImageSrc: null,
|
||||
coverRenderMode: 'image',
|
||||
coverCharacterImageSrcs: [],
|
||||
updatedAt: '2026-04-20T10:00:00.000Z',
|
||||
publishedAt: null,
|
||||
stage: 'object_refining',
|
||||
stageLabel: '精修对象',
|
||||
playableNpcCount: 3,
|
||||
landmarkCount: 4,
|
||||
roleVisualReadyCount: 1,
|
||||
roleAnimationReadyCount: 0,
|
||||
roleAssetSummaryLabel: '沈砺 · 主图已生成',
|
||||
sessionId: 'custom-world-agent-session-1',
|
||||
profileId: null,
|
||||
canResume: true,
|
||||
canEnterWorld: false,
|
||||
},
|
||||
]);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreationHub(user);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /继续精修/u }),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /继续精修/u })).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /继续精修/u }));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Agent工作区:custom-world-agent-session-1'),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('clicking a public work while logged out routes through requireAuth', async () => {
|
||||
const user = userEvent.setup();
|
||||
const requireAuth = vi.fn();
|
||||
@@ -448,10 +517,7 @@ test('selecting RPG creation while logged out routes through requireAuth', async
|
||||
/>,
|
||||
);
|
||||
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
await clickFirstButtonByName(user, /开启新的创作/u);
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演 RPG/u }));
|
||||
|
||||
await openNewRpgCreation(user);
|
||||
expect(requireAuth).toHaveBeenCalledTimes(1);
|
||||
expect(createCustomWorldAgentSession).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -461,9 +527,7 @@ test('starting draft generation leaves the agent workspace and shows the generat
|
||||
|
||||
render(<TestWrapper />);
|
||||
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
await clickFirstButtonByName(user, /开启新的创作/u);
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演 RPG/u }));
|
||||
await openNewRpgCreation(user);
|
||||
|
||||
expect(
|
||||
await screen.findByText('Agent工作区:custom-world-agent-session-1'),
|
||||
@@ -490,7 +554,7 @@ test('starting draft generation leaves the agent workspace and shows the generat
|
||||
expect(screen.queryByText('先告诉我你想做一个怎样的 RPG 世界。')).toBeNull();
|
||||
});
|
||||
|
||||
test('existing draft sessions enter the legacy result layout directly', async () => {
|
||||
test('existing draft sessions enter the agent preview layout without opening legacy editor', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
vi.mocked(getCustomWorldAgentOperation).mockResolvedValue({
|
||||
@@ -595,9 +659,7 @@ test('existing draft sessions enter the legacy result layout directly', async ()
|
||||
|
||||
render(<TestWrapper />);
|
||||
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
await clickFirstButtonByName(user, /开启新的创作/u);
|
||||
await user.click(screen.getByRole('button', { name: /角色扮演 RPG/u }));
|
||||
await openNewRpgCreation(user);
|
||||
|
||||
await waitFor(
|
||||
async () => {
|
||||
@@ -611,13 +673,295 @@ test('existing draft sessions enter the legacy result layout directly', async ()
|
||||
expect(screen.queryByText(/Agent工作区/u)).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /^锚点/u })).toBeNull();
|
||||
expect(screen.getByText(/基本设定/u)).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: /新增场景角色/u })).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /场景角色/u }));
|
||||
await user.click(screen.getByRole('button', { name: /顾潮音/u }));
|
||||
expect(screen.getByRole('button', { name: /顾潮音/u })).toBeTruthy();
|
||||
expect(screen.queryByText(/编辑场景角色:顾潮音/u)).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /AI生成/u })).toBeNull();
|
||||
expect(screen.queryByText('技能')).toBeNull();
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/编辑场景角色:顾潮音/u)).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /AI生成/u })).toBeTruthy();
|
||||
expect(screen.getByText('技能')).toBeTruthy();
|
||||
test('agent draft result back button returns to workspace without redundant sync when session is already latest', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
vi.mocked(executeCustomWorldAgentAction).mockResolvedValue({
|
||||
operation: {
|
||||
operationId: 'operation-sync-result-profile-1',
|
||||
type: 'sync_result_profile',
|
||||
status: 'queued',
|
||||
phaseLabel: '同步结果页快照',
|
||||
phaseDetail: '正在把结果页里的最新世界结构写回当前草稿。',
|
||||
progress: 24,
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
vi.mocked(getCustomWorldAgentOperation).mockResolvedValue({
|
||||
operationId: 'operation-sync-result-profile-1',
|
||||
type: 'sync_result_profile',
|
||||
status: 'completed',
|
||||
phaseLabel: '结果页快照已同步',
|
||||
phaseDetail: '当前结果页编辑内容已经并回 Agent 草稿主链。',
|
||||
progress: 100,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const resultSession = {
|
||||
...mockSession,
|
||||
stage: 'object_refining' as const,
|
||||
creatorIntent: {
|
||||
sourceMode: 'card',
|
||||
worldHook: '被海雾吞没的旧航路群岛',
|
||||
playerPremise: '玩家回到群岛调查沉船真相。',
|
||||
themeKeywords: ['海雾', '旧航路'],
|
||||
toneDirectives: ['压抑', '悬疑'],
|
||||
openingSituation: '首夜就有陌生船只闯入禁航区。',
|
||||
coreConflicts: ['航运公会与守灯会争夺航路控制权'],
|
||||
keyFactions: [],
|
||||
keyCharacters: [],
|
||||
keyLandmarks: [],
|
||||
iconicElements: ['会移动的海雾'],
|
||||
forbiddenDirectives: [],
|
||||
rawSettingText: '',
|
||||
},
|
||||
draftProfile: {
|
||||
name: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '第一版世界底稿已经整理完成。',
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清沉船与禁航区异动的真相。',
|
||||
majorFactions: ['守灯会', '航运公会'],
|
||||
coreConflicts: ['守灯会与航运公会争夺旧航路控制权'],
|
||||
playableNpcs: [
|
||||
{
|
||||
id: 'playable-1',
|
||||
name: '沈砺',
|
||||
title: '旧航路引路人',
|
||||
role: '关键同行者',
|
||||
publicIdentity: '最熟悉旧航路的人。',
|
||||
publicMask: '看上去像可靠旧友。',
|
||||
currentPressure: '他必须在两股势力间站队。',
|
||||
hiddenHook: '暗中替沉船商盟引路。',
|
||||
relationToPlayer: '旧友兼潜在背叛者',
|
||||
threadIds: ['thread-1'],
|
||||
summary: '他像旧友,但也像一把始终没收回鞘的刀。',
|
||||
},
|
||||
],
|
||||
storyNpcs: [
|
||||
{
|
||||
id: 'story-1',
|
||||
name: '顾潮音',
|
||||
title: '守灯会值夜人',
|
||||
role: '场景关键角色',
|
||||
publicIdentity: '负责夜间巡灯与封锁。',
|
||||
publicMask: '对外一直冷静克制。',
|
||||
currentPressure: '她知道更多禁航区真相。',
|
||||
hiddenHook: '曾亲眼见过失控海雾吞船。',
|
||||
relationToPlayer: '最早愿意交换线索的人',
|
||||
threadIds: ['thread-1'],
|
||||
summary: '她总像比所有人更早知道海雾会往哪一侧压下来。',
|
||||
},
|
||||
],
|
||||
landmarks: [
|
||||
{
|
||||
id: 'landmark-1',
|
||||
name: '回潮旧灯塔',
|
||||
purpose: '观察雾潮与往来船只',
|
||||
mood: '潮湿、压抑、风声不止',
|
||||
importance: '开局核心场景',
|
||||
characterIds: ['story-1'],
|
||||
threadIds: ['thread-1'],
|
||||
summary: '旧灯塔是整片群岛最先看见异动的地方。',
|
||||
},
|
||||
],
|
||||
factions: [],
|
||||
threads: [],
|
||||
chapters: [],
|
||||
worldHook: '被海雾吞没的旧航路群岛',
|
||||
playerPremise: '玩家回到群岛调查沉船真相。',
|
||||
openingSituation: '首夜就有陌生船只闯入禁航区。',
|
||||
iconicElements: ['会移动的海雾'],
|
||||
sourceAnchorSummary: '海雾、旧灯塔、失控航路。',
|
||||
legacyResultProfile: {
|
||||
id: 'agent-draft-custom-world-agent-session-1',
|
||||
settingText: '被海雾吞没的旧航路群岛',
|
||||
name: '潮雾列岛·同步后',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '同步后的结果页快照已经回写到 session。',
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清沉船与禁航区异动的真相。',
|
||||
templateWorldType: 'WUXIA',
|
||||
majorFactions: ['守灯会', '航运公会'],
|
||||
coreConflicts: ['守灯会与航运公会争夺旧航路控制权'],
|
||||
playableNpcs: [],
|
||||
storyNpcs: [],
|
||||
items: [],
|
||||
landmarks: [],
|
||||
generationMode: 'full',
|
||||
generationStatus: 'complete',
|
||||
},
|
||||
},
|
||||
draftCards: [
|
||||
{
|
||||
id: 'world-foundation',
|
||||
kind: 'world',
|
||||
title: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '第一版世界底稿已经整理完成。',
|
||||
status: 'warning',
|
||||
linkedIds: ['playable-1', 'story-1', 'landmark-1'],
|
||||
warningCount: 0,
|
||||
},
|
||||
],
|
||||
} satisfies CustomWorldAgentSessionSnapshot;
|
||||
vi.mocked(getCustomWorldAgentSession).mockResolvedValue(resultSession);
|
||||
|
||||
render(<TestWrapper />);
|
||||
|
||||
await openNewRpgCreation(user);
|
||||
|
||||
await waitFor(
|
||||
async () => {
|
||||
expect(await screen.findByText('世界档案')).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /返回创作/u })).toBeTruthy();
|
||||
},
|
||||
{ timeout: 2500 },
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /返回创作/u }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('Agent工作区:custom-world-agent-session-1'),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(
|
||||
vi.mocked(executeCustomWorldAgentAction).mock.calls.some(
|
||||
([sessionId, payload]) =>
|
||||
sessionId === 'custom-world-agent-session-1' &&
|
||||
payload?.action === 'sync_result_profile',
|
||||
),
|
||||
).toBe(false);
|
||||
expect(screen.queryByText('世界档案')).toBeNull();
|
||||
});
|
||||
|
||||
test('agent draft result auto-save persists the latest profile rebuilt from synced session', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
vi.mocked(executeCustomWorldAgentAction).mockResolvedValue({
|
||||
operation: {
|
||||
operationId: 'operation-sync-result-profile-2',
|
||||
type: 'sync_result_profile',
|
||||
status: 'queued',
|
||||
phaseLabel: '同步结果页快照',
|
||||
phaseDetail: '正在把结果页里的最新世界结构写回当前草稿。',
|
||||
progress: 24,
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
vi.mocked(getCustomWorldAgentOperation).mockResolvedValue({
|
||||
operationId: 'operation-sync-result-profile-2',
|
||||
type: 'sync_result_profile',
|
||||
status: 'completed',
|
||||
phaseLabel: '结果页快照已同步',
|
||||
phaseDetail: '当前结果页编辑内容已经并回 Agent 草稿主链。',
|
||||
progress: 100,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const syncedSession = {
|
||||
...mockSession,
|
||||
stage: 'object_refining' as const,
|
||||
creatorIntent: {
|
||||
sourceMode: 'card',
|
||||
worldHook: '被海雾吞没的旧航路群岛',
|
||||
playerPremise: '玩家回到群岛调查沉船真相。',
|
||||
themeKeywords: ['海雾', '旧航路'],
|
||||
toneDirectives: ['压抑', '悬疑'],
|
||||
openingSituation: '首夜就有陌生船只闯入禁航区。',
|
||||
coreConflicts: ['航运公会与守灯会争夺航路控制权'],
|
||||
keyFactions: [],
|
||||
keyCharacters: [],
|
||||
keyLandmarks: [],
|
||||
iconicElements: ['会移动的海雾'],
|
||||
forbiddenDirectives: [],
|
||||
rawSettingText: '',
|
||||
},
|
||||
draftProfile: {
|
||||
name: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '第一版世界底稿已经整理完成。',
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清沉船与禁航区异动的真相。',
|
||||
majorFactions: ['守灯会', '航运公会'],
|
||||
coreConflicts: ['守灯会与航运公会争夺旧航路控制权'],
|
||||
playableNpcs: [],
|
||||
storyNpcs: [],
|
||||
landmarks: [],
|
||||
factions: [],
|
||||
threads: [],
|
||||
chapters: [],
|
||||
worldHook: '被海雾吞没的旧航路群岛',
|
||||
playerPremise: '玩家回到群岛调查沉船真相。',
|
||||
openingSituation: '首夜就有陌生船只闯入禁航区。',
|
||||
iconicElements: ['会移动的海雾'],
|
||||
sourceAnchorSummary: '海雾、旧灯塔、失控航路。',
|
||||
legacyResultProfile: {
|
||||
id: 'agent-draft-custom-world-agent-session-1',
|
||||
settingText: '被海雾吞没的旧航路群岛',
|
||||
name: '潮雾列岛·session最新版',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '作品库应该保存这份同步后的最新快照。',
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清沉船与禁航区异动的真相。',
|
||||
templateWorldType: 'WUXIA',
|
||||
majorFactions: ['守灯会', '航运公会'],
|
||||
coreConflicts: ['守灯会与航运公会争夺旧航路控制权'],
|
||||
playableNpcs: [],
|
||||
storyNpcs: [],
|
||||
items: [],
|
||||
landmarks: [],
|
||||
generationMode: 'full',
|
||||
generationStatus: 'complete',
|
||||
},
|
||||
},
|
||||
draftCards: [
|
||||
{
|
||||
id: 'world-foundation',
|
||||
kind: 'world',
|
||||
title: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '第一版世界底稿已经整理完成。',
|
||||
status: 'warning',
|
||||
linkedIds: [],
|
||||
warningCount: 0,
|
||||
},
|
||||
],
|
||||
} satisfies CustomWorldAgentSessionSnapshot;
|
||||
vi.mocked(getCustomWorldAgentSession).mockResolvedValue(syncedSession);
|
||||
|
||||
render(<TestWrapper />);
|
||||
|
||||
await openNewRpgCreation(user);
|
||||
|
||||
await waitFor(
|
||||
async () => {
|
||||
expect(await screen.findByText('世界档案')).toBeTruthy();
|
||||
expect(screen.getByText('已自动保存')).toBeTruthy();
|
||||
},
|
||||
{ timeout: 2500 },
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(upsertCustomWorldProfile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const latestSavedProfile = vi.mocked(upsertCustomWorldProfile).mock.calls.at(-1)?.[0];
|
||||
expect(latestSavedProfile?.name).toBe('潮雾列岛·session最新版');
|
||||
expect(latestSavedProfile?.summary).toBe(
|
||||
'作品库应该保存这份同步后的最新快照。',
|
||||
);
|
||||
});
|
||||
|
||||
test('authenticated users with save archives default into the saves tab', async () => {
|
||||
@@ -697,42 +1041,71 @@ test('owned world detail can delete a work and return to the create tab list', a
|
||||
const user = userEvent.setup();
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
|
||||
vi.mocked(listCustomWorldLibrary).mockResolvedValue([
|
||||
{
|
||||
ownerUserId: 'user-1',
|
||||
profileId: 'world-delete-1',
|
||||
profile: {
|
||||
id: 'world-delete-1',
|
||||
name: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '用于测试删除流程的作品。',
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清旧案。',
|
||||
majorFactions: ['守灯会'],
|
||||
coreConflicts: ['雾潮正在逼近港口'],
|
||||
playableNpcs: [],
|
||||
storyNpcs: [],
|
||||
landmarks: [],
|
||||
} as never,
|
||||
visibility: 'draft',
|
||||
publishedAt: null,
|
||||
updatedAt: '2026-04-16T12:00:00.000Z',
|
||||
authorDisplayName: '测试玩家',
|
||||
worldName: '潮雾列岛',
|
||||
const publishedWork = {
|
||||
workId: 'published:world-delete-1',
|
||||
sourceType: 'published_profile' as const,
|
||||
status: 'published' as const,
|
||||
title: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '用于测试删除流程的作品。',
|
||||
coverImageSrc: null,
|
||||
coverRenderMode: 'image' as const,
|
||||
coverCharacterImageSrcs: [],
|
||||
updatedAt: '2026-04-16T12:00:00.000Z',
|
||||
publishedAt: '2026-04-16T12:00:00.000Z',
|
||||
stage: null,
|
||||
stageLabel: '已发布',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
roleVisualReadyCount: 0,
|
||||
roleAnimationReadyCount: 0,
|
||||
roleAssetSummaryLabel: null,
|
||||
sessionId: null,
|
||||
profileId: 'world-delete-1',
|
||||
canResume: false,
|
||||
canEnterWorld: true,
|
||||
};
|
||||
const publishedLibraryEntry = {
|
||||
ownerUserId: 'user-1',
|
||||
profileId: 'world-delete-1',
|
||||
profile: {
|
||||
id: 'world-delete-1',
|
||||
name: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summaryText: '用于测试删除流程的作品。',
|
||||
coverImageSrc: null,
|
||||
themeMode: 'tide',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
},
|
||||
]);
|
||||
summary: '用于测试删除流程的作品。',
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清旧案。',
|
||||
majorFactions: ['守灯会'],
|
||||
coreConflicts: ['雾潮正在逼近港口'],
|
||||
playableNpcs: [],
|
||||
storyNpcs: [],
|
||||
landmarks: [],
|
||||
} as never,
|
||||
visibility: 'published' as const,
|
||||
publishedAt: '2026-04-16T12:00:00.000Z',
|
||||
updatedAt: '2026-04-16T12:00:00.000Z',
|
||||
authorDisplayName: '测试玩家',
|
||||
worldName: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summaryText: '用于测试删除流程的作品。',
|
||||
coverImageSrc: null,
|
||||
themeMode: 'tide' as const,
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
};
|
||||
|
||||
vi.mocked(listCustomWorldWorks)
|
||||
.mockResolvedValueOnce([publishedWork])
|
||||
.mockResolvedValue([]);
|
||||
vi.mocked(listCustomWorldLibrary)
|
||||
.mockResolvedValueOnce([publishedLibraryEntry])
|
||||
.mockResolvedValue([]);
|
||||
vi.mocked(deleteCustomWorldProfile).mockResolvedValue([]);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
await clickFirstAsyncButtonByName(user, /潮雾列岛/u);
|
||||
await openCreationHub(user);
|
||||
await user.click(screen.getByRole('button', { name: /进入世界/u }));
|
||||
await user.click(await screen.findByRole('button', { name: '删除作品' }));
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -742,8 +1115,77 @@ test('owned world detail can delete a work and return to the create tab list', a
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('button', { name: '删除作品' })).toBeNull();
|
||||
});
|
||||
expect(
|
||||
screen.getAllByText('你还没有保存任何自定义世界,先创建一个草稿开始吧。')
|
||||
.length,
|
||||
).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('还没有作品')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('creation hub published work enters existing detail view', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
vi.mocked(listCustomWorldWorks).mockResolvedValue([
|
||||
{
|
||||
workId: 'published:world-public-1',
|
||||
sourceType: 'published_profile',
|
||||
status: 'published',
|
||||
title: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '已经发布的群岛世界作品。',
|
||||
coverImageSrc: null,
|
||||
coverRenderMode: 'image',
|
||||
coverCharacterImageSrcs: [],
|
||||
updatedAt: '2026-04-20T10:00:00.000Z',
|
||||
publishedAt: '2026-04-20T10:00:00.000Z',
|
||||
stage: null,
|
||||
stageLabel: '已发布',
|
||||
playableNpcCount: 3,
|
||||
landmarkCount: 4,
|
||||
roleVisualReadyCount: 1,
|
||||
roleAnimationReadyCount: 0,
|
||||
roleAssetSummaryLabel: null,
|
||||
sessionId: null,
|
||||
profileId: 'world-public-1',
|
||||
canResume: false,
|
||||
canEnterWorld: true,
|
||||
},
|
||||
]);
|
||||
vi.mocked(listCustomWorldLibrary).mockResolvedValue([
|
||||
{
|
||||
ownerUserId: 'user-1',
|
||||
profileId: 'world-public-1',
|
||||
profile: {
|
||||
id: 'world-public-1',
|
||||
name: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '已经发布的群岛世界作品。',
|
||||
tone: '压抑、潮湿、悬疑',
|
||||
playerGoal: '查清群岛旧案。',
|
||||
majorFactions: ['守灯会'],
|
||||
coreConflicts: ['假航灯正在扰乱航线'],
|
||||
playableNpcs: [],
|
||||
storyNpcs: [],
|
||||
landmarks: [],
|
||||
} as never,
|
||||
visibility: 'published',
|
||||
publishedAt: '2026-04-20T10:00:00.000Z',
|
||||
updatedAt: '2026-04-20T10:00:00.000Z',
|
||||
authorDisplayName: '测试玩家',
|
||||
worldName: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summaryText: '已经发布的群岛世界作品。',
|
||||
coverImageSrc: null,
|
||||
themeMode: 'tide',
|
||||
playableNpcCount: 3,
|
||||
landmarkCount: 4,
|
||||
},
|
||||
]);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreationHub(user);
|
||||
await user.click(screen.getByRole('button', { name: /进入世界/u }));
|
||||
|
||||
expect(await screen.findByText('世界信息')).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '开始游戏' })).toBeTruthy();
|
||||
expect(screen.getByText('已发布')).toBeTruthy();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user