210 lines
6.1 KiB
TypeScript
210 lines
6.1 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { render, screen } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { expect, test, vi } from 'vitest';
|
|
|
|
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
|
|
import {
|
|
BABY_OBJECT_MATCH_EDUTAINMENT_TAG,
|
|
BABY_OBJECT_MATCH_TEMPLATE_ID,
|
|
BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
|
} from '../../../packages/shared/src/contracts/edutainmentBabyObject';
|
|
import { BabyObjectMatchResultView } from './BabyObjectMatchResultView';
|
|
|
|
vi.mock('../ResolvedAssetImage', () => ({
|
|
ResolvedAssetImage: ({
|
|
src,
|
|
alt,
|
|
className,
|
|
}: {
|
|
src?: string | null;
|
|
alt?: string;
|
|
className?: string;
|
|
}) => (src ? <img src={src} alt={alt} className={className} /> : null),
|
|
}));
|
|
|
|
function createDraft(overrides: Partial<BabyObjectMatchDraft> = {}) {
|
|
const draft: BabyObjectMatchDraft = {
|
|
draftId: 'baby-object-draft-1',
|
|
profileId: 'baby-object-profile-1',
|
|
templateId: BABY_OBJECT_MATCH_TEMPLATE_ID,
|
|
templateName: BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
|
workTitle: '宝贝识物',
|
|
workDescription: '苹果和香蕉识物分类',
|
|
itemNames: ['苹果', '香蕉'],
|
|
itemAssets: [
|
|
{
|
|
itemId: 'baby-object-item-1',
|
|
itemName: '苹果',
|
|
imageSrc: 'data:image/svg+xml;utf8,a',
|
|
assetObjectId: null,
|
|
generationProvider: 'placeholder',
|
|
prompt: '苹果',
|
|
},
|
|
{
|
|
itemId: 'baby-object-item-2',
|
|
itemName: '香蕉',
|
|
imageSrc: 'data:image/svg+xml;utf8,b',
|
|
assetObjectId: null,
|
|
generationProvider: 'placeholder',
|
|
prompt: '香蕉',
|
|
},
|
|
],
|
|
visualPackage: null,
|
|
themeTags: ['宝贝识物'],
|
|
publicationStatus: 'draft',
|
|
createdAt: '2026-05-11T00:00:00.000Z',
|
|
updatedAt: '2026-05-11T00:00:00.000Z',
|
|
publishedAt: null,
|
|
...overrides,
|
|
};
|
|
|
|
return draft;
|
|
}
|
|
|
|
function createGeneratedDraft() {
|
|
return createDraft({
|
|
itemAssets: [
|
|
{
|
|
itemId: 'baby-object-item-1',
|
|
itemName: '苹果',
|
|
imageSrc: 'data:image/png;base64,a',
|
|
assetObjectId: null,
|
|
generationProvider: 'vector-engine-gpt-image-2',
|
|
prompt: '苹果',
|
|
},
|
|
{
|
|
itemId: 'baby-object-item-2',
|
|
itemName: '香蕉',
|
|
imageSrc: 'data:image/png;base64,b',
|
|
assetObjectId: null,
|
|
generationProvider: 'vector-engine-gpt-image-2',
|
|
prompt: '香蕉',
|
|
},
|
|
],
|
|
visualPackage: {
|
|
themePrompt: '果园主题',
|
|
assets: [
|
|
{
|
|
assetId: 'baby-object-visual-background',
|
|
assetKind: 'background',
|
|
imageSrc: 'data:image/png;base64,background',
|
|
assetObjectId: null,
|
|
generationProvider: 'vector-engine-gpt-image-2',
|
|
prompt: 'background',
|
|
},
|
|
{
|
|
assetId: 'baby-object-visual-ui-frame',
|
|
assetKind: 'ui-frame',
|
|
imageSrc: 'data:image/png;base64,ui',
|
|
assetObjectId: null,
|
|
generationProvider: 'vector-engine-gpt-image-2',
|
|
prompt: 'ui',
|
|
},
|
|
{
|
|
assetId: 'baby-object-visual-gift-box',
|
|
assetKind: 'gift-box',
|
|
imageSrc: 'data:image/png;base64,gift',
|
|
assetObjectId: null,
|
|
generationProvider: 'vector-engine-gpt-image-2',
|
|
prompt: 'gift',
|
|
},
|
|
{
|
|
assetId: 'baby-object-visual-basket',
|
|
assetKind: 'basket',
|
|
imageSrc: 'data:image/png;base64,basket',
|
|
assetObjectId: null,
|
|
generationProvider: 'vector-engine-gpt-image-2',
|
|
prompt: 'basket',
|
|
},
|
|
{
|
|
assetId: 'baby-object-visual-smoke-puff',
|
|
assetKind: 'smoke-puff',
|
|
imageSrc: 'data:image/png;base64,smoke',
|
|
assetObjectId: null,
|
|
generationProvider: 'vector-engine-gpt-image-2',
|
|
prompt: 'smoke',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
}
|
|
|
|
test('baby object result publishes with exact edutainment tag', async () => {
|
|
const user = userEvent.setup();
|
|
const onPublish = vi.fn();
|
|
|
|
render(
|
|
<BabyObjectMatchResultView
|
|
draft={createGeneratedDraft()}
|
|
onBack={() => {}}
|
|
onPublish={onPublish}
|
|
/>,
|
|
);
|
|
|
|
await user.click(screen.getByRole('button', { name: '发布' }));
|
|
|
|
expect(onPublish).toHaveBeenCalledTimes(1);
|
|
expect(onPublish.mock.calls[0]?.[0].themeTags[0]).toBe(
|
|
BABY_OBJECT_MATCH_EDUTAINMENT_TAG,
|
|
);
|
|
expect(onPublish.mock.calls[0]?.[0].themeTags).toContain('宝贝识物');
|
|
});
|
|
|
|
test('baby object result exposes save and test run actions', async () => {
|
|
const user = userEvent.setup();
|
|
const onSaveDraft = vi.fn();
|
|
const onStartTestRun = vi.fn();
|
|
|
|
render(
|
|
<BabyObjectMatchResultView
|
|
draft={createGeneratedDraft()}
|
|
onBack={() => {}}
|
|
onSaveDraft={onSaveDraft}
|
|
onStartTestRun={onStartTestRun}
|
|
/>,
|
|
);
|
|
|
|
await user.click(screen.getByRole('button', { name: '保存草稿' }));
|
|
await user.click(screen.getByRole('button', { name: '试玩' }));
|
|
|
|
expect(onSaveDraft).toHaveBeenCalledTimes(1);
|
|
expect(onStartTestRun).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('baby object result blocks placeholder assets and exposes regeneration', async () => {
|
|
const user = userEvent.setup();
|
|
const onPublish = vi.fn();
|
|
const onStartTestRun = vi.fn();
|
|
const onRegenerateAssets = vi.fn();
|
|
|
|
render(
|
|
<BabyObjectMatchResultView
|
|
draft={createDraft()}
|
|
onBack={() => {}}
|
|
onPublish={onPublish}
|
|
onStartTestRun={onStartTestRun}
|
|
onRegenerateAssets={onRegenerateAssets}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByText('当前作品仍是占位资源,请重新生成 image-2 资源后再试玩或发布。'),
|
|
).toBeTruthy();
|
|
expect(
|
|
(screen.getByRole('button', { name: '试玩' }) as HTMLButtonElement)
|
|
.disabled,
|
|
).toBe(true);
|
|
expect(
|
|
(screen.getByRole('button', { name: '发布' }) as HTMLButtonElement)
|
|
.disabled,
|
|
).toBe(true);
|
|
|
|
await user.click(screen.getByRole('button', { name: '重新生成资源' }));
|
|
|
|
expect(onRegenerateAssets).toHaveBeenCalledTimes(1);
|
|
expect(onPublish).not.toHaveBeenCalled();
|
|
expect(onStartTestRun).not.toHaveBeenCalled();
|
|
});
|