1
This commit is contained in:
@@ -45,96 +45,79 @@ afterEach(() => {
|
||||
function createSession(
|
||||
overrides: Partial<PuzzleAgentSessionSnapshot> = {},
|
||||
): PuzzleAgentSessionSnapshot {
|
||||
const anchorPack = {
|
||||
themePromise: {
|
||||
key: 'themePromise',
|
||||
label: '题材承诺',
|
||||
value: '雨夜猫咪',
|
||||
status: 'confirmed' as const,
|
||||
},
|
||||
visualSubject: {
|
||||
key: 'visualSubject',
|
||||
label: '画面主体',
|
||||
value: '屋檐下的猫',
|
||||
status: 'confirmed' as const,
|
||||
},
|
||||
visualMood: {
|
||||
key: 'visualMood',
|
||||
label: '视觉气质',
|
||||
value: '温暖',
|
||||
status: 'confirmed' as const,
|
||||
},
|
||||
compositionHooks: {
|
||||
key: 'compositionHooks',
|
||||
label: '拼图记忆点',
|
||||
value: '雨滴与灯牌',
|
||||
status: 'confirmed' as const,
|
||||
},
|
||||
tagsAndForbidden: {
|
||||
key: 'tagsAndForbidden',
|
||||
label: '标签与禁忌',
|
||||
value: '猫咪、雨夜',
|
||||
status: 'confirmed' as const,
|
||||
},
|
||||
};
|
||||
const level = {
|
||||
levelId: 'puzzle-level-1',
|
||||
levelName: '雨夜猫街',
|
||||
pictureDescription: '屋檐下的猫与暖灯街角。',
|
||||
candidates: [
|
||||
{
|
||||
candidateId: 'candidate-1',
|
||||
imageSrc: '/puzzle/candidate-1.png',
|
||||
assetId: 'asset-1',
|
||||
prompt: '雨夜猫咪',
|
||||
actualPrompt: null,
|
||||
sourceType: 'generated' as const,
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
selectedCandidateId: 'candidate-1',
|
||||
coverImageSrc: '/puzzle/candidate-1.png',
|
||||
coverAssetId: 'asset-1',
|
||||
generationStatus: 'ready' as const,
|
||||
};
|
||||
const baseSession: PuzzleAgentSessionSnapshot = {
|
||||
sessionId: 'puzzle-session-1',
|
||||
currentTurn: 2,
|
||||
progressPercent: 88,
|
||||
stage: 'ready_to_publish',
|
||||
anchorPack: {
|
||||
themePromise: {
|
||||
key: 'themePromise',
|
||||
label: '题材承诺',
|
||||
value: '雨夜猫咪',
|
||||
status: 'confirmed',
|
||||
},
|
||||
visualSubject: {
|
||||
key: 'visualSubject',
|
||||
label: '画面主体',
|
||||
value: '屋檐下的猫',
|
||||
status: 'confirmed',
|
||||
},
|
||||
visualMood: {
|
||||
key: 'visualMood',
|
||||
label: '视觉气质',
|
||||
value: '温暖',
|
||||
status: 'confirmed',
|
||||
},
|
||||
compositionHooks: {
|
||||
key: 'compositionHooks',
|
||||
label: '拼图记忆点',
|
||||
value: '雨滴与灯牌',
|
||||
status: 'confirmed',
|
||||
},
|
||||
tagsAndForbidden: {
|
||||
key: 'tagsAndForbidden',
|
||||
label: '标签与禁忌',
|
||||
value: '猫咪、雨夜',
|
||||
status: 'confirmed',
|
||||
},
|
||||
},
|
||||
anchorPack,
|
||||
draft: {
|
||||
levelName: '雨夜猫街',
|
||||
summary: '屋檐下的猫与暖灯街角。',
|
||||
themeTags: ['猫咪', '雨夜'],
|
||||
workTitle: '暖灯猫街作品',
|
||||
workDescription: '一套雨夜猫街主题拼图。',
|
||||
levelName: level.levelName,
|
||||
summary: level.pictureDescription,
|
||||
themeTags: ['猫咪', '雨夜', '暖灯'],
|
||||
forbiddenDirectives: [],
|
||||
creatorIntent: null,
|
||||
anchorPack: {
|
||||
themePromise: {
|
||||
key: 'themePromise',
|
||||
label: '题材承诺',
|
||||
value: '雨夜猫咪',
|
||||
status: 'confirmed',
|
||||
},
|
||||
visualSubject: {
|
||||
key: 'visualSubject',
|
||||
label: '画面主体',
|
||||
value: '屋檐下的猫',
|
||||
status: 'confirmed',
|
||||
},
|
||||
visualMood: {
|
||||
key: 'visualMood',
|
||||
label: '视觉气质',
|
||||
value: '温暖',
|
||||
status: 'confirmed',
|
||||
},
|
||||
compositionHooks: {
|
||||
key: 'compositionHooks',
|
||||
label: '拼图记忆点',
|
||||
value: '雨滴与灯牌',
|
||||
status: 'confirmed',
|
||||
},
|
||||
tagsAndForbidden: {
|
||||
key: 'tagsAndForbidden',
|
||||
label: '标签与禁忌',
|
||||
value: '猫咪、雨夜',
|
||||
status: 'confirmed',
|
||||
},
|
||||
},
|
||||
candidates: [
|
||||
{
|
||||
candidateId: 'candidate-1',
|
||||
imageSrc: '/puzzle/candidate-1.png',
|
||||
assetId: 'asset-1',
|
||||
prompt: '雨夜猫咪',
|
||||
actualPrompt: null,
|
||||
sourceType: 'generated',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
selectedCandidateId: 'candidate-1',
|
||||
coverImageSrc: '/puzzle/candidate-1.png',
|
||||
coverAssetId: 'asset-1',
|
||||
anchorPack,
|
||||
candidates: level.candidates,
|
||||
selectedCandidateId: level.selectedCandidateId,
|
||||
coverImageSrc: level.coverImageSrc,
|
||||
coverAssetId: level.coverAssetId,
|
||||
generationStatus: 'ready',
|
||||
levels: [level],
|
||||
metadata: null,
|
||||
},
|
||||
messages: [],
|
||||
@@ -160,40 +143,7 @@ function createSession(
|
||||
}
|
||||
|
||||
describe('PuzzleResultView', () => {
|
||||
test('auto saves renamed title to the puzzle work profile', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
|
||||
item: {} as never,
|
||||
});
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession()}
|
||||
profileId="puzzle-profile-session-1"
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('雨夜猫街'), {
|
||||
target: { value: '暖灯猫街' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(puzzleWorksService.updatePuzzleWork).toHaveBeenCalledWith(
|
||||
'puzzle-profile-session-1',
|
||||
expect.objectContaining({
|
||||
levelName: '暖灯猫街',
|
||||
summary: '屋檐下的猫与暖灯街角。',
|
||||
themeTags: ['猫咪', '雨夜'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('uses one ordered list without tabs or persistent publish validation', () => {
|
||||
test('renders level list and work info tabs', () => {
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession()}
|
||||
@@ -203,117 +153,22 @@ describe('PuzzleResultView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('button', { name: '基本信息' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '拼图图片' })).toBeNull();
|
||||
const html = document.body.textContent ?? '';
|
||||
expect(html.indexOf('关卡名称')).toBeLessThan(html.indexOf('画面预览'));
|
||||
expect(html.indexOf('画面预览')).toBeLessThan(html.indexOf('画面描述'));
|
||||
expect(html.indexOf('画面描述')).toBeLessThan(html.indexOf('重新生成画面'));
|
||||
expect(html.indexOf('重新生成画面')).toBeLessThan(html.indexOf('题材标签'));
|
||||
expect(screen.queryByText('作者预览')).toBeNull();
|
||||
expect(screen.queryByText('发布校验')).toBeNull();
|
||||
expect(screen.getByRole('button', { name: /作品测试/u })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /发布/u })).toBeTruthy();
|
||||
});
|
||||
expect(screen.getByRole('button', { name: '拼图关卡' })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '作品信息' })).toBeTruthy();
|
||||
expect(screen.getByText('雨夜猫街')).toBeTruthy();
|
||||
|
||||
test('edits theme tags with chips instead of a persistent tag input', () => {
|
||||
vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
|
||||
item: {} as never,
|
||||
});
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession()}
|
||||
profileId="puzzle-profile-session-1"
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
/>,
|
||||
fireEvent.click(screen.getByRole('button', { name: '作品信息' }));
|
||||
expect(screen.getByLabelText('作品名称')).toHaveProperty(
|
||||
'value',
|
||||
'暖灯猫街作品',
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText('新题材标签')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByLabelText('删除标签 猫咪'));
|
||||
expect(screen.queryByText('猫咪')).toBeNull();
|
||||
expect(screen.getByText('雨夜')).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByLabelText('新增题材标签'));
|
||||
fireEvent.change(screen.getByLabelText('新题材标签'), {
|
||||
target: { value: '暖灯' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加' }));
|
||||
|
||||
expect(screen.getByText('暖灯')).toBeTruthy();
|
||||
expect(screen.queryByLabelText('新题材标签')).toBeNull();
|
||||
});
|
||||
|
||||
test('shows blockers only after clicking publish and blocks publish action', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession({
|
||||
resultPreview: {
|
||||
draft: createSession().draft!,
|
||||
publishReady: false,
|
||||
blockers: [
|
||||
{
|
||||
id: 'missing-cover',
|
||||
code: 'missing-cover',
|
||||
message: '请先选择正式图',
|
||||
},
|
||||
],
|
||||
qualityFindings: [],
|
||||
},
|
||||
})}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={onExecuteAction}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText('请先选择正式图')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /发布/u }));
|
||||
const dialog = screen.getByRole('dialog', { name: '发布拼图作品' });
|
||||
expect(within(dialog).getByText('请先选择正式图')).toBeTruthy();
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: '发布到广场' }));
|
||||
expect(onExecuteAction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('starts work test from the current editable draft', () => {
|
||||
const onStartTestRun = vi.fn();
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession()}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
onStartTestRun={onStartTestRun}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByDisplayValue('雨夜猫街'), {
|
||||
target: { value: '暖灯猫街' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||||
target: { value: '一只猫在雨夜灯牌下回头。' },
|
||||
});
|
||||
fireEvent.click(screen.getByLabelText('新增题材标签'));
|
||||
fireEvent.change(screen.getByLabelText('新题材标签'), {
|
||||
target: { value: '暖灯' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /作品测试/u }));
|
||||
|
||||
expect(onStartTestRun).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
levelName: '暖灯猫街',
|
||||
summary: '一只猫在雨夜灯牌下回头。',
|
||||
themeTags: ['猫咪', '雨夜', '暖灯'],
|
||||
}),
|
||||
expect(screen.getByLabelText('作品描述')).toHaveProperty(
|
||||
'value',
|
||||
'一套雨夜猫街主题拼图。',
|
||||
);
|
||||
});
|
||||
|
||||
test('auto saves edited picture description to the puzzle work profile', async () => {
|
||||
test('auto saves work info and levels through one payload', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
|
||||
item: {} as never,
|
||||
@@ -328,8 +183,9 @@ describe('PuzzleResultView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||||
target: { value: '一只猫在雨夜灯牌下回头。' },
|
||||
fireEvent.click(screen.getByRole('button', { name: '作品信息' }));
|
||||
fireEvent.change(screen.getByLabelText('作品名称'), {
|
||||
target: { value: '暖灯猫街合集' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
@@ -339,83 +195,68 @@ describe('PuzzleResultView', () => {
|
||||
expect(puzzleWorksService.updatePuzzleWork).toHaveBeenCalledWith(
|
||||
'puzzle-profile-session-1',
|
||||
expect.objectContaining({
|
||||
summary: '一只猫在雨夜灯牌下回头。',
|
||||
workTitle: '暖灯猫街合集',
|
||||
workDescription: '一套雨夜猫街主题拼图。',
|
||||
levelName: '雨夜猫街',
|
||||
summary: '屋檐下的猫与暖灯街角。',
|
||||
themeTags: ['猫咪', '雨夜', '暖灯'],
|
||||
levels: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
levelId: 'puzzle-level-1',
|
||||
levelName: '雨夜猫街',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('requires at least three theme tags before publish can pass', () => {
|
||||
test('opens an independent level detail dialog for generation and test play', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
const onStartTestRun = vi.fn();
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession()}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={onExecuteAction}
|
||||
onStartTestRun={onStartTestRun}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByLabelText('删除标签 猫咪'));
|
||||
fireEvent.click(screen.getByRole('button', { name: /发布/u }));
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '发布拼图作品' });
|
||||
expect(
|
||||
within(dialog).getByText('正式标签数量必须在 3 到 6 之间。'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
(
|
||||
within(dialog).getByRole('button', {
|
||||
name: '发布到广场',
|
||||
}) as HTMLButtonElement
|
||||
).disabled,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('publishes with the edited picture description', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession({
|
||||
draft: {
|
||||
...createSession().draft!,
|
||||
themeTags: ['猫咪', '雨夜', '暖灯'],
|
||||
},
|
||||
resultPreview: {
|
||||
draft: {
|
||||
...createSession().draft!,
|
||||
themeTags: ['猫咪', '雨夜', '暖灯'],
|
||||
},
|
||||
publishReady: true,
|
||||
blockers: [],
|
||||
qualityFindings: [],
|
||||
},
|
||||
})}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={onExecuteAction}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||||
fireEvent.click(screen.getByText('雨夜猫街'));
|
||||
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
|
||||
fireEvent.change(within(dialog).getByLabelText('关卡名称'), {
|
||||
target: { value: '暖灯猫街' },
|
||||
});
|
||||
fireEvent.change(within(dialog).getByLabelText('画面描述'), {
|
||||
target: { value: '一只猫在雨夜灯牌下回头。' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: /发布/u }));
|
||||
fireEvent.click(
|
||||
within(screen.getByRole('dialog', { name: '发布拼图作品' })).getByRole(
|
||||
'button',
|
||||
{ name: '发布到广场' },
|
||||
),
|
||||
);
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: /重新生成画面/u }));
|
||||
|
||||
expect(onExecuteAction).toHaveBeenCalledWith({
|
||||
action: 'publish_puzzle_work',
|
||||
levelName: '雨夜猫街',
|
||||
summary: '一只猫在雨夜灯牌下回头。',
|
||||
themeTags: ['猫咪', '雨夜', '暖灯'],
|
||||
action: 'generate_puzzle_images',
|
||||
levelId: 'puzzle-level-1',
|
||||
promptText: '一只猫在雨夜灯牌下回头。',
|
||||
referenceImageSrc: undefined,
|
||||
candidateCount: 1,
|
||||
});
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: /体验该关/u }));
|
||||
expect(onStartTestRun).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
levelName: '暖灯猫街',
|
||||
summary: '一只猫在雨夜灯牌下回头。',
|
||||
levels: [
|
||||
expect.objectContaining({
|
||||
levelId: 'puzzle-level-1',
|
||||
levelName: '暖灯猫街',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('auto saves added and removed theme tags', async () => {
|
||||
test('adds and deletes levels from the list', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
|
||||
item: {} as never,
|
||||
@@ -430,11 +271,10 @@ describe('PuzzleResultView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByLabelText('新增题材标签'));
|
||||
fireEvent.change(screen.getByLabelText('新题材标签'), {
|
||||
target: { value: '暖灯' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /新增关卡/u }));
|
||||
expect(screen.getByRole('dialog', { name: '关卡详情' })).toBeTruthy();
|
||||
fireEvent.click(screen.getByLabelText('关闭'));
|
||||
expect(screen.getAllByText('第2关').length).toBeGreaterThan(0);
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
@@ -443,11 +283,16 @@ describe('PuzzleResultView', () => {
|
||||
expect(puzzleWorksService.updatePuzzleWork).toHaveBeenLastCalledWith(
|
||||
'puzzle-profile-session-1',
|
||||
expect.objectContaining({
|
||||
themeTags: ['猫咪', '雨夜', '暖灯'],
|
||||
levels: expect.arrayContaining([
|
||||
expect.objectContaining({ levelId: 'puzzle-level-1' }),
|
||||
expect.objectContaining({ levelName: '第2关' }),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByLabelText('删除标签 猫咪'));
|
||||
fireEvent.click(screen.getByLabelText('删除关卡 第2关'));
|
||||
expect(screen.queryByText('第2关')).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
@@ -455,12 +300,16 @@ describe('PuzzleResultView', () => {
|
||||
expect(puzzleWorksService.updatePuzzleWork).toHaveBeenLastCalledWith(
|
||||
'puzzle-profile-session-1',
|
||||
expect.objectContaining({
|
||||
themeTags: ['雨夜', '暖灯'],
|
||||
levels: [
|
||||
expect.objectContaining({
|
||||
levelId: 'puzzle-level-1',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('generates one image from the picture description and replaces current image', () => {
|
||||
test('publishes with work info and serialized levels', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
|
||||
render(
|
||||
@@ -471,24 +320,34 @@ describe('PuzzleResultView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('画面描述')).toBeTruthy();
|
||||
expect(screen.queryByText(/候选图/u)).toBeNull();
|
||||
expect(screen.queryByText(/请生成一张适合正方形拼图关卡/u)).toBeNull();
|
||||
fireEvent.click(screen.getByRole('button', { name: /发布/u }));
|
||||
fireEvent.click(
|
||||
within(screen.getByRole('dialog', { name: '发布拼图作品' })).getByRole(
|
||||
'button',
|
||||
{ name: '发布到广场' },
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||||
target: { value: '一只猫在雨夜灯牌下回头。' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: /重新生成画面/u }));
|
||||
|
||||
expect(onExecuteAction).toHaveBeenCalledWith({
|
||||
action: 'generate_puzzle_images',
|
||||
promptText: '一只猫在雨夜灯牌下回头。',
|
||||
referenceImageSrc: undefined,
|
||||
candidateCount: 1,
|
||||
});
|
||||
expect(onExecuteAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'publish_puzzle_work',
|
||||
workTitle: '暖灯猫街作品',
|
||||
workDescription: '一套雨夜猫街主题拼图。',
|
||||
levelName: '雨夜猫街',
|
||||
summary: '屋檐下的猫与暖灯街角。',
|
||||
themeTags: ['猫咪', '雨夜', '暖灯'],
|
||||
}),
|
||||
);
|
||||
const payload = onExecuteAction.mock.calls[0]![0];
|
||||
expect(JSON.parse(payload.levelsJson)).toEqual([
|
||||
expect.objectContaining({
|
||||
levelId: 'puzzle-level-1',
|
||||
levelName: '雨夜猫街',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
test('selects a history puzzle asset as reference image for the next generation', async () => {
|
||||
test('selects a history puzzle asset as reference image for the selected level', async () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
vi.mocked(puzzleAssetClient.listHistoryAssets).mockResolvedValue([
|
||||
{
|
||||
@@ -512,13 +371,14 @@ describe('PuzzleResultView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('雨夜猫街'));
|
||||
fireEvent.click(screen.getByLabelText('从历史拼图素材库选择'));
|
||||
|
||||
const dialog = await screen.findByRole('dialog', {
|
||||
const picker = await screen.findByRole('dialog', {
|
||||
name: '选择历史拼图素材',
|
||||
});
|
||||
fireEvent.click(
|
||||
await within(dialog).findByRole('button', { name: /账号 user-1/u }),
|
||||
await within(picker).findByRole('button', { name: /账号 user-1/u }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -528,102 +388,12 @@ describe('PuzzleResultView', () => {
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /重新生成画面/u }));
|
||||
|
||||
expect(onExecuteAction).toHaveBeenLastCalledWith({
|
||||
action: 'generate_puzzle_images',
|
||||
levelId: 'puzzle-level-1',
|
||||
promptText: '屋檐下的猫与暖灯街角。',
|
||||
referenceImageSrc: '/generated-puzzle-assets/history/image.png',
|
||||
candidateCount: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('refreshes the current formal image when session cover image changes', async () => {
|
||||
const { rerender } = render(
|
||||
<PuzzleResultView
|
||||
session={createSession()}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole('img', { name: '雨夜猫街' }).getAttribute('src'),
|
||||
).toBe('/puzzle/candidate-1.png');
|
||||
|
||||
rerender(
|
||||
<PuzzleResultView
|
||||
session={createSession({
|
||||
draft: {
|
||||
...createSession().draft!,
|
||||
candidates: [
|
||||
{
|
||||
candidateId: 'candidate-2',
|
||||
imageSrc: '/puzzle/candidate-2.png',
|
||||
assetId: 'asset-2',
|
||||
prompt: '新图',
|
||||
actualPrompt: '新图',
|
||||
sourceType: 'generated',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
selectedCandidateId: 'candidate-2',
|
||||
coverImageSrc: '/puzzle/candidate-2.png',
|
||||
coverAssetId: 'asset-2',
|
||||
},
|
||||
updatedAt: '2026-04-27T11:11:11.000Z',
|
||||
})}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('img', { name: '雨夜猫街' }).getAttribute('src'),
|
||||
).toBe('/puzzle/candidate-2.png');
|
||||
});
|
||||
});
|
||||
|
||||
test('prefers the selected latest candidate image when coverImageSrc lags behind', async () => {
|
||||
render(
|
||||
<PuzzleResultView
|
||||
session={createSession({
|
||||
draft: {
|
||||
...createSession().draft!,
|
||||
candidates: [
|
||||
{
|
||||
candidateId: 'candidate-1',
|
||||
imageSrc: '/puzzle/candidate-1.png',
|
||||
assetId: 'asset-1',
|
||||
prompt: '旧图',
|
||||
actualPrompt: '旧图',
|
||||
sourceType: 'generated',
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
candidateId: 'candidate-2',
|
||||
imageSrc: '/puzzle/candidate-2.png',
|
||||
assetId: 'asset-2',
|
||||
prompt: '新图',
|
||||
actualPrompt: '新图',
|
||||
sourceType: 'generated',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
selectedCandidateId: 'candidate-2',
|
||||
coverImageSrc: '/puzzle/candidate-1.png',
|
||||
coverAssetId: 'asset-1',
|
||||
},
|
||||
})}
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('img', { name: '雨夜猫街' }).getAttribute('src'),
|
||||
).toBe('/puzzle/candidate-2.png');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user