This commit is contained in:
2026-05-08 20:48:29 +08:00
parent abf1f1ebea
commit 94975e4735
82 changed files with 7786 additions and 1012 deletions

View File

@@ -74,6 +74,10 @@ import {
listPuzzleGallery,
remixPuzzleGalleryWork,
} from '../../services/puzzle-gallery';
import {
generatePuzzleOnboardingWork,
savePuzzleOnboardingWork,
} from '../../services/puzzle-onboarding';
import {
advancePuzzleNextLevel,
dragPuzzlePieceOrGroup,
@@ -161,7 +165,7 @@ async function clickFirstAsyncButtonByName(
async function openCreateTemplateHub(user: ReturnType<typeof userEvent.setup>) {
await clickFirstButtonByName(user, '创作');
expect(
await screen.findByText('10分钟创作一个精品互动玩法'),
await screen.findByRole('tablist', { name: '选择模板' }),
).toBeTruthy();
expect(screen.getByRole('tab', { name: '拼图' })).toBeTruthy();
expect(screen.getByText('拼图工作区missing-session')).toBeTruthy();
@@ -390,6 +394,11 @@ vi.mock('../../services/puzzle-runtime/puzzleLocalRuntime', async () => {
};
});
vi.mock('../../services/puzzle-onboarding', () => ({
generatePuzzleOnboardingWork: vi.fn(),
savePuzzleOnboardingWork: vi.fn(),
}));
vi.mock('../../services/puzzle-agent', () => ({
createPuzzleAgentSession: vi.fn(),
executePuzzleAgentAction: vi.fn(),
@@ -2080,6 +2089,107 @@ beforeEach(() => {
vi.mocked(listPuzzleGallery).mockResolvedValue({
items: [],
});
vi.mocked(generatePuzzleOnboardingWork).mockResolvedValue({
item: {
workId: 'onboarding-work-1',
profileId: 'onboarding-profile-1',
ownerUserId: 'onboarding-guest',
sourceSessionId: null,
authorDisplayName: '百梦主',
workTitle: '梦境拼图',
workDescription: '我想飞上天',
levelName: '云上飞行',
summary: '我想飞上天',
themeTags: ['新手引导', '拼图'],
coverImageSrc: 'data:image/svg+xml;utf8,onboarding',
coverAssetId: 'onboarding-asset-1',
publicationStatus: 'draft',
updatedAt: '2026-05-05T12:00:00.000Z',
publishedAt: null,
playCount: 0,
remixCount: 0,
likeCount: 0,
publishReady: true,
levels: [],
},
level: {
levelId: 'onboarding-level-1',
levelName: '云上飞行',
pictureDescription: '我想飞上天',
pictureReference: null,
candidates: [
{
candidateId: 'onboarding-candidate-1',
imageSrc: 'data:image/svg+xml;utf8,onboarding',
assetId: 'onboarding-asset-1',
prompt: '我想飞上天',
actualPrompt: '我想飞上天',
sourceType: 'generated',
selected: true,
},
],
selectedCandidateId: 'onboarding-candidate-1',
coverImageSrc: 'data:image/svg+xml;utf8,onboarding',
coverAssetId: 'onboarding-asset-1',
generationStatus: 'ready',
},
});
vi.mocked(savePuzzleOnboardingWork).mockResolvedValue({
item: {
workId: 'onboarding-work-saved',
profileId: 'onboarding-profile-saved',
ownerUserId: mockAuthUser.id,
sourceSessionId: 'puzzle-session-onboarding',
authorDisplayName: mockAuthUser.displayName,
workTitle: '梦境拼图',
workDescription: '我想飞上天',
levelName: '云上飞行',
summary: '我想飞上天',
themeTags: ['新手引导', '拼图'],
coverImageSrc: 'data:image/svg+xml;utf8,onboarding',
coverAssetId: 'onboarding-asset-1',
publicationStatus: 'draft',
updatedAt: '2026-05-05T12:00:00.000Z',
publishedAt: null,
playCount: 0,
remixCount: 0,
likeCount: 0,
publishReady: true,
levels: [],
anchorPack: {
themePromise: {
key: 'theme_promise',
label: '主题承诺',
value: '新手引导',
status: 'confirmed',
},
visualSubject: {
key: 'visual_subject',
label: '视觉主体',
value: '云上飞行',
status: 'confirmed',
},
visualMood: {
key: 'visual_mood',
label: '视觉气质',
value: '明亮',
status: 'confirmed',
},
compositionHooks: {
key: 'composition_hooks',
label: '构图钩子',
value: '天空',
status: 'confirmed',
},
tagsAndForbidden: {
key: 'tags_and_forbidden',
label: '标签与禁区',
value: '拼图',
status: 'confirmed',
},
},
},
});
vi.mocked(remixPuzzleGalleryWork).mockRejectedValue(
new Error('未启用拼图 remix'),
);
@@ -3262,7 +3372,7 @@ test('puzzle draft result back button returns to creation hub', async () => {
await user.click(screen.getByRole('button', { name: '返回' }));
expect(
await screen.findByText('10分钟创作一个精品互动玩法'),
await screen.findByRole('tablist', { name: '选择模板' }),
).toBeTruthy();
expect(screen.getByText('雨夜里有一只会发光的猫站在遗迹台阶上。')).toBeTruthy();
expect(screen.queryByText('拼图结果页')).toBeNull();
@@ -3312,6 +3422,82 @@ test('published puzzle work card restores its source session for editing', async
expect(screen.getByDisplayValue('雨夜猫塔')).toBeTruthy();
});
test('first launch puzzle onboarding can be skipped from top right', async () => {
const user = userEvent.setup();
window.localStorage.removeItem(
'genarrative.puzzle-onboarding.first-visit.v1',
);
render(
<TestWrapper
authValue={createAuthValue({
user: null,
canAccessProtectedData: false,
openLoginModal: () => {},
requireAuth: () => {},
})}
/>,
);
expect(await screen.findByText('待定待定待定')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '跳过' }));
await waitFor(() => {
expect(screen.queryByText('待定待定待定')).toBeNull();
});
expect(
window.localStorage.getItem(
'genarrative.puzzle-onboarding.first-visit.v1',
),
).toBe('1');
expect(generatePuzzleOnboardingWork).not.toHaveBeenCalled();
});
test('first launch puzzle onboarding falls back to local run when generate route is missing', async () => {
const user = userEvent.setup();
window.localStorage.removeItem(
'genarrative.puzzle-onboarding.first-visit.v1',
);
vi.mocked(generatePuzzleOnboardingWork).mockRejectedValueOnce(
new ApiClientError({
message: '资源不存在',
status: 404,
code: 'NOT_FOUND',
}),
);
render(
<TestWrapper
authValue={createAuthValue({
user: null,
canAccessProtectedData: false,
openLoginModal: () => {},
requireAuth: () => {},
})}
/>,
);
await user.type(
await screen.findByPlaceholderText('把你的梦讲给我听吧'),
'我想飞上天',
);
await user.click(screen.getByRole('button', { name: '生成' }));
expect(
await screen.findByTestId('puzzle-board', undefined, { timeout: 3000 }),
).toBeTruthy();
expect(generatePuzzleOnboardingWork).toHaveBeenCalledWith({
promptText: '我想飞上天',
});
expect(screen.queryByText('资源不存在')).toBeNull();
expect(startPuzzleRun).not.toHaveBeenCalled();
expect(
window.localStorage.getItem(
'genarrative.puzzle-onboarding.first-visit.v1',
),
).toBe('1');
});
test('formal puzzle runtime uses frontend move merge logic and backend leaderboard next level', async () => {
const user = userEvent.setup();
const clearedFirstLevel = buildClearedPuzzleRun({
@@ -4717,7 +4903,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('10分钟创作一个精品互动玩法')).toBeTruthy();
expect(screen.getByRole('tablist', { name: '选择模板' })).toBeTruthy();
});
expect(
@@ -5041,16 +5227,16 @@ test('manual tab switch is preserved after platform bootstrap requests finish',
await clickFirstButtonByName(user, '创作');
expect(
await screen.findByText('10分钟创作一个精品互动玩法'),
await screen.findByRole('tablist', { name: '选择模板' }),
).toBeTruthy();
resolveGalleryRequest([]);
await waitFor(() => {
expect(
within(getPlatformTabPanel('create')).getByText(
'10分钟创作一个精品互动玩法',
),
within(getPlatformTabPanel('create')).getByRole('tablist', {
name: '选择模板',
}),
).toBeTruthy();
});