/* @vitest-environment jsdom */
import { fireEvent, render, screen } from '@testing-library/react';
import { act } from 'react';
import type { ImgHTMLAttributes } from 'react';
import { expect, test, vi } from 'vitest';
import type {
PuzzleClearCardAsset,
PuzzleClearPatternGroup,
PuzzleClearWorkProfileResponse,
} from '../../../packages/shared/src/contracts/puzzleClear';
import { PuzzleClearResultView } from './PuzzleClearResultView';
vi.mock('../ResolvedAssetImage', () => ({
ResolvedAssetImage: ({
src,
alt,
className,
...rest
}: {
src?: string | null;
alt?: string;
className?: string;
[key: string]: unknown;
}) =>
src ? (
)}
/>
) : null,
}));
function createPatternGroup(index: number): PuzzleClearPatternGroup {
return {
groupId: `group-${index}`,
shape: '1x2',
width: 2,
height: 1,
atlasX: index * 64,
atlasY: 0,
atlasWidth: 128,
atlasHeight: 64,
};
}
function createCard(index: number): PuzzleClearCardAsset {
return {
cardId: `card-${index}`,
groupId: `group-${Math.floor(index / 2)}`,
shape: '1x2',
orientation: 'horizontal',
partX: index % 2,
partY: 0,
imageSrc: `/cards/card-${index}.png`,
imageObjectKey: `generated-puzzle-clear-assets/card-${index}.png`,
assetObjectId: `assetobj_card_${index}`,
sourceAtlasCell: `${index}:0:0`,
};
}
function createProfile(
overrides: Partial = {},
): PuzzleClearWorkProfileResponse {
const atlasAsset = {
assetId: 'atlas-1',
imageSrc: '/atlas.png',
imageObjectKey: 'generated-puzzle-clear-assets/atlas.png',
assetObjectId: 'assetobj_atlas',
generationProvider: 'gpt-image-2',
prompt: '星港',
width: 2560,
height: 2560,
};
const boardBackgroundAsset = {
...atlasAsset,
assetId: 'board-background-1',
imageSrc: '/board-background.png',
imageObjectKey: 'generated-puzzle-clear-assets/board-background.png',
assetObjectId: 'assetobj_board_background',
};
const patternGroups = Array.from({ length: 35 }, (_, index) =>
createPatternGroup(index),
);
const cardAssets = Array.from({ length: 95 }, (_, index) => createCard(index));
const draft = {
templateId: 'puzzle-clear',
templateName: '拼消消',
profileId: 'puzzle-clear-profile-12345678',
workTitle: '星港拼消消',
workDescription: '霓虹星港主题',
themePrompt: '星港',
boardBackgroundPrompt: '星港中央棋盘底图',
generateBoardBackground: true,
boardBackgroundAsset,
cardBackImageSrc: '/creation-type-references/puzzle-clear-card-back.webp',
atlasAsset,
patternGroups,
cardAssets,
generationStatus: 'ready' as const,
};
return {
summary: {
runtimeKind: 'puzzle-clear',
workId: 'puzzle-clear-work-12345678',
profileId: 'puzzle-clear-profile-12345678',
ownerUserId: 'user-1',
sourceSessionId: 'puzzle-clear-session-1',
workTitle: '星港拼消消',
workDescription: '霓虹星港主题',
themePrompt: '星港',
coverImageSrc: '/atlas.png',
publicationStatus: 'draft',
playCount: 0,
updatedAt: '2026-05-30T00:00:00.000Z',
publishedAt: null,
publishReady: true,
generationStatus: 'ready',
...overrides,
},
draft,
boardBackgroundAsset,
atlasAsset,
patternGroups,
cardAssets,
};
}
test('结果页展示 atlas、中央底图与卡牌预览,并触发试玩、发布和图集重试', async () => {
const onStartTestRun = vi.fn();
const onPublish = vi.fn();
const onRegenerateAtlas = vi.fn();
const { container } = render(
,
);
expect(screen.getByAltText('场地底图').getAttribute('src')).toBe(
'/board-background.png',
);
expect(screen.getByAltText('素材图集').getAttribute('src')).toBe('/atlas.png');
expect(screen.getByText('35')).not.toBeNull();
expect(screen.getByText('95')).not.toBeNull();
expect(container.querySelectorAll('img[src^="/cards/"]')).toHaveLength(24);
fireEvent.click(screen.getByRole('button', { name: /试玩/u }));
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /发布/u }));
});
fireEvent.click(screen.getByRole('button', { name: /图集/u }));
expect(onStartTestRun).toHaveBeenCalledTimes(1);
expect(onPublish).toHaveBeenCalledTimes(1);
expect(onRegenerateAtlas).toHaveBeenCalledTimes(1);
});
test('结果页在素材未发布就绪时禁用发布,且不写入规则说明文案', () => {
render(
,
);
expect(
(screen.getByRole('button', { name: /发布/u }) as HTMLButtonElement).disabled,
).toBe(true);
expect(screen.queryByText(/规则|玩法说明|拖动卡片|拼接完整/u)).toBeNull();
});