Merge branch 'codex/feature-1'
# Conflicts: # docs/【玩法创作】平台入口与玩法链路-2026-05-15.md # src/components/platform-entry/PlatformEntryFlowShellImpl.tsx # src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx # src/services/miniGameDraftGenerationProgress.ts
This commit is contained in:
@@ -236,8 +236,12 @@ async function openCreateTemplateHub(user: ReturnType<typeof userEvent.setup>) {
|
||||
expect(panel.getAttribute('aria-hidden')).toBe('false');
|
||||
});
|
||||
expect(
|
||||
await within(panel).findByRole('tablist', { name: '玩法模板分类' }),
|
||||
await within(panel).findByRole('tablist', { name: '创作入口页签' }),
|
||||
).toBeTruthy();
|
||||
// 中文注释:真实最近创作存在时会成为默认页签,模板入口用例需显式切回模板分类。
|
||||
if (!within(panel).queryByRole('button', { name: /拼图/u })) {
|
||||
await user.click(await within(panel).findByRole('tab', { name: '热门推荐' }));
|
||||
}
|
||||
expect(
|
||||
await within(panel).findByRole('button', { name: /拼图/u }),
|
||||
).toBeTruthy();
|
||||
@@ -367,6 +371,26 @@ const testCreationEntryConfig = {
|
||||
startsAtText: '2026-05-01',
|
||||
endsAtText: '2026-05-31',
|
||||
},
|
||||
eventBanners: [
|
||||
{
|
||||
title: '后台拼图赛',
|
||||
description: '后台配置的拼图横幅。',
|
||||
coverImageSrc: '/creation-type-references/puzzle.webp',
|
||||
prizePoolMudPoints: 1000,
|
||||
startsAtText: '2026-05-01',
|
||||
endsAtText: '2026-05-31',
|
||||
renderMode: 'structured' as const,
|
||||
},
|
||||
{
|
||||
title: '后台抓大鹅赛',
|
||||
description: '后台配置的抓大鹅横幅。',
|
||||
coverImageSrc: '/creation-type-references/match3d.webp',
|
||||
prizePoolMudPoints: 1200,
|
||||
startsAtText: '2026-06-01',
|
||||
endsAtText: '2026-06-30',
|
||||
renderMode: 'structured' as const,
|
||||
},
|
||||
],
|
||||
creationTypes: [
|
||||
{
|
||||
id: 'rpg',
|
||||
@@ -377,9 +401,9 @@ const testCreationEntryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 10,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -391,9 +415,9 @@ const testCreationEntryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 30,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -405,9 +429,9 @@ const testCreationEntryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 40,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -419,9 +443,9 @@ const testCreationEntryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 45,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -433,9 +457,9 @@ const testCreationEntryConfig = {
|
||||
visible: false,
|
||||
open: true,
|
||||
sortOrder: 50,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -447,9 +471,9 @@ const testCreationEntryConfig = {
|
||||
visible: false,
|
||||
open: false,
|
||||
sortOrder: 60,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -461,9 +485,9 @@ const testCreationEntryConfig = {
|
||||
visible: true,
|
||||
open: false,
|
||||
sortOrder: 70,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -475,9 +499,9 @@ const testCreationEntryConfig = {
|
||||
visible: false,
|
||||
open: true,
|
||||
sortOrder: 80,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -489,9 +513,9 @@ const testCreationEntryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 90,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
],
|
||||
@@ -645,6 +669,22 @@ vi.mock('../../services/jump-hop/jumpHopClient', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../services/wooden-fish/woodenFishClient', () => ({
|
||||
woodenFishClient: {
|
||||
checkpointRun: vi.fn(),
|
||||
createSession: vi.fn(),
|
||||
executeAction: vi.fn(),
|
||||
finishRun: vi.fn(),
|
||||
getGalleryDetail: vi.fn(),
|
||||
getSession: vi.fn(),
|
||||
getWorkDetail: vi.fn(),
|
||||
listGallery: vi.fn(),
|
||||
listWorks: vi.fn(),
|
||||
publishWork: vi.fn(),
|
||||
startRun: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../services/match3d-creation', () => ({
|
||||
match3dCreationClient: {
|
||||
createSession: vi.fn(),
|
||||
@@ -2707,6 +2747,18 @@ beforeEach(() => {
|
||||
vi.mocked(jumpHopClient.getWorkDetail).mockRejectedValue(
|
||||
new Error('未找到跳一跳作品'),
|
||||
);
|
||||
vi.mocked(woodenFishClient.listGallery).mockResolvedValue({
|
||||
items: [],
|
||||
hasMore: false,
|
||||
nextCursor: null,
|
||||
});
|
||||
vi.mocked(woodenFishClient.listWorks).mockResolvedValue({ items: [] });
|
||||
vi.mocked(woodenFishClient.getSession).mockRejectedValue(
|
||||
new Error('未找到敲木鱼会话'),
|
||||
);
|
||||
vi.mocked(woodenFishClient.getWorkDetail).mockRejectedValue(
|
||||
new Error('未找到敲木鱼作品'),
|
||||
);
|
||||
vi.mocked(saveBabyObjectMatchDraft).mockImplementation(async (payload) => ({
|
||||
draft: payload.draft,
|
||||
}));
|
||||
@@ -3669,14 +3721,17 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
expect(screen.queryByText('后台拼图赛')).toBeNull();
|
||||
await openCreateTemplateHub(user);
|
||||
|
||||
expect(screen.getByRole('tablist', { name: '玩法模板分类' })).toBeTruthy();
|
||||
expect(screen.getByRole('tablist', { name: '创作入口页签' })).toBeTruthy();
|
||||
expect(await screen.findByText('后台拼图赛')).toBeTruthy();
|
||||
expect(screen.getByText('后台抓大鹅赛')).toBeTruthy();
|
||||
expect(
|
||||
screen.getByRole('tablist', { name: '玩法模板分类' }).className,
|
||||
screen.getByRole('tablist', { name: '创作入口页签' }).className,
|
||||
).toContain('scroll-px-2');
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '最近创作' }).getAttribute('aria-selected'),
|
||||
screen.getByRole('tab', { name: '热门推荐' }).getAttribute('aria-selected'),
|
||||
).toBe('true');
|
||||
expect(await findCreationTypeButton('拼图')).toBeTruthy();
|
||||
expect(await findCreationTypeButton('文字冒险')).toBeTruthy();
|
||||
@@ -3686,7 +3741,7 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
|
||||
expect(queryCreationTypeButton('智能创作')).toBeNull();
|
||||
expect(
|
||||
screen
|
||||
.getByRole('tab', { name: '最近创作' })
|
||||
.getByRole('tab', { name: '热门推荐' })
|
||||
.querySelector('[class*="bg-[#d9793f]"]'),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: /智能创作/u })).toBeNull();
|
||||
@@ -3697,6 +3752,69 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
|
||||
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('create tab shows recent tab when backend returns failed drafts', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockExistingRpgDraftShelf({
|
||||
title: '入口可见的失败草稿',
|
||||
summary: '失败草稿也要进入创作入口最近创作。',
|
||||
stage: 'failed',
|
||||
stageLabel: '生成失败待处理',
|
||||
updatedAt: '2026-06-02T10:00:00.000Z',
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
const panel = getPlatformTabPanel('create');
|
||||
|
||||
const tablist = await within(panel).findByRole('tablist', {
|
||||
name: '创作入口页签',
|
||||
});
|
||||
expect(tablist).toBeTruthy();
|
||||
expect(
|
||||
within(panel)
|
||||
.getByRole('tab', { name: '最近创作' })
|
||||
.getAttribute('aria-selected'),
|
||||
).toBe('true');
|
||||
expect(await within(panel).findByText('入口可见的失败草稿')).toBeTruthy();
|
||||
expect(
|
||||
within(panel).getByText('失败草稿也要进入创作入口最近创作。'),
|
||||
).toBeTruthy();
|
||||
expect(within(panel).getByText('生成失败待处理')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('create tab refreshes recent works after opening from an empty draft shelf', async () => {
|
||||
const user = userEvent.setup();
|
||||
const failedDraft = buildExistingRpgDraftWork({
|
||||
title: '点击创作后出现的失败草稿',
|
||||
summary: '创作入口需要在进入时重新读取真实作品架。',
|
||||
stage: 'error',
|
||||
stageLabel: '发生错误',
|
||||
updatedAt: '2026-06-02T10:30:00.000Z',
|
||||
});
|
||||
vi.mocked(listRpgCreationWorks)
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValue([failedDraft]);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openDraftHub(user);
|
||||
expect(within(getPlatformTabPanel('saves')).getByText('还没有作品')).toBeTruthy();
|
||||
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
const panel = getPlatformTabPanel('create');
|
||||
|
||||
expect(await within(panel).findByText('点击创作后出现的失败草稿')).toBeTruthy();
|
||||
expect(
|
||||
within(panel).getByText('创作入口需要在进入时重新读取真实作品架。'),
|
||||
).toBeTruthy();
|
||||
expect(within(panel).getByText('发生错误')).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
vi.mocked(listRpgCreationWorks).mock.calls.length,
|
||||
).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
test('create tab opens match3d entry form from the template card', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -3845,10 +3963,10 @@ test('bark battle form checks mud points before creating image assets', async ()
|
||||
within(noticeDialog).getByText('本次需要 3 泥点,当前 2 泥点。'),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText('汪汪声浪配置表单')).toBeTruthy();
|
||||
expect(screen.queryByRole('tablist', { name: '玩法模板分类' })).toBeNull();
|
||||
expect(
|
||||
(screen.getByLabelText('汪汪作品标题') as HTMLInputElement).value,
|
||||
).toBe('自定义声浪杯');
|
||||
expect(screen.queryByRole('tablist', { name: '创作入口页签' })).toBeNull();
|
||||
expect((screen.getByLabelText('汪汪作品标题') as HTMLInputElement).value).toBe(
|
||||
'自定义声浪杯',
|
||||
);
|
||||
expect(createBarkBattleDraft).not.toHaveBeenCalled();
|
||||
expect(generateAllBarkBattleImageAssets).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -3979,7 +4097,7 @@ test('running match3d form generation can return to draft tab and reopen progres
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
await openDraftHub(user);
|
||||
|
||||
expect(await screen.findByText('抓大鹅草稿')).toBeTruthy();
|
||||
expect((await screen.findAllByText('抓大鹅草稿')).length).toBeGreaterThan(0);
|
||||
await expectDraftHubGeneratingBadgeCountAtLeast(1);
|
||||
|
||||
await user.click(
|
||||
@@ -4883,7 +5001,7 @@ test('puzzle form checks mud points before creating a draft', async () => {
|
||||
within(noticeDialog).getByText('本次需要 2 泥点,当前 1 泥点。'),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText('拼图工作区:missing-session')).toBeTruthy();
|
||||
expect(screen.queryByRole('tablist', { name: '玩法模板分类' })).toBeNull();
|
||||
expect(screen.queryByRole('tablist', { name: '创作入口页签' })).toBeNull();
|
||||
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
|
||||
expect(executePuzzleAgentAction).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -4910,7 +5028,7 @@ test('match3d form checks mud points before creating a draft', async () => {
|
||||
within(noticeDialog).getByText('本次需要 10 泥点,当前 9 泥点。'),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText('抓大鹅工作区:missing-session')).toBeTruthy();
|
||||
expect(screen.queryByRole('tablist', { name: '玩法模板分类' })).toBeNull();
|
||||
expect(screen.queryByRole('tablist', { name: '创作入口页签' })).toBeNull();
|
||||
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
|
||||
expect(match3dCreationClient.executeAction).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -7727,8 +7845,8 @@ 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.findAllByText('当前登录状态已失效,请重新登录后继续。'),
|
||||
).not.toHaveLength(0);
|
||||
(await screen.findAllByText('当前登录状态已失效,请重新登录后继续。')).length,
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText('缺少 Authorization Bearer Token')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -9022,11 +9140,11 @@ test('running custom world draft generation can return to creation center with s
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
|
||||
expect(
|
||||
await screen.findByRole('tablist', { name: '玩法模板分类' }),
|
||||
await screen.findByRole('tablist', { name: '创作入口页签' }),
|
||||
).toBeTruthy();
|
||||
await openDraftHub(user);
|
||||
|
||||
expect(await screen.findByText('潮雾列岛')).toBeTruthy();
|
||||
expect((await screen.findAllByText('潮雾列岛')).length).toBeGreaterThan(0);
|
||||
await expectDraftHubGeneratingBadgeCountAtLeast(1);
|
||||
});
|
||||
|
||||
@@ -10420,7 +10538,7 @@ test('manual tab switch is preserved after platform bootstrap requests finish',
|
||||
|
||||
await clickFirstButtonByName(user, '创作');
|
||||
expect(
|
||||
await screen.findByRole('tablist', { name: '玩法模板分类' }),
|
||||
await screen.findByRole('tablist', { name: '创作入口页签' }),
|
||||
).toBeTruthy();
|
||||
|
||||
resolveGalleryRequest([]);
|
||||
@@ -10428,7 +10546,7 @@ test('manual tab switch is preserved after platform bootstrap requests finish',
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
within(getPlatformTabPanel('create')).getByRole('tablist', {
|
||||
name: '玩法模板分类',
|
||||
name: '创作入口页签',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
});
|
||||
@@ -11195,7 +11313,11 @@ test('creation hub published work card reveals delete action after card action r
|
||||
|
||||
const deleteButtons = screen.getAllByRole('button', { name: '删除' });
|
||||
expect(deleteButtons.length).toBeGreaterThan(0);
|
||||
await user.click(deleteButtons[0]!);
|
||||
const deleteButton = deleteButtons[0];
|
||||
if (!deleteButton) {
|
||||
throw new Error('delete button should exist after swipe');
|
||||
}
|
||||
await user.click(deleteButton);
|
||||
|
||||
const dialog = await screen.findByRole('dialog', { name: '删除作品' });
|
||||
expect(dialog.parentElement?.className).toContain('platform-theme--light');
|
||||
|
||||
Reference in New Issue
Block a user