1
This commit is contained in:
@@ -63,6 +63,7 @@ import {
|
||||
listMatch3DGallery,
|
||||
listMatch3DWorks,
|
||||
} from '../../services/match3d-works';
|
||||
import * as match3dGeneratedModelCache from '../../services/match3dGeneratedModelCache';
|
||||
import {
|
||||
createPuzzleAgentSession,
|
||||
executePuzzleAgentAction,
|
||||
@@ -436,7 +437,20 @@ vi.mock('../../services/match3d-works', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../../services/match3dGeneratedModelCache', () => ({
|
||||
preloadMatch3DGeneratedModelAssets: vi.fn(() => Promise.resolve()),
|
||||
hasMatch3DGeneratedImageAsset: vi.fn(
|
||||
(assets: Match3DWorkSummary['generatedItemAssets']) =>
|
||||
Boolean(
|
||||
assets?.some(
|
||||
(asset) =>
|
||||
asset.imageSrc?.trim() ||
|
||||
asset.imageObjectKey?.trim() ||
|
||||
asset.imageViews?.some(
|
||||
(view) => view.imageSrc?.trim() || view.imageObjectKey?.trim(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
preloadMatch3DGeneratedRuntimeAssets: vi.fn(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
const match3dRuntimeServiceMocks = vi.hoisted(() => ({
|
||||
@@ -719,9 +733,11 @@ vi.mock('../match3d-result/Match3DResultView', () => ({
|
||||
vi.mock('../match3d-creation/Match3DAgentWorkspace', () => ({
|
||||
Match3DAgentWorkspace: ({
|
||||
session,
|
||||
isBusy,
|
||||
onCreateFromForm,
|
||||
}: {
|
||||
session: { sessionId: string; messages: Array<{ text: string }> } | null;
|
||||
isBusy?: boolean;
|
||||
onCreateFromForm?: (payload: {
|
||||
seedText: string;
|
||||
themeText: string;
|
||||
@@ -736,8 +752,12 @@ vi.mock('../match3d-creation/Match3DAgentWorkspace', () => ({
|
||||
{session?.messages.map((message) => (
|
||||
<div key={`${session.sessionId}-${message.text}`}>{message.text}</div>
|
||||
))}
|
||||
<div data-testid="match3d-workspace-busy-state">
|
||||
{isBusy ? 'busy' : 'idle'}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={() => {
|
||||
onCreateFromForm?.({
|
||||
seedText: '赛博水果摊题材,消除9次,难度6',
|
||||
@@ -773,6 +793,54 @@ vi.mock('../match3d-runtime/Match3DRuntimeShell', () => ({
|
||||
).length
|
||||
}
|
||||
</div>
|
||||
<div data-testid="match3d-runtime-generated-asset-count">
|
||||
{
|
||||
generatedItemAssets.filter(
|
||||
(asset) =>
|
||||
asset.modelSrc?.trim() ||
|
||||
asset.modelObjectKey?.trim() ||
|
||||
asset.imageSrc?.trim() ||
|
||||
asset.imageObjectKey?.trim() ||
|
||||
asset.imageViews?.some(
|
||||
(view) => view.imageSrc?.trim() || view.imageObjectKey?.trim(),
|
||||
) ||
|
||||
asset.backgroundMusic?.audioSrc?.trim() ||
|
||||
asset.clickSound?.audioSrc?.trim() ||
|
||||
asset.backgroundAsset?.imageSrc?.trim() ||
|
||||
asset.backgroundAsset?.imageObjectKey?.trim() ||
|
||||
asset.backgroundAsset?.containerImageSrc?.trim() ||
|
||||
asset.backgroundAsset?.containerImageObjectKey?.trim(),
|
||||
).length
|
||||
}
|
||||
</div>
|
||||
<div data-testid="match3d-runtime-generated-item-image-count">
|
||||
{
|
||||
generatedItemAssets.filter(
|
||||
(asset) =>
|
||||
asset.imageSrc?.trim() ||
|
||||
asset.imageObjectKey?.trim() ||
|
||||
asset.imageViews?.some(
|
||||
(view) => view.imageSrc?.trim() || view.imageObjectKey?.trim(),
|
||||
),
|
||||
).length
|
||||
}
|
||||
</div>
|
||||
<div data-testid="match3d-runtime-background-music-count">
|
||||
{
|
||||
generatedItemAssets.filter((asset) =>
|
||||
asset.backgroundMusic?.audioSrc?.trim(),
|
||||
).length
|
||||
}
|
||||
</div>
|
||||
<div data-testid="match3d-runtime-container-ui-count">
|
||||
{
|
||||
generatedItemAssets.filter(
|
||||
(asset) =>
|
||||
asset.backgroundAsset?.containerImageSrc?.trim() ||
|
||||
asset.backgroundAsset?.containerImageObjectKey?.trim(),
|
||||
).length
|
||||
}
|
||||
</div>
|
||||
<button type="button" onClick={onBack}>
|
||||
返回
|
||||
</button>
|
||||
@@ -1618,6 +1686,23 @@ function TestWrapper({
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(
|
||||
match3dGeneratedModelCache.hasMatch3DGeneratedImageAsset,
|
||||
).mockImplementation((assets) =>
|
||||
Boolean(
|
||||
assets?.some(
|
||||
(asset) =>
|
||||
asset.imageSrc?.trim() ||
|
||||
asset.imageObjectKey?.trim() ||
|
||||
asset.imageViews?.some(
|
||||
(view) => view.imageSrc?.trim() || view.imageObjectKey?.trim(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
vi.mocked(
|
||||
match3dGeneratedModelCache.preloadMatch3DGeneratedRuntimeAssets,
|
||||
).mockResolvedValue(undefined);
|
||||
vi.mocked(createServerMatch3DRuntimeAdapter).mockReturnValue(
|
||||
match3dServerRuntimeAdapterMock,
|
||||
);
|
||||
@@ -2676,6 +2761,228 @@ test('running match3d form generation can return to draft tab and reopen progres
|
||||
});
|
||||
});
|
||||
|
||||
test('running match3d form generation keeps other creation templates available', async () => {
|
||||
const user = userEvent.setup();
|
||||
const runningSession = buildMockMatch3DAgentSession({
|
||||
sessionId: 'match3d-running-session',
|
||||
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;
|
||||
}),
|
||||
);
|
||||
const puzzleReadySession: PuzzleAgentSessionSnapshot = {
|
||||
sessionId: 'puzzle-session-parallel-1',
|
||||
seedText: '暖灯猫街',
|
||||
currentTurn: 1,
|
||||
progressPercent: 100,
|
||||
stage: 'ready_to_publish',
|
||||
anchorPack: buildPuzzleAnchorPack(),
|
||||
draft: {
|
||||
workTitle: '并行拼图',
|
||||
workDescription: '抓大鹅后台生成时创建的新拼图。',
|
||||
levelName: '并行拼图',
|
||||
summary: '抓大鹅后台生成时创建的新拼图。',
|
||||
themeTags: ['并行创作'],
|
||||
forbiddenDirectives: [],
|
||||
creatorIntent: null,
|
||||
anchorPack: buildPuzzleAnchorPack(),
|
||||
candidates: [
|
||||
{
|
||||
candidateId: 'candidate-parallel-1',
|
||||
imageSrc: '/puzzle/parallel-candidate.png',
|
||||
assetId: 'asset-parallel-1',
|
||||
prompt: '暖灯猫街',
|
||||
actualPrompt: null,
|
||||
sourceType: 'generated',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
selectedCandidateId: 'candidate-parallel-1',
|
||||
coverImageSrc: '/puzzle/parallel-candidate.png',
|
||||
coverAssetId: 'asset-parallel-1',
|
||||
generationStatus: 'ready',
|
||||
levels: [
|
||||
{
|
||||
levelId: 'puzzle-level-parallel-1',
|
||||
levelName: '并行拼图',
|
||||
pictureDescription: '一只猫在雨夜灯牌下回头。',
|
||||
pictureReference: null,
|
||||
candidates: [
|
||||
{
|
||||
candidateId: 'candidate-parallel-1',
|
||||
imageSrc: '/puzzle/parallel-candidate.png',
|
||||
assetId: 'asset-parallel-1',
|
||||
prompt: '暖灯猫街',
|
||||
actualPrompt: null,
|
||||
sourceType: 'generated',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
selectedCandidateId: 'candidate-parallel-1',
|
||||
coverImageSrc: '/puzzle/parallel-candidate.png',
|
||||
coverAssetId: 'asset-parallel-1',
|
||||
generationStatus: 'ready',
|
||||
},
|
||||
],
|
||||
},
|
||||
messages: [],
|
||||
lastAssistantReply: '拼图草稿已经生成。',
|
||||
publishedProfileId: null,
|
||||
suggestedActions: [],
|
||||
resultPreview: null,
|
||||
updatedAt: '2026-05-13T10:00:00.000Z',
|
||||
};
|
||||
vi.mocked(executePuzzleAgentAction).mockResolvedValueOnce({
|
||||
operation: {
|
||||
operationId: 'compile-puzzle-parallel-1',
|
||||
type: 'compile_puzzle_draft',
|
||||
status: 'completed',
|
||||
phaseLabel: '已完成',
|
||||
phaseDetail: '草稿已生成',
|
||||
progress: 1,
|
||||
},
|
||||
session: puzzleReadySession,
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
|
||||
);
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).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);
|
||||
const generatePuzzleButton = await screen.findByRole('button', {
|
||||
name: '生成草稿',
|
||||
});
|
||||
expect((generatePuzzleButton as HTMLButtonElement).disabled).toBe(false);
|
||||
await user.click(generatePuzzleButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createPuzzleAgentSession).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(executePuzzleAgentAction).toHaveBeenCalledWith(
|
||||
'puzzle-session-1',
|
||||
expect.objectContaining({
|
||||
action: 'compile_puzzle_draft',
|
||||
}),
|
||||
);
|
||||
expect(match3dCreationClient.executeAction).toHaveBeenCalledTimes(1);
|
||||
|
||||
await act(async () => {
|
||||
resolveCompile({ session: buildMockMatch3DAgentSession() });
|
||||
});
|
||||
});
|
||||
|
||||
test('running match3d form generation keeps same template generation available', async () => {
|
||||
const user = userEvent.setup();
|
||||
const firstSession = buildMockMatch3DAgentSession({
|
||||
sessionId: 'match3d-parallel-session-1',
|
||||
draft: null,
|
||||
stage: 'collecting_config',
|
||||
});
|
||||
const secondSession = buildMockMatch3DAgentSession({
|
||||
sessionId: 'match3d-parallel-session-2',
|
||||
draft: null,
|
||||
stage: 'collecting_config',
|
||||
});
|
||||
let resolveFirstCompile!: (value: {
|
||||
session: Match3DAgentSessionSnapshot;
|
||||
}) => void;
|
||||
let resolveSecondCompile!: (value: {
|
||||
session: Match3DAgentSessionSnapshot;
|
||||
}) => void;
|
||||
vi.mocked(match3dCreationClient.createSession)
|
||||
.mockResolvedValueOnce({ session: firstSession })
|
||||
.mockResolvedValueOnce({ session: secondSession });
|
||||
vi.mocked(match3dCreationClient.executeAction)
|
||||
.mockReturnValueOnce(
|
||||
new Promise((resolve) => {
|
||||
resolveFirstCompile = resolve;
|
||||
}),
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
new Promise((resolve) => {
|
||||
resolveSecondCompile = resolve;
|
||||
}),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('tab', { name: '抓大鹅' }));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '生成抓大鹅草稿' }),
|
||||
);
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).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);
|
||||
|
||||
const secondGenerateButton = await screen.findByRole('button', {
|
||||
name: '生成抓大鹅草稿',
|
||||
});
|
||||
expect((secondGenerateButton as HTMLButtonElement).disabled).toBe(false);
|
||||
expect(screen.getByTestId('match3d-workspace-busy-state')).toHaveProperty(
|
||||
'textContent',
|
||||
'idle',
|
||||
);
|
||||
await user.click(secondGenerateButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(match3dCreationClient.createSession).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
expect(match3dCreationClient.executeAction).toHaveBeenCalledTimes(2);
|
||||
expect(match3dCreationClient.executeAction).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'match3d-parallel-session-1',
|
||||
expect.objectContaining({ action: 'match3d_compile_draft' }),
|
||||
);
|
||||
expect(match3dCreationClient.executeAction).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'match3d-parallel-session-2',
|
||||
expect.objectContaining({ action: 'match3d_compile_draft' }),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||
await openDraftHub(user);
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('抓大鹅草稿').length).toBeGreaterThanOrEqual(2);
|
||||
expect(screen.getAllByText('生成中').length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
resolveFirstCompile({
|
||||
session: buildMockMatch3DAgentSession({
|
||||
sessionId: 'match3d-parallel-session-1',
|
||||
}),
|
||||
});
|
||||
resolveSecondCompile({
|
||||
session: buildMockMatch3DAgentSession({
|
||||
sessionId: 'match3d-parallel-session-2',
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('match3d result trial passes generated models into first runtime mount', async () => {
|
||||
const user = userEvent.setup();
|
||||
const generatedItemAssets: Match3DWorkSummary['generatedItemAssets'] = [
|
||||
@@ -2761,6 +3068,166 @@ test('match3d result trial passes generated models into first runtime mount', as
|
||||
expect(
|
||||
await screen.findByTestId('match3d-runtime-generated-model-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
expect(
|
||||
screen.getByTestId('match3d-runtime-generated-asset-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
});
|
||||
|
||||
test('match3d result trial passes generated 2D image views into first runtime mount', async () => {
|
||||
const user = userEvent.setup();
|
||||
const generatedItemAssets: Match3DWorkSummary['generatedItemAssets'] = [
|
||||
{
|
||||
itemId: 'match3d-item-1',
|
||||
itemName: '草莓',
|
||||
imageSrc: null,
|
||||
imageObjectKey: null,
|
||||
imageViews: [1, 2, 3, 4, 5].map((viewIndex) => ({
|
||||
viewId: `view-${String(viewIndex).padStart(2, '0')}`,
|
||||
viewIndex,
|
||||
imageSrc:
|
||||
`/generated-match3d-assets/session/profile/items/match3d-item-1-item/views/view-${String(viewIndex).padStart(2, '0')}.png`,
|
||||
imageObjectKey: null,
|
||||
})),
|
||||
modelSrc: null,
|
||||
modelObjectKey: null,
|
||||
modelFileName: null,
|
||||
taskUuid: null,
|
||||
subscriptionKey: null,
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
},
|
||||
];
|
||||
const match3dDraftWork: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-draft-2d-1',
|
||||
profileId: 'match3d-profile-draft-2d-1',
|
||||
ownerUserId: 'user-1',
|
||||
sourceSessionId: 'match3d-session-draft-2d-1',
|
||||
gameName: '水果抓大鹅',
|
||||
themeText: '水果',
|
||||
summary: '',
|
||||
tags: ['水果', '抓大鹅'],
|
||||
coverImageSrc: null,
|
||||
referenceImageSrc: null,
|
||||
clearCount: 12,
|
||||
difficulty: 4,
|
||||
publicationStatus: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: '2026-05-13T10:30:00.000Z',
|
||||
publishedAt: null,
|
||||
publishReady: false,
|
||||
generatedItemAssets,
|
||||
};
|
||||
vi.mocked(listMatch3DWorks).mockResolvedValue({
|
||||
items: [match3dDraftWork],
|
||||
});
|
||||
vi.mocked(match3dCreationClient.getSession).mockResolvedValue({
|
||||
session: buildMockMatch3DAgentSession({
|
||||
sessionId: 'match3d-session-draft-2d-1',
|
||||
draft: {
|
||||
profileId: 'match3d-profile-draft-2d-1',
|
||||
gameName: '水果抓大鹅',
|
||||
themeText: '水果',
|
||||
summary: '',
|
||||
tags: ['水果', '抓大鹅'],
|
||||
coverImageSrc: null,
|
||||
referenceImageSrc: null,
|
||||
clearCount: 12,
|
||||
difficulty: 4,
|
||||
generatedItemAssets,
|
||||
},
|
||||
}),
|
||||
});
|
||||
vi.mocked(getMatch3DWorkDetail).mockResolvedValue({
|
||||
item: match3dDraftWork,
|
||||
});
|
||||
match3dServerRuntimeAdapterMock.startRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun(match3dDraftWork.profileId),
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openDraftHub(user);
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: /继续创作《水果抓大鹅》/u }),
|
||||
);
|
||||
expect(await screen.findByText('抓大鹅结果页')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '试玩' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(match3dServerRuntimeAdapterMock.startRun).toHaveBeenCalledWith(
|
||||
'match3d-profile-draft-2d-1',
|
||||
{},
|
||||
);
|
||||
});
|
||||
expect(
|
||||
await screen.findByTestId('match3d-runtime-generated-model-count'),
|
||||
).toHaveProperty('textContent', '0');
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId('match3d-runtime-generated-item-image-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
expect(
|
||||
screen.getByTestId('match3d-runtime-generated-asset-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
});
|
||||
});
|
||||
|
||||
test('match3d result back returns to platform creation page', async () => {
|
||||
const user = userEvent.setup();
|
||||
const match3dDraftWork: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-back-1',
|
||||
profileId: 'match3d-profile-back-1',
|
||||
ownerUserId: 'user-1',
|
||||
sourceSessionId: 'match3d-session-back-1',
|
||||
gameName: '自动试玩抓大鹅',
|
||||
themeText: '水果',
|
||||
summary: '',
|
||||
tags: ['水果', '休闲', '抓大鹅'],
|
||||
coverImageSrc: null,
|
||||
referenceImageSrc: null,
|
||||
clearCount: 12,
|
||||
difficulty: 4,
|
||||
publicationStatus: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: '2026-05-12T12:10:00.000Z',
|
||||
publishedAt: null,
|
||||
publishReady: false,
|
||||
generatedItemAssets: [],
|
||||
};
|
||||
|
||||
vi.mocked(listMatch3DWorks).mockResolvedValue({
|
||||
items: [match3dDraftWork],
|
||||
});
|
||||
vi.mocked(match3dCreationClient.getSession).mockResolvedValue({
|
||||
session: buildMockMatch3DAgentSession({
|
||||
sessionId: 'match3d-session-back-1',
|
||||
draft: {
|
||||
profileId: 'match3d-profile-back-1',
|
||||
gameName: '自动试玩抓大鹅',
|
||||
themeText: '水果',
|
||||
summary: '',
|
||||
tags: ['水果', '休闲', '抓大鹅'],
|
||||
coverImageSrc: null,
|
||||
referenceImageSrc: null,
|
||||
clearCount: 12,
|
||||
difficulty: 4,
|
||||
generatedItemAssets: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openDraftHub(user);
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: /继续创作《自动试玩抓大鹅》/u }),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('抓大鹅结果页')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '返回' }));
|
||||
|
||||
expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy();
|
||||
expect(screen.queryByText('抓大鹅结果页')).toBeNull();
|
||||
});
|
||||
|
||||
test('match3d draft generation auto starts trial and runtime back opens draft result', async () => {
|
||||
@@ -3915,7 +4382,7 @@ test('home recommendation Match3D runtime keeps profile generated models when ca
|
||||
});
|
||||
});
|
||||
|
||||
test('home recommendation Match3D runtime refetches detail when stale card only has image assets', async () => {
|
||||
test('home recommendation Match3D runtime keeps image, music and UI assets without requiring models', async () => {
|
||||
const match3dCard: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-card-image-only',
|
||||
profileId: 'match3d-profile-card-image-only',
|
||||
@@ -3949,6 +4416,108 @@ test('home recommendation Match3D runtime refetches detail when stale card only
|
||||
subscriptionKey: null,
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
backgroundMusic: {
|
||||
taskId: 'music-task-1',
|
||||
provider: 'vector-engine-suno',
|
||||
assetObjectId: 'asset-music-1',
|
||||
assetKind: 'match3d_background_music',
|
||||
audioSrc:
|
||||
'/generated-match3d-assets/session/profile/audio/background.mp3',
|
||||
prompt: '',
|
||||
title: '果园轻舞',
|
||||
updatedAt: '2026-05-12T10:00:00.000Z',
|
||||
},
|
||||
backgroundAsset: {
|
||||
prompt: '果园竖屏纯背景',
|
||||
imageSrc:
|
||||
'/generated-match3d-assets/session/profile/background/background.png',
|
||||
imageObjectKey:
|
||||
'generated-match3d-assets/session/profile/background/background.png',
|
||||
containerPrompt: '果园浅盘容器',
|
||||
containerImageSrc:
|
||||
'/generated-match3d-assets/session/profile/ui-container/container.png',
|
||||
containerImageObjectKey:
|
||||
'generated-match3d-assets/session/profile/ui-container/container.png',
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
vi.mocked(listMatch3DGallery).mockResolvedValue({
|
||||
items: [match3dCard],
|
||||
});
|
||||
match3dServerRuntimeAdapterMock.startRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun(match3dCard.profileId),
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId('match3d-runtime-generated-asset-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
});
|
||||
expect(getMatch3DWorkDetail).not.toHaveBeenCalledWith(
|
||||
'match3d-profile-card-image-only',
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('match3d-runtime-background-music-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
expect(screen.getByTestId('match3d-runtime-container-ui-count')).toHaveProperty(
|
||||
'textContent',
|
||||
'1',
|
||||
);
|
||||
});
|
||||
|
||||
test('home recommendation Match3D runtime reloads detail when card only has UI assets', async () => {
|
||||
const match3dCard: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-card-ui-only',
|
||||
profileId: 'match3d-profile-card-ui-only',
|
||||
ownerUserId: 'user-2',
|
||||
sourceSessionId: 'match3d-session-card-ui-only',
|
||||
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: null,
|
||||
imageObjectKey: null,
|
||||
imageViews: [],
|
||||
modelSrc: null,
|
||||
modelObjectKey: null,
|
||||
modelFileName: null,
|
||||
taskUuid: null,
|
||||
subscriptionKey: null,
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
backgroundAsset: {
|
||||
prompt: '果园竖屏纯背景',
|
||||
imageSrc:
|
||||
'/generated-match3d-assets/session/profile/background/background.png',
|
||||
imageObjectKey:
|
||||
'generated-match3d-assets/session/profile/background/background.png',
|
||||
containerPrompt: '果园浅盘容器',
|
||||
containerImageSrc:
|
||||
'/generated-match3d-assets/session/profile/ui-container/container.png',
|
||||
containerImageObjectKey:
|
||||
'generated-match3d-assets/session/profile/ui-container/container.png',
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -3956,17 +4525,29 @@ test('home recommendation Match3D runtime refetches detail when stale card only
|
||||
...match3dCard,
|
||||
generatedItemAssets: [
|
||||
{
|
||||
...match3dCard.generatedItemAssets![0]!,
|
||||
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',
|
||||
itemId: 'match3d-item-1',
|
||||
itemName: '草莓',
|
||||
imageSrc: null,
|
||||
imageObjectKey: null,
|
||||
imageViews: [
|
||||
{
|
||||
viewId: 'view-01',
|
||||
viewIndex: 1,
|
||||
imageSrc:
|
||||
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/views/view-01.png',
|
||||
imageObjectKey: null,
|
||||
},
|
||||
],
|
||||
modelSrc: null,
|
||||
modelObjectKey: null,
|
||||
modelFileName: null,
|
||||
taskUuid: null,
|
||||
subscriptionKey: null,
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
vi.mocked(listMatch3DGallery).mockResolvedValue({
|
||||
items: [match3dCard],
|
||||
});
|
||||
@@ -3981,14 +4562,12 @@ test('home recommendation Match3D runtime refetches detail when stale card only
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getMatch3DWorkDetail).toHaveBeenCalledWith(
|
||||
'match3d-profile-card-image-only',
|
||||
'match3d-profile-card-ui-only',
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId('match3d-runtime-generated-model-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
});
|
||||
expect(
|
||||
await screen.findByTestId('match3d-runtime-generated-item-image-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
});
|
||||
|
||||
test('home recommendation surfaces start failure instead of staying in loading state', async () => {
|
||||
|
||||
Reference in New Issue
Block a user