Merge remote-tracking branch 'origin/master' into codex/fix-draft-result-back-target
# Conflicts: # src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
This commit is contained in:
@@ -83,7 +83,10 @@ import {
|
||||
saveBabyObjectMatchDraft,
|
||||
} from '../../services/edutainment-baby-object';
|
||||
import { match3dCreationClient } from '../../services/match3d-creation';
|
||||
import { createServerMatch3DRuntimeAdapter } from '../../services/match3d-runtime';
|
||||
import {
|
||||
createLocalMatch3DRuntimeAdapter,
|
||||
createServerMatch3DRuntimeAdapter,
|
||||
} from '../../services/match3d-runtime';
|
||||
import {
|
||||
deleteMatch3DWork,
|
||||
getMatch3DWorkDetail,
|
||||
@@ -257,6 +260,13 @@ function queryCreationTypeButton(name: string | RegExp) {
|
||||
});
|
||||
}
|
||||
|
||||
async function openPuzzleFormFromCreateHub(
|
||||
user: ReturnType<typeof userEvent.setup>,
|
||||
) {
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await screen.findByText(/拼图工作区:/u);
|
||||
}
|
||||
|
||||
async function openDraftHub(user: ReturnType<typeof userEvent.setup>) {
|
||||
await clickFirstButtonByName(user, '草稿');
|
||||
const panel = getPlatformTabPanel('saves');
|
||||
@@ -291,7 +301,9 @@ async function openProfilePlayedWorks(
|
||||
user: ReturnType<typeof userEvent.setup>,
|
||||
) {
|
||||
await clickFirstButtonByName(user, '我的');
|
||||
await user.click(await screen.findByRole('button', { name: /玩过/u }));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: /已玩游戏数量/u }),
|
||||
);
|
||||
expect(await screen.findByText('可继续')).toBeTruthy();
|
||||
}
|
||||
|
||||
@@ -662,6 +674,7 @@ vi.mock('../../services/match3dGeneratedModelCache', () => ({
|
||||
}));
|
||||
|
||||
const match3dRuntimeServiceMocks = vi.hoisted(() => ({
|
||||
createLocalMatch3DRuntimeAdapter: vi.fn(),
|
||||
createServerMatch3DRuntimeAdapter: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -674,6 +687,15 @@ const match3dServerRuntimeAdapterMock = vi.hoisted(() => ({
|
||||
stopRun: vi.fn(),
|
||||
}));
|
||||
|
||||
const match3dLocalRuntimeAdapterMock = vi.hoisted(() => ({
|
||||
clickItem: vi.fn(),
|
||||
finishTimeUp: vi.fn(),
|
||||
getRun: vi.fn(),
|
||||
restartRun: vi.fn(),
|
||||
startRun: vi.fn(),
|
||||
stopRun: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/match3d-runtime', async () => {
|
||||
const actual = await vi.importActual<
|
||||
typeof import('../../services/match3d-runtime')
|
||||
@@ -2383,6 +2405,9 @@ beforeEach(() => {
|
||||
vi.mocked(createServerMatch3DRuntimeAdapter).mockReturnValue(
|
||||
match3dServerRuntimeAdapterMock,
|
||||
);
|
||||
vi.mocked(createLocalMatch3DRuntimeAdapter).mockReturnValue(
|
||||
match3dLocalRuntimeAdapterMock,
|
||||
);
|
||||
match3dServerRuntimeAdapterMock.startRun.mockRejectedValue(
|
||||
new Error('未启动抓大鹅运行态'),
|
||||
);
|
||||
@@ -2398,6 +2423,21 @@ beforeEach(() => {
|
||||
match3dServerRuntimeAdapterMock.stopRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-profile-stopped'),
|
||||
});
|
||||
match3dLocalRuntimeAdapterMock.startRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-demo-20260525'),
|
||||
});
|
||||
match3dLocalRuntimeAdapterMock.clickItem.mockRejectedValue(
|
||||
new Error('未执行本地抓大鹅点击'),
|
||||
);
|
||||
match3dLocalRuntimeAdapterMock.restartRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-demo-20260525'),
|
||||
});
|
||||
match3dLocalRuntimeAdapterMock.finishTimeUp.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-demo-20260525'),
|
||||
});
|
||||
match3dLocalRuntimeAdapterMock.stopRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-demo-20260525'),
|
||||
});
|
||||
window.history.replaceState(null, '', '/');
|
||||
window.sessionStorage.clear();
|
||||
window.localStorage.clear();
|
||||
@@ -3476,7 +3516,7 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
|
||||
expect(screen.getByRole('tablist', { name: '玩法模板分类' })).toBeTruthy();
|
||||
expect(
|
||||
screen.getByRole('tablist', { name: '玩法模板分类' }).className,
|
||||
).toContain('scroll-px-3');
|
||||
).toContain('scroll-px-2');
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '最近创作' }).getAttribute('aria-selected'),
|
||||
).toBe('true');
|
||||
@@ -3518,7 +3558,7 @@ test('create tab opens puzzle entry form from the template card', async () => {
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await openPuzzleFormFromCreateHub(user);
|
||||
|
||||
expect(await screen.findByText('拼图工作区:missing-session')).toBeTruthy();
|
||||
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
|
||||
@@ -3670,7 +3710,11 @@ test('bark battle draft is visible in draft shelf while image assets are generat
|
||||
await user.click(await findCreationTypeButton('汪汪声浪'));
|
||||
await user.click(await screen.findByRole('button', { name: '生成草稿' }));
|
||||
|
||||
expect(await screen.findByText('自动生成素材')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '汪汪声浪素材生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '返回编辑' }));
|
||||
await openDraftHub(user);
|
||||
@@ -3735,7 +3779,7 @@ test('published bark battle stays visible when refresh temporarily returns only
|
||||
|
||||
await openDraftHub(user);
|
||||
const panel = getPlatformTabPanel('saves');
|
||||
await user.click(within(panel).getByRole('button', { name: /已发布/u }));
|
||||
await user.click(within(panel).getByRole('tab', { name: /已发布/u }));
|
||||
|
||||
expect(await within(panel).findByText('汪汪测试杯')).toBeTruthy();
|
||||
expect(
|
||||
@@ -3764,12 +3808,16 @@ test('running match3d form generation can return to draft tab and reopen progres
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
|
||||
await user.click(await findCreationTypeButton('抓大鹅'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
await openDraftHub(user);
|
||||
|
||||
@@ -3779,7 +3827,11 @@ test('running match3d form generation can return to draft tab and reopen progres
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /继续创作《抓大鹅草稿》/u }),
|
||||
);
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
resolveCompile({ session: buildMockMatch3DAgentSession() });
|
||||
@@ -3848,11 +3900,15 @@ test('running match3d persisted draft reopens progress instead of unfinished res
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
|
||||
await user.click(await findCreationTypeButton('抓大鹅'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
|
||||
);
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(await screen.findAllByText('素材生成仍在后台处理')).not.toHaveLength(
|
||||
0,
|
||||
);
|
||||
@@ -3866,7 +3922,11 @@ test('running match3d persisted draft reopens progress instead of unfinished res
|
||||
await screen.findByRole('button', { name: /继续创作《赛博水果摊》/u }),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText('抓大鹅结果页')).toBeNull();
|
||||
expect(match3dCreationClient.getSession).toHaveBeenCalledWith(
|
||||
'match3d-running-persisted-session',
|
||||
@@ -4045,17 +4105,22 @@ test('running match3d form generation keeps other creation templates available',
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
|
||||
await user.click(await findCreationTypeButton('抓大鹅'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
|
||||
);
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
const puzzleTab = await screen.findByRole('tab', { name: '拼图' });
|
||||
expect((puzzleTab as HTMLButtonElement).disabled).toBe(false);
|
||||
await openCreateTemplateHub(user);
|
||||
const puzzleCard = await findCreationTypeButton('拼图');
|
||||
expect((puzzleCard as HTMLButtonElement).disabled).toBe(false);
|
||||
|
||||
await user.click(puzzleTab);
|
||||
await user.click(puzzleCard);
|
||||
const generatePuzzleButton = await screen.findByRole('button', {
|
||||
name: '生成草稿',
|
||||
});
|
||||
@@ -4114,16 +4179,21 @@ test('running match3d form generation keeps same template generation available',
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
|
||||
await user.click(await findCreationTypeButton('抓大鹅'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
|
||||
);
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
const match3dTab = await screen.findByRole('tab', { name: '抓大鹅' });
|
||||
expect((match3dTab as HTMLButtonElement).disabled).toBe(false);
|
||||
await user.click(match3dTab);
|
||||
await openCreateTemplateHub(user);
|
||||
const match3dCard = await findCreationTypeButton('抓大鹅');
|
||||
expect((match3dCard as HTMLButtonElement).disabled).toBe(false);
|
||||
await user.click(match3dCard);
|
||||
|
||||
const secondGenerateButton = await screen.findByRole('button', {
|
||||
name: '生成抓大鹅草稿',
|
||||
@@ -4150,7 +4220,11 @@ test('running match3d form generation keeps same template generation available',
|
||||
expect.objectContaining({ action: 'match3d_compile_draft' }),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
await openDraftHub(user);
|
||||
await waitFor(() => {
|
||||
@@ -4220,15 +4294,23 @@ test('running puzzle form generation creates a new puzzle draft on same template
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(await screen.findByRole('button', { name: '生成草稿' }));
|
||||
expect(await screen.findByText('拼图草稿生成进度')).toBeTruthy();
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成草稿' }),
|
||||
);
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '拼图草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
const puzzleTab = await screen.findByRole('tab', { name: '拼图' });
|
||||
expect((puzzleTab as HTMLButtonElement).disabled).toBe(false);
|
||||
await user.click(puzzleTab);
|
||||
await openCreateTemplateHub(user);
|
||||
const puzzleCard = await findCreationTypeButton('拼图');
|
||||
expect((puzzleCard as HTMLButtonElement).disabled).toBe(false);
|
||||
await user.click(puzzleCard);
|
||||
|
||||
expect(await screen.findByText('拼图工作区:missing-session')).toBeTruthy();
|
||||
expect(await screen.findByText(/拼图工作区:/u)).toBeTruthy();
|
||||
expect(screen.getByTestId('puzzle-workspace-busy-state')).toHaveProperty(
|
||||
'textContent',
|
||||
'idle',
|
||||
@@ -4239,9 +4321,7 @@ test('running puzzle form generation creates a new puzzle draft on same template
|
||||
expect((secondGenerateButton as HTMLButtonElement).disabled).toBe(false);
|
||||
await user.click(secondGenerateButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createPuzzleAgentSession).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
expect(createPuzzleAgentSession).toHaveBeenCalledTimes(1);
|
||||
expect(executePuzzleAgentAction).toHaveBeenCalledTimes(2);
|
||||
expect(executePuzzleAgentAction).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
@@ -4250,11 +4330,15 @@ test('running puzzle form generation creates a new puzzle draft on same template
|
||||
);
|
||||
expect(executePuzzleAgentAction).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'puzzle-parallel-session-2',
|
||||
'puzzle-session-1',
|
||||
expect.objectContaining({ action: 'compile_puzzle_draft' }),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('拼图草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '拼图草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
await openDraftHub(user);
|
||||
await waitFor(() => {
|
||||
@@ -4326,8 +4410,15 @@ test('running puzzle draft opens generation progress from draft tab', async () =
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(await screen.findByRole('button', { name: '生成草稿' }));
|
||||
expect(await screen.findByText('拼图草稿生成进度')).toBeTruthy();
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成草稿' }),
|
||||
);
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '拼图草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
await openDraftHub(user);
|
||||
@@ -4337,7 +4428,11 @@ test('running puzzle draft opens generation progress from draft tab', async () =
|
||||
screen.getByRole('button', { name: /继续创作《拼图草稿》/u }),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('拼图草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '拼图草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText('拼图结果页')).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
@@ -4370,7 +4465,9 @@ test('puzzle form checks mud points before creating a draft', async () => {
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await user.click(await screen.findByRole('button', { name: '生成草稿' }));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成草稿' }),
|
||||
);
|
||||
|
||||
const noticeDialog = await screen.findByRole('dialog', { name: '泥点不足' });
|
||||
expect(
|
||||
@@ -4598,7 +4695,7 @@ test('match3d result trial passes generated 2D image views into first runtime mo
|
||||
});
|
||||
});
|
||||
|
||||
test('match3d result back returns to platform creation page', async () => {
|
||||
test('match3d result back returns to draft hub when opened from shelf', async () => {
|
||||
const user = userEvent.setup();
|
||||
const match3dDraftWork: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-back-1',
|
||||
@@ -4652,9 +4749,15 @@ test('match3d result back returns to platform creation page', async () => {
|
||||
expect(await screen.findByText('抓大鹅结果页')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回' }));
|
||||
|
||||
const draftPanel = await findPlatformTabPanel('saves');
|
||||
await waitFor(() => {
|
||||
expect(draftPanel.getAttribute('aria-hidden')).toBe('false');
|
||||
});
|
||||
expect(
|
||||
await screen.findByRole('tablist', { name: '玩法模板分类' }),
|
||||
within(draftPanel).getByRole('tablist', { name: '作品筛选' }),
|
||||
).toBeTruthy();
|
||||
expect(within(draftPanel).getByText('自动试玩抓大鹅')).toBeTruthy();
|
||||
expect(getPlatformTabPanel('create').getAttribute('aria-hidden')).toBe('true');
|
||||
expect(screen.queryByText('抓大鹅结果页')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -4728,7 +4831,7 @@ test('match3d draft generation auto starts trial and runtime back opens draft re
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
|
||||
await user.click(await findCreationTypeButton('抓大鹅'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
|
||||
);
|
||||
@@ -4960,11 +5063,15 @@ test('completed match3d draft notice first opens trial then reopens result', asy
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
|
||||
await user.click(await findCreationTypeButton('抓大鹅'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
|
||||
);
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
await openDraftHub(user);
|
||||
await expectDraftHubGeneratingBadgeCountAtLeast(1);
|
||||
@@ -4981,7 +5088,11 @@ test('completed match3d draft notice first opens trial then reopens result', asy
|
||||
);
|
||||
|
||||
expect(await screen.findByText(/抓大鹅运行态/u)).toBeTruthy();
|
||||
expect(screen.queryByText('抓大鹅草稿生成进度')).toBeNull();
|
||||
expect(
|
||||
screen.queryByRole('progressbar', {
|
||||
name: '抓大鹅草稿生成进度',
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(match3dServerRuntimeAdapterMock.startRun).toHaveBeenCalledTimes(1);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -5023,14 +5134,7 @@ test('completed baby object match draft viewed immediately does not keep unread
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '宝贝识物' }));
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen
|
||||
.getByRole('tab', { name: '宝贝识物' })
|
||||
.getAttribute('aria-selected'),
|
||||
).toBe('true');
|
||||
});
|
||||
await user.click(await findCreationTypeButton('宝贝识物'));
|
||||
await user.type(await screen.findByLabelText('物品 A'), '苹果');
|
||||
await user.type(await screen.findByLabelText('物品 B'), '香蕉');
|
||||
await user.click(screen.getByRole('button', { name: '生成宝贝识物草稿' }));
|
||||
@@ -5062,8 +5166,10 @@ test('completed baby object match draft viewed immediately does not keep unread
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('宝贝识物结果页')).toBeNull();
|
||||
});
|
||||
await user.click(await screen.findByRole('button', { name: '返回' }));
|
||||
await openDraftHub(user);
|
||||
const reopenedDraftPanel = await findPlatformTabPanel('saves');
|
||||
await waitFor(() => {
|
||||
expect(reopenedDraftPanel.getAttribute('aria-hidden')).toBe('false');
|
||||
});
|
||||
expect(screen.queryByLabelText('新生成完成')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -5081,12 +5187,16 @@ test('completed baby object match draft shows unread marker after leaving genera
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '宝贝识物' }));
|
||||
await user.click(await findCreationTypeButton('宝贝识物'));
|
||||
await user.type(await screen.findByLabelText('物品 A'), '苹果');
|
||||
await user.type(await screen.findByLabelText('物品 B'), '香蕉');
|
||||
await user.click(screen.getByRole('button', { name: '生成宝贝识物草稿' }));
|
||||
|
||||
expect(await screen.findByText('宝贝识物草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '宝贝识物草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
await openDraftHub(user);
|
||||
|
||||
@@ -5110,8 +5220,10 @@ test('completed baby object match draft shows unread marker after leaving genera
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('宝贝识物结果页')).toBeNull();
|
||||
});
|
||||
await user.click(await screen.findByRole('button', { name: '返回' }));
|
||||
await openDraftHub(user);
|
||||
const draftPanel = await findPlatformTabPanel('saves');
|
||||
await waitFor(() => {
|
||||
expect(draftPanel.getAttribute('aria-hidden')).toBe('false');
|
||||
});
|
||||
expect(screen.queryByLabelText('新生成完成')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -5180,7 +5292,9 @@ test('puzzle draft generation auto starts trial and runtime back opens draft res
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await user.click(await screen.findByRole('button', { name: '生成草稿' }));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成草稿' }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(updatePuzzleWork).toHaveBeenCalledWith(
|
||||
@@ -5266,7 +5380,10 @@ test('embedded puzzle form recovers when compile request times out after backend
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('button', { name: '生成草稿' }));
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成草稿' }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getPuzzleAgentSession).toHaveBeenCalledWith(
|
||||
@@ -5303,12 +5420,10 @@ test('embedded puzzle form routes through requireAuth while logged out', async (
|
||||
);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
const generateButton = await screen.findByRole('button', {
|
||||
name: /生成草稿/u,
|
||||
});
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
|
||||
await user.click(generateButton);
|
||||
expect(requireAuth).toHaveBeenCalledTimes(1);
|
||||
expect(screen.queryByText('拼图工作区:missing-session')).toBeNull();
|
||||
expect(createCreativeAgentSession).not.toHaveBeenCalled();
|
||||
expect(streamCreativeAgentMessage).not.toHaveBeenCalled();
|
||||
expect(createRpgCreationSession).not.toHaveBeenCalled();
|
||||
@@ -6873,7 +6988,9 @@ test('embedded puzzle form maps raw bearer token errors to user-facing auth copy
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
const generateButton = screen.getByRole('button', { name: /生成草稿/u });
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await screen.findByText(/拼图工作区:/u);
|
||||
const generateButton = screen.getByRole('button', { name: '生成草稿' });
|
||||
|
||||
expect((generateButton as HTMLButtonElement).disabled).toBe(false);
|
||||
await user.click(generateButton);
|
||||
@@ -6910,8 +7027,10 @@ test('embedded puzzle form timeout exits busy state and shows a readable error',
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(await findCreationTypeButton('拼图'));
|
||||
await screen.findByText(/拼图工作区:/u);
|
||||
|
||||
const button = screen.getByRole('button', { name: /生成草稿/u });
|
||||
const button = screen.getByRole('button', { name: '生成草稿' });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -6941,7 +7060,7 @@ test('match3d creation tab stays usable even when public galleries fail', async
|
||||
await openCreateTemplateHub(user);
|
||||
expect(screen.queryByText('读取作品广场失败')).toBeNull();
|
||||
expect(screen.queryByText('读取抓大鹅广场失败')).toBeNull();
|
||||
expect(screen.getByRole('tab', { name: '抓大鹅' })).toBeTruthy();
|
||||
expect(await findCreationTypeButton('抓大鹅')).toBeTruthy();
|
||||
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -7334,6 +7453,7 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa
|
||||
profileId: 'puzzle-profile-public-1',
|
||||
levelId: null,
|
||||
},
|
||||
ISOLATED_RUNTIME_AUTH_OPTIONS,
|
||||
);
|
||||
vi.mocked(listProfileSaveArchives).mockClear();
|
||||
vi.mocked(listProfileSaveArchives).mockRejectedValueOnce(
|
||||
@@ -7374,10 +7494,7 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa
|
||||
await user.click(within(dialog).getByRole('button', { name: '下一关' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(advancePuzzleNextLevel).toHaveBeenCalledWith(
|
||||
clearedFirstLevel.runId,
|
||||
{},
|
||||
);
|
||||
expect(advancePuzzleNextLevel).toHaveBeenCalledWith(clearedFirstLevel.runId);
|
||||
});
|
||||
expect(
|
||||
(
|
||||
@@ -7722,6 +7839,9 @@ test('recommend puzzle remix return restarts recommendation instead of stale loa
|
||||
profileId: 'puzzle-profile-public-1',
|
||||
levelId: null,
|
||||
},
|
||||
expect.objectContaining({
|
||||
authImpact: 'local',
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(screen.queryByText('正在进入拼图关卡')).toBeNull();
|
||||
@@ -7808,6 +7928,7 @@ test('missing puzzle public detail returns to platform home', async () => {
|
||||
);
|
||||
|
||||
render(<TestWrapper />);
|
||||
vi.mocked(startPuzzleRun).mockClear();
|
||||
await openDiscoverHub(user);
|
||||
|
||||
const workCards = await screen.findAllByRole('button', { name: /失效拼图/u });
|
||||
@@ -7819,7 +7940,6 @@ test('missing puzzle public detail returns to platform home', async () => {
|
||||
expect(getPlatformTabPanel('home').getAttribute('aria-hidden')).toBe('false');
|
||||
expect(screen.queryByText('详情')).toBeNull();
|
||||
expect(screen.queryByText('资源不存在')).toBeNull();
|
||||
expect(startPuzzleRun).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('direct missing public work detail alert returns to platform home', async () => {
|
||||
@@ -7955,6 +8075,38 @@ test('public code search opens a published Match3D work by M3 code and starts ru
|
||||
expect(getRpgEntryWorldGalleryDetailByCode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('public code search opens the local Match3D demo and starts local runtime', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
vi.mocked(listMatch3DGallery).mockResolvedValue({ items: [] });
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
await openDiscoverHub(user);
|
||||
|
||||
const searchInput =
|
||||
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
|
||||
await user.type(searchInput, 'M3-20260525');
|
||||
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(match3dLocalRuntimeAdapterMock.startRun).toHaveBeenCalledWith(
|
||||
'match3d-demo-20260525',
|
||||
{},
|
||||
);
|
||||
});
|
||||
expect(match3dServerRuntimeAdapterMock.startRun).not.toHaveBeenCalled();
|
||||
expect(getMatch3DWorkDetail).not.toHaveBeenCalledWith(
|
||||
'match3d-demo-20260525',
|
||||
);
|
||||
expect(
|
||||
await screen.findByText('抓大鹅运行态:match3d-run-match3d-demo-20260525'),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('published Match3D runtime receives persisted generated models', async () => {
|
||||
const user = userEvent.setup();
|
||||
const match3dWork: Match3DWorkSummary = {
|
||||
@@ -8078,9 +8230,12 @@ test('starting draft generation leaves the agent workspace and shows the generat
|
||||
);
|
||||
});
|
||||
|
||||
expect(await screen.findByText('世界草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '世界草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText(/Agent工作区/u)).toBeNull();
|
||||
expect(screen.getAllByText('生成世界底稿').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('当前世界信息')).toBeTruthy();
|
||||
expect(screen.queryByText('回到工作区')).toBeNull();
|
||||
expect(screen.getByText('世界承诺')).toBeTruthy();
|
||||
@@ -8113,7 +8268,11 @@ test('running custom world draft generation can return to creation center with s
|
||||
).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '开始生成草稿' }));
|
||||
|
||||
expect(await screen.findByText('世界草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '世界草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
|
||||
expect(
|
||||
@@ -8143,9 +8302,12 @@ test('refresh restores running draft generation progress instead of agent worksp
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
expect(await screen.findByText('世界草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '世界草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText(/Agent工作区/u)).toBeNull();
|
||||
expect(screen.getAllByText('生成世界底稿').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('failed draft work continues on generation progress view instead of agent workspace', async () => {
|
||||
@@ -8194,7 +8356,11 @@ test('failed draft work continues on generation progress view instead of agent w
|
||||
expect(await screen.findByText('失败中的潮雾列岛')).toBeTruthy();
|
||||
await user.click(await screen.findByRole('button', { name: /继续创作/u }));
|
||||
|
||||
expect(await screen.findByText('世界草稿生成进度')).toBeTruthy();
|
||||
expect(
|
||||
await screen.findByRole('progressbar', {
|
||||
name: '世界草稿生成进度',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText(/Agent工作区/u)).toBeNull();
|
||||
});
|
||||
|
||||
@@ -9575,7 +9741,7 @@ test('save tab can resume a selected archive directly into the game', async () =
|
||||
});
|
||||
});
|
||||
|
||||
test('profile page exposes save archive picker as a direct entry', async () => {
|
||||
test('profile page keeps save archives inside played stats panel', async () => {
|
||||
const user = userEvent.setup();
|
||||
const handleContinueGame = vi.fn();
|
||||
|
||||
@@ -9617,20 +9783,11 @@ test('profile page exposes save archive picker as a direct entry', async () => {
|
||||
|
||||
render(<TestWrapper withAuth onContinueGame={handleContinueGame} />);
|
||||
|
||||
await clickFirstButtonByName(user, '我的');
|
||||
const shortcutRegion = await screen.findByRole('region', {
|
||||
name: '常用功能',
|
||||
});
|
||||
await user.click(
|
||||
within(shortcutRegion).getByRole('button', { name: /存档/u }),
|
||||
);
|
||||
await openProfilePlayedWorks(user);
|
||||
|
||||
const closeButton = await screen.findByLabelText('关闭存档');
|
||||
const modal = closeButton.closest('.fixed') as HTMLElement;
|
||||
expect(modal).toBeTruthy();
|
||||
expect(within(modal).getByText('SAVES')).toBeTruthy();
|
||||
|
||||
await user.click(within(modal).getByRole('button', { name: /潮雾列岛/u }));
|
||||
expect(screen.queryByLabelText('关闭存档')).toBeNull();
|
||||
expect(screen.queryByText('SAVES')).toBeNull();
|
||||
await clickFirstAsyncButtonByName(user, /潮雾列岛/u);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(resumeProfileSaveArchive).toHaveBeenCalledWith('custom:world-1');
|
||||
|
||||
@@ -78,6 +78,7 @@ import type {
|
||||
WechatMiniProgramPayParams,
|
||||
WechatNativePayment,
|
||||
} from '../../../packages/shared/src/contracts/runtime';
|
||||
import { isMatch3DDemoProfileId } from '../../data/match3dDemoGalleryCard';
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
|
||||
import type { AuthUser } from '../../services/authService';
|
||||
@@ -4064,6 +4065,7 @@ export function RpgEntryHomeView({
|
||||
const [mobileCenteredCardKey, setMobileCenteredCardKey] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const hasManualCategoryTagSelectionRef = useRef(false);
|
||||
const pendingPublicAuthorKeysRef = useRef<Set<string>>(new Set());
|
||||
const [publicAuthorSummariesByKey, setPublicAuthorSummariesByKey] = useState<
|
||||
Record<string, PublicUserSummary | null>
|
||||
@@ -4288,16 +4290,33 @@ export function RpgEntryHomeView({
|
||||
useEffect(() => {
|
||||
if (categoryGroups.length === 0) {
|
||||
setSelectedCategoryTag(null);
|
||||
hasManualCategoryTagSelectionRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const firstCategoryGroup = categoryGroups[0];
|
||||
const firstCategoryGroup =
|
||||
categoryGroups.find((group) =>
|
||||
group.entries.some((entry) => !isMatch3DDemoProfileId(entry.profileId)),
|
||||
) ?? categoryGroups[0];
|
||||
const selectedCategoryGroup =
|
||||
categoryGroups.find((group) => group.tag === selectedCategoryTag) ?? null;
|
||||
if (
|
||||
firstCategoryGroup &&
|
||||
!categoryGroups.some((group) => group.tag === selectedCategoryTag)
|
||||
(!selectedCategoryGroup ||
|
||||
(!hasManualCategoryTagSelectionRef.current &&
|
||||
selectedCategoryGroup.entries.every((entry) =>
|
||||
isMatch3DDemoProfileId(entry.profileId),
|
||||
) &&
|
||||
firstCategoryGroup.tag !== selectedCategoryGroup.tag))
|
||||
) {
|
||||
setSelectedCategoryTag(firstCategoryGroup.tag);
|
||||
}
|
||||
if (
|
||||
selectedCategoryTag &&
|
||||
!categoryGroups.some((group) => group.tag === selectedCategoryTag)
|
||||
) {
|
||||
hasManualCategoryTagSelectionRef.current = false;
|
||||
}
|
||||
}, [categoryGroups, selectedCategoryTag]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -5612,7 +5631,10 @@ export function RpgEntryHomeView({
|
||||
<button
|
||||
key={group.tag}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
onClick={() => {
|
||||
hasManualCategoryTagSelectionRef.current = true;
|
||||
setSelectedCategoryTag(group.tag);
|
||||
}}
|
||||
className={`platform-category-chip ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
@@ -5810,7 +5832,10 @@ export function RpgEntryHomeView({
|
||||
<button
|
||||
key={`${group.tag}:desktop-discover-category`}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
onClick={() => {
|
||||
hasManualCategoryTagSelectionRef.current = true;
|
||||
setSelectedCategoryTag(group.tag);
|
||||
}}
|
||||
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
@@ -6590,7 +6615,10 @@ export function RpgEntryHomeView({
|
||||
<button
|
||||
key={`${group.tag}:desktop-category`}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
onClick={() => {
|
||||
hasManualCategoryTagSelectionRef.current = true;
|
||||
setSelectedCategoryTag(group.tag);
|
||||
}}
|
||||
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
formatPlatformWorldTime,
|
||||
isBarkBattleGalleryEntry,
|
||||
isEdutainmentGalleryEntry,
|
||||
isMatch3DGalleryEntry,
|
||||
isVisualNovelGalleryEntry,
|
||||
isWoodenFishGalleryEntry,
|
||||
mapBabyObjectMatchDraftToPlatformGalleryCard,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
resolvePlatformPublicWorkCode,
|
||||
resolvePlatformWorldFallbackCoverImage,
|
||||
} from './rpgEntryWorldPresentation';
|
||||
import { buildMatch3DDemoGalleryCard } from '../../data/match3dDemoGalleryCard';
|
||||
|
||||
test('formatPlatformWorldTime formats backend seconds timestamp text as date', () => {
|
||||
expect(formatPlatformWorldTime('1777110165.990127Z')).toBe('2026-04-25');
|
||||
@@ -78,6 +80,24 @@ test('platform public cards use play type reference images as cover fallback', (
|
||||
);
|
||||
});
|
||||
|
||||
test('builds local Match3D demo gallery card with generated runtime assets intact', () => {
|
||||
const card = buildMatch3DDemoGalleryCard();
|
||||
|
||||
expect(isMatch3DGalleryEntry(card)).toBe(true);
|
||||
expect(card.publicWorkCode).toBe('M3-20260525');
|
||||
expect(resolvePlatformPublicWorkCode(card)).toBe('M3-20260525');
|
||||
expect(card.coverImageSrc).toBe(
|
||||
'/match3d-demo/undersea-candy-market/level-scene.png',
|
||||
);
|
||||
expect(card.generatedBackgroundAsset?.uiSpritesheetImageSrc).toBe(
|
||||
'/match3d-demo/undersea-candy-market/ui-spritesheet.png',
|
||||
);
|
||||
expect(card.generatedBackgroundAsset?.containerImageSrc).toBeNull();
|
||||
expect(card.generatedItemAssets?.[0]?.imageViews?.[0]?.imageSrc).toBe(
|
||||
'/match3d-demo/undersea-candy-market/item-slices/item-01/view-01.png',
|
||||
);
|
||||
});
|
||||
|
||||
test('buildPuzzleWorkCoverSlides prefers each level formal image', () => {
|
||||
const slides = buildPuzzleWorkCoverSlides({
|
||||
workId: 'work-1',
|
||||
|
||||
Reference in New Issue
Block a user