This commit is contained in:
2026-05-11 20:27:41 +08:00
parent e30b733b17
commit 481a27fc53
60 changed files with 6357 additions and 1100 deletions

View File

@@ -32,6 +32,8 @@ vi.mock('./Match3DModelPreview', () => ({
}));
vi.mock('../../services/assetReadUrlService', () => ({
isGeneratedLegacyPath: (value: string) =>
/^\/?generated-[^/?#]+\/.+/u.test(value.trim()),
readAssetBytes: vi.fn(() =>
Promise.resolve(
new Response(new Uint8Array([104, 101, 108, 108, 111]), {
@@ -47,6 +49,7 @@ vi.mock('../../services/assetReadUrlService', () => ({
vi.mock('../../services/match3d-works', () => ({
generateMatch3DWorkTags: vi.fn(),
publishMatch3DWork: vi.fn(),
updateMatch3DGeneratedItemAssets: vi.fn(),
updateMatch3DWork: vi.fn(),
}));
@@ -180,6 +183,53 @@ describe('Match3DResultView', () => {
expect(match3dWorksService.publishMatch3DWork).not.toHaveBeenCalled();
});
test('试玩前保存响应缺少素材时仍把当前生成模型带入运行态', async () => {
const generatedItemAssets = [
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc:
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
imageObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
modelSrc:
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/model/model.glb',
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',
error: null,
},
];
const profile = createProfile({ generatedItemAssets });
const savedProfile = createProfile({ generatedItemAssets: [] });
const onStartTestRun = vi.fn();
vi.mocked(match3dWorksService.updateMatch3DWork).mockResolvedValue({
item: savedProfile,
});
render(
<Match3DResultView
profile={profile}
onBack={() => {}}
onStartTestRun={onStartTestRun}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '试玩' }));
await waitFor(() => {
expect(onStartTestRun).toHaveBeenCalledWith(
expect.objectContaining({
profileId: profile.profileId,
generatedItemAssets,
}),
);
});
});
test('发布仍要求封面和标签数量满足门槛', () => {
render(
<Match3DResultView
@@ -277,6 +327,44 @@ describe('Match3DResultView', () => {
);
});
test('历史素材只有 modelObjectKey 时仍能进入模型预览', () => {
const modelObjectKey =
'generated-match3d-assets/session/profile/items/strawberry/model/model.glb';
render(
<Match3DResultView
profile={createProfile({
clearCount: 3,
generatedItemAssets: [
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc:
'/generated-match3d-assets/session/profile/items/strawberry/image.png',
imageObjectKey:
'generated-match3d-assets/session/profile/items/strawberry/image.png',
modelSrc: null,
modelObjectKey,
modelFileName: 'strawberry.glb',
taskUuid: 'task-strawberry',
subscriptionKey: 'sub-strawberry',
status: 'model_ready',
error: null,
},
],
})}
onBack={() => {}}
onStartTestRun={() => {}}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '3D素材' }));
fireEvent.click(screen.getByRole('button', { name: //u }));
expect(
screen.getByTestId('match3d-model-preview').getAttribute('data-model-src'),
).toBe(modelObjectKey);
});
test('草稿阶段仅有切割图片时模型预览为空', () => {
render(
<Match3DResultView
@@ -364,6 +452,82 @@ describe('Match3DResultView', () => {
expect(screen.queryByRole('button', { name: //u })).toBeNull();
});
test('历史草稿同时带旧 draft 和 profile 模型时以 profile 模型补齐试玩资产', async () => {
const draftAsset = {
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc:
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
imageObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
modelSrc: null,
modelObjectKey: null,
modelFileName: null,
taskUuid: null,
subscriptionKey: null,
status: 'image_ready',
error: null,
};
const profileAsset = {
...draftAsset,
modelSrc: null,
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',
};
const profile = createProfile({ generatedItemAssets: [profileAsset] });
const onStartTestRun = vi.fn();
vi.mocked(match3dWorksService.updateMatch3DWork).mockResolvedValue({
item: createProfile({ generatedItemAssets: [] }),
});
render(
<Match3DResultView
profile={profile}
draft={{
profileId: profile.profileId,
gameName: profile.gameName,
themeText: profile.themeText,
summary: profile.summary,
tags: profile.tags,
coverImageSrc: null,
referenceImageSrc: null,
clearCount: profile.clearCount,
difficulty: profile.difficulty,
generatedItemAssets: [draftAsset],
}}
onBack={() => {}}
onStartTestRun={onStartTestRun}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '3D素材' }));
fireEvent.click(screen.getByRole('button', { name: //u }));
expect(
screen.getByTestId('match3d-model-preview').getAttribute('data-model-src'),
).toBe(profileAsset.modelObjectKey);
fireEvent.click(screen.getByRole('button', { name: '试玩' }));
await waitFor(() => {
expect(onStartTestRun).toHaveBeenCalledWith(
expect.objectContaining({
generatedItemAssets: [
expect.objectContaining({
itemId: 'match3d-item-1',
modelObjectKey: profileAsset.modelObjectKey,
status: 'model_ready',
}),
],
}),
);
});
});
test('Rodin 图生模型提交前会把 generated 参考图转成 data URL', async () => {
vi.mocked(hyper3dService.submitHyper3dImageToModel).mockResolvedValue({
ok: true,
@@ -393,6 +557,28 @@ describe('Match3DResultView', () => {
],
raw: {},
});
vi.mocked(match3dWorksService.updateMatch3DGeneratedItemAssets)
.mockResolvedValue({
item: createProfile({
generatedItemAssets: [
{
itemId: 'match3d-item-1',
itemName: '草莓',
imageSrc:
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
imageObjectKey:
'generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
modelSrc: 'https://cdn.example.com/strawberry.glb',
modelObjectKey: null,
modelFileName: 'strawberry.glb',
taskUuid: 'task-image',
subscriptionKey: 'sub-image',
status: 'model_ready',
error: null,
},
],
}),
});
vi.stubGlobal('fetch', vi.fn());
render(
@@ -440,6 +626,23 @@ describe('Match3DResultView', () => {
expect(hyper3dService.getHyper3dDownloads).toHaveBeenCalledWith({
taskUuid: 'task-image',
});
expect(
match3dWorksService.updateMatch3DGeneratedItemAssets,
).toHaveBeenCalledWith(
'match3d-profile-1',
expect.objectContaining({
generatedItemAssets: [
expect.objectContaining({
itemId: 'match3d-item-1',
modelSrc: 'https://cdn.example.com/strawberry.glb',
modelFileName: 'strawberry.glb',
status: 'model_ready',
taskUuid: 'task-image',
subscriptionKey: 'sub-image',
}),
],
}),
);
});
});
});