收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
@@ -51,7 +51,9 @@ vi.mock('../../services/match3d-works', () => ({
|
||||
|
||||
vi.mock('../../services/match3dSpritesheetParser', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('../../services/match3dSpritesheetParser')>();
|
||||
await importOriginal<
|
||||
typeof import('../../services/match3dSpritesheetParser')
|
||||
>();
|
||||
return {
|
||||
...actual,
|
||||
loadMatch3DSpritesheetAssetRegions:
|
||||
@@ -146,6 +148,96 @@ function createReadyGeneratedItemAsset(index: number) {
|
||||
}
|
||||
|
||||
describe('Match3DResultView', () => {
|
||||
test('标准白底面板使用 PlatformSubpanel lg 外壳', async () => {
|
||||
match3dSpritesheetParser.loadMatch3DSpritesheetAssetRegions.mockResolvedValue(
|
||||
Array.from({ length: 10 }, (_, index) => ({
|
||||
label: `素材${index + 1}`,
|
||||
x: index * 2,
|
||||
y: 0,
|
||||
width: 2,
|
||||
height: 2,
|
||||
sheetWidth: 20,
|
||||
sheetHeight: 2,
|
||||
imageSrc: `data:image/png;base64,item-${index + 1}`,
|
||||
})),
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
<Match3DResultView
|
||||
profile={createProfile({
|
||||
generatedItemAssets: [
|
||||
createReadyGeneratedItemAsset(1),
|
||||
createReadyGeneratedItemAsset(2),
|
||||
],
|
||||
generatedBackgroundAsset: {
|
||||
prompt: '果园主题抓大鹅竖屏背景',
|
||||
imageSrc:
|
||||
'/generated-match3d-assets/session/profile/background/background.png',
|
||||
imageObjectKey: null,
|
||||
uiSpritesheetPrompt: 'UI spritesheet',
|
||||
uiSpritesheetImageSrc:
|
||||
'/generated-match3d-assets/session/profile/ui-spritesheet/ui.png',
|
||||
uiSpritesheetImageObjectKey: null,
|
||||
itemSpritesheetPrompt: '物品 spritesheet',
|
||||
itemSpritesheetImageSrc:
|
||||
'/generated-match3d-assets/session/profile/item-spritesheet/items.png',
|
||||
itemSpritesheetImageObjectKey: null,
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
},
|
||||
})}
|
||||
onBack={() => {}}
|
||||
onStartTestRun={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const getStandardPanels = () =>
|
||||
Array.from(
|
||||
container.querySelectorAll('section.platform-subpanel'),
|
||||
).filter(
|
||||
(panel) =>
|
||||
panel.className.includes('rounded-[1.35rem]') &&
|
||||
panel.className.includes('p-4') &&
|
||||
panel.className.includes('sm:p-5'),
|
||||
);
|
||||
|
||||
expect(getStandardPanels()).toHaveLength(1);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '难度配置' }));
|
||||
|
||||
expect(getStandardPanels()).toHaveLength(2);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '素材配置' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'UI素材' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByAltText('物品素材图')).toBeTruthy();
|
||||
});
|
||||
expect(getStandardPanels()).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('标准字段标题使用 PlatformFieldLabel section 外观', () => {
|
||||
render(
|
||||
<Match3DResultView
|
||||
profile={createProfile()}
|
||||
onBack={() => {}}
|
||||
onStartTestRun={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const workNameLabel = screen.getByText('作品名称');
|
||||
|
||||
expect(workNameLabel.className).toContain('tracking-[0.18em]');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '发布' }));
|
||||
|
||||
expect(screen.getByText('发布检查').className).toContain(
|
||||
'tracking-[0.18em]',
|
||||
);
|
||||
expect(screen.getByText('封面图').className).toContain('tracking-[0.18em]');
|
||||
expect(screen.getByText('封面图').className).toContain('block');
|
||||
});
|
||||
|
||||
test('作品信息 Tab 字段命名对齐拼图草稿且描述可为空', async () => {
|
||||
const profile = createProfile();
|
||||
vi.mocked(match3dWorksService.updateMatch3DWork).mockResolvedValue({
|
||||
@@ -583,7 +675,9 @@ describe('Match3DResultView', () => {
|
||||
within(publishDialog).getByRole('button', { name: '发布到广场' }),
|
||||
).toHaveProperty('disabled', true);
|
||||
expect(match3dWorksService.publishMatch3DWork).not.toHaveBeenCalled();
|
||||
fireEvent.click(within(publishDialog).getByRole('button', { name: '取消' }));
|
||||
fireEvent.click(
|
||||
within(publishDialog).getByRole('button', { name: '取消' }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: '难度配置' }));
|
||||
expect(screen.getByText('已生成物品种类')).toBeTruthy();
|
||||
expect(screen.getAllByText('2 种').length).toBeGreaterThan(0);
|
||||
@@ -638,9 +732,10 @@ describe('Match3DResultView', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '发布' }));
|
||||
fireEvent.click(
|
||||
within(
|
||||
screen.getByRole('dialog', { name: '发布抓大鹅作品' }),
|
||||
).getByRole('button', { name: '发布到广场' }),
|
||||
within(screen.getByRole('dialog', { name: '发布抓大鹅作品' })).getByRole(
|
||||
'button',
|
||||
{ name: '发布到广场' },
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -704,8 +799,14 @@ describe('Match3DResultView', () => {
|
||||
expect(screen.getByRole('button', { name: '物品' })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: 'UI素材' })).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: '背景音乐' })).toBeNull();
|
||||
expect(screen.getAllByRole('button', { name: /打开.+物品素材/u }))
|
||||
.toHaveLength(20);
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: /打开.+物品素材/u }),
|
||||
).toHaveLength(20);
|
||||
const assetPreviewFrame = screen
|
||||
.getByRole('button', { name: '打开水果核心物件物品素材' })
|
||||
.querySelector('div.relative');
|
||||
expect(assetPreviewFrame?.className).toContain('aspect-square');
|
||||
expect(assetPreviewFrame?.className).toContain('bg-white/82');
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: '打开水果核心物件物品素材' }),
|
||||
@@ -851,6 +952,11 @@ describe('Match3DResultView', () => {
|
||||
fireEvent.change(screen.getByLabelText('物品名称 4'), {
|
||||
target: { value: '苹果' },
|
||||
});
|
||||
const batchDialog = screen.getByRole('dialog', { name: '批量新增物品' });
|
||||
const parsedNameBadge = within(batchDialog).getByText('蓝莓');
|
||||
expect(parsedNameBadge.className).toContain('rounded-full');
|
||||
expect(parsedNameBadge.className).toContain('bg-white/72');
|
||||
expect(parsedNameBadge.className).not.toContain('platform-pill');
|
||||
expect(
|
||||
screen.getByRole('button', { name: /生成物品素材 · 2泥点/u }),
|
||||
).toBeTruthy();
|
||||
@@ -908,7 +1014,16 @@ describe('Match3DResultView', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭' }));
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: '批量新增物品' })).toBeNull();
|
||||
expect(screen.getByLabelText('物品素材生成进度')).toBeTruthy();
|
||||
const generationProgress = screen.getByLabelText('物品素材生成进度');
|
||||
expect(generationProgress.className).toContain(
|
||||
'border-[var(--platform-subpanel-border)]',
|
||||
);
|
||||
expect(generationProgress.className).toContain('rounded-[1rem]');
|
||||
expect(generationProgress.className).toContain('bg-white/62');
|
||||
expect(
|
||||
screen.getByRole('progressbar', { name: '物品素材生成进度百分比' })
|
||||
.className,
|
||||
).toContain('platform-progress-track');
|
||||
|
||||
deferred.resolve({
|
||||
item: createProfile({
|
||||
@@ -967,6 +1082,12 @@ describe('Match3DResultView', () => {
|
||||
'value',
|
||||
'苹果',
|
||||
);
|
||||
const targetItemBadge = within(
|
||||
screen.getByRole('dialog', { name: '批量重新生成物品' }),
|
||||
).getByText('苹果');
|
||||
expect(targetItemBadge.className).toContain('rounded-full');
|
||||
expect(targetItemBadge.className).toContain('bg-white/72');
|
||||
expect(targetItemBadge.className).not.toContain('platform-pill');
|
||||
fireEvent.change(screen.getByLabelText('重新生成物品名称 2'), {
|
||||
target: { value: '' },
|
||||
});
|
||||
@@ -1079,6 +1200,19 @@ describe('Match3DResultView', () => {
|
||||
).toBe('true');
|
||||
expect(screen.getByText('36 件')).toBeTruthy();
|
||||
expect(screen.getAllByText('9 种').length).toBeGreaterThan(0);
|
||||
const difficultySummaryPanel = screen.getByTestId(
|
||||
'match3d-difficulty-summary-panel',
|
||||
);
|
||||
expect(difficultySummaryPanel.className.split(/\s+/u)).not.toContain(
|
||||
'platform-subpanel',
|
||||
);
|
||||
expect(difficultySummaryPanel.className).toContain('bg-white/72');
|
||||
expect(difficultySummaryPanel.className).toContain('rounded-[1rem]');
|
||||
expect(difficultySummaryPanel.className).toContain('p-3');
|
||||
const difficultyBadge = screen.getByText('难度 4');
|
||||
expect(difficultyBadge.className).toContain('rounded-full');
|
||||
expect(difficultyBadge.className).toContain('bg-[var(--platform-accent)]');
|
||||
expect(difficultyBadge.className).toContain('text-white');
|
||||
|
||||
fireEvent.change(difficultySlider, { target: { value: '3' } });
|
||||
|
||||
@@ -1093,6 +1227,7 @@ describe('Match3DResultView', () => {
|
||||
});
|
||||
expect(screen.getByText('63 件')).toBeTruthy();
|
||||
expect(screen.getAllByText('20 种').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('难度 8').className).toContain('rounded-full');
|
||||
expect(onSaved).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
clearCount: 21,
|
||||
@@ -1154,6 +1289,13 @@ describe('Match3DResultView', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开物品1物品素材' }));
|
||||
|
||||
expect(screen.getByDisplayValue('物品1')).toBeTruthy();
|
||||
const itemDetailPanel = screen
|
||||
.getByDisplayValue('物品1')
|
||||
.closest('section');
|
||||
expect(itemDetailPanel?.className).toContain('platform-subpanel');
|
||||
expect(itemDetailPanel?.className).toContain('rounded-[1.5rem]');
|
||||
expect(itemDetailPanel?.className).toContain('p-3');
|
||||
expect(itemDetailPanel?.className).toContain('sm:p-5');
|
||||
expect(
|
||||
[...document.querySelectorAll('img')].some((image) =>
|
||||
image
|
||||
@@ -1218,28 +1360,32 @@ describe('Match3DResultView', () => {
|
||||
|
||||
const preview = screen.getByLabelText('物品1五视角预览');
|
||||
const stage = screen.getByTestId('match3d-item-preview-stage');
|
||||
const focusImage = screen.getByTestId('match3d-item-preview-focus-image');
|
||||
const stageImage = stage.querySelector('img');
|
||||
const thumbnails = screen.getByTestId('match3d-item-preview-thumbnails');
|
||||
expect(stage.className).toContain('aspect-square');
|
||||
expect(stage.className).toContain('max-w-[22rem]');
|
||||
expect(focusImage.className).toContain('place-items-center');
|
||||
expect(focusImage.querySelector('img')?.className).toContain('p-3');
|
||||
expect(stage.className).toContain('bg-white/82');
|
||||
expect(stageImage?.className).toContain('p-3');
|
||||
expect(thumbnails.style.gridAutoColumns).toBe('calc((100% - 1.5rem) / 4)');
|
||||
const activeThumbnailFrame = screen
|
||||
.getByRole('button', { name: '切换物品1视角3' })
|
||||
.querySelector('div.relative');
|
||||
expect(activeThumbnailFrame?.className).toContain('aspect-square');
|
||||
expect(activeThumbnailFrame?.className).toContain('rounded-[0.65rem]');
|
||||
expect(preview.querySelectorAll('img')).toHaveLength(6);
|
||||
expect(
|
||||
screen
|
||||
.getByRole('button', { name: '切换物品1视角3' })
|
||||
.getAttribute('aria-pressed'),
|
||||
).toBe('true');
|
||||
expect(
|
||||
screen.queryByTestId('match3d-item-preview-focus-frame'),
|
||||
).toBeNull();
|
||||
expect(screen.queryByTestId('match3d-item-preview-focus-frame')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '切换物品1视角5' }));
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('match3d-item-preview-focus-image')
|
||||
.getAttribute('data-preview-src'),
|
||||
.getByTestId('match3d-item-preview-stage')
|
||||
.querySelector('img')
|
||||
?.getAttribute('src'),
|
||||
).toContain('views/view-05.png');
|
||||
});
|
||||
|
||||
@@ -1382,8 +1528,12 @@ describe('Match3DResultView', () => {
|
||||
'/generated-match3d-assets/session/profile/ui-spritesheet/ui.png',
|
||||
);
|
||||
expect(
|
||||
screen.queryByLabelText('UI背景图画面描述提示词'),
|
||||
).toBeNull();
|
||||
screen.getByAltText('游戏背景图').closest('div.relative')?.className,
|
||||
).toContain('aspect-[9/16]');
|
||||
expect(
|
||||
screen.getByAltText('UI素材图').closest('div.relative')?.className,
|
||||
).toContain('aspect-square');
|
||||
expect(screen.queryByLabelText('UI背景图画面描述提示词')).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /重新生成/u })).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '预览UI页面' }));
|
||||
@@ -1558,20 +1708,35 @@ describe('Match3DResultView', () => {
|
||||
expect(screen.getByAltText('物品素材图').getAttribute('src')).toBe(
|
||||
'/generated-match3d-assets/session/profile/item-spritesheet/items.png',
|
||||
);
|
||||
expect(
|
||||
screen.getByAltText('物品素材图').closest('div.relative')?.className,
|
||||
).toContain('aspect-square');
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
document.querySelector('.platform-media-tile-grid')?.className,
|
||||
).toContain('grid-cols-5');
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('match3d-item-spritesheet-preview-0-0')
|
||||
.getAttribute('src'),
|
||||
.querySelector('img')
|
||||
?.getAttribute('src'),
|
||||
).toBe('data:image/png;base64,item-1');
|
||||
expect(
|
||||
screen
|
||||
.getByTestId('match3d-item-spritesheet-preview-1-4')
|
||||
.getAttribute('src'),
|
||||
.querySelector('img')
|
||||
?.getAttribute('src'),
|
||||
).toBe('data:image/png;base64,item-10');
|
||||
});
|
||||
expect(screen.getByText('草莓')).toBeTruthy();
|
||||
expect(screen.getByText('苹果')).toBeTruthy();
|
||||
const strawberryGroupCard = screen.getByText('草莓').parentElement;
|
||||
expect(strawberryGroupCard?.className).toContain(
|
||||
'border-[var(--platform-subpanel-border)]',
|
||||
);
|
||||
expect(strawberryGroupCard?.className).toContain('rounded-[1rem]');
|
||||
expect(strawberryGroupCard?.className).toContain('bg-white/58');
|
||||
expect(strawberryGroupCard?.className).toContain('p-3');
|
||||
expect(
|
||||
match3dSpritesheetParser.loadMatch3DSpritesheetAssetRegions,
|
||||
).toHaveBeenCalledWith(
|
||||
@@ -1633,8 +1798,12 @@ describe('Match3DResultView', () => {
|
||||
expect(screen.queryByLabelText('容器形象画面描述提示词')).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '容器形象' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /重新生成/u })).toBeNull();
|
||||
expect(match3dWorksService.generateMatch3DBackgroundImage).not.toHaveBeenCalled();
|
||||
expect(match3dWorksService.generateMatch3DContainerImage).not.toHaveBeenCalled();
|
||||
expect(
|
||||
match3dWorksService.generateMatch3DBackgroundImage,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(
|
||||
match3dWorksService.generateMatch3DContainerImage,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('历史草稿同时带旧 draft 和 profile 素材时以 profile 多视角素材补齐试玩资产', async () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user