130 lines
4.9 KiB
TypeScript
130 lines
4.9 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { ImagePromptMuseum } from '@/pages/ImagePromptMuseum';
|
|
import { useImagePromptMuseumStore } from '@/stores/image-prompt-museum';
|
|
import type { PromptMuseumEntry, PromptMuseumPage } from '../../shared/image-prompt-museum';
|
|
|
|
const fetchPromptMuseumPageMock = vi.hoisted(() => vi.fn());
|
|
const fetchPromptMuseumEntryMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock('@/lib/image-prompt-museum', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('@/lib/image-prompt-museum')>();
|
|
return {
|
|
...actual,
|
|
fetchPromptMuseumPage: (...args: unknown[]) => fetchPromptMuseumPageMock(...args),
|
|
fetchPromptMuseumEntry: (...args: unknown[]) => fetchPromptMuseumEntryMock(...args),
|
|
};
|
|
});
|
|
|
|
const pageFixture: PromptMuseumPage = {
|
|
items: [{
|
|
id: 'prompt-one',
|
|
slug: 'editorial-poster',
|
|
title: '编辑感产品海报',
|
|
summary: '一张留白充足的编辑版式海报。',
|
|
thumbnail: {
|
|
url: 'https://cdn.example.com/prompt-one/thumb.webp',
|
|
width: 1200,
|
|
height: 900,
|
|
alt: '编辑感产品海报示例',
|
|
},
|
|
categories: [
|
|
{ id: 'poster', name: '海报', group: 'use_case' },
|
|
{ id: 'editorial', name: '编辑感', group: 'style' },
|
|
],
|
|
model: { id: 'gpt-image-2', name: 'GPT Image 2' },
|
|
language: 'zh-CN',
|
|
attribution: {
|
|
author: { name: '示例作者' },
|
|
source: { name: '示例来源', url: 'https://example.com/source' },
|
|
license: { name: 'CC BY 4.0', attributionText: '示例作者 / 示例来源 / CC BY 4.0' },
|
|
},
|
|
publishedAt: '2026-08-01T00:00:00Z',
|
|
updatedAt: '2026-08-01T00:00:00Z',
|
|
}],
|
|
facets: {
|
|
useCases: [{ id: 'poster', name: '海报', count: 1 }],
|
|
styles: [{ id: 'editorial', name: '编辑感', count: 1 }],
|
|
subjects: [],
|
|
},
|
|
nextCursor: null,
|
|
total: 1,
|
|
};
|
|
|
|
const entryFixture: PromptMuseumEntry = {
|
|
...pageFixture.items[0],
|
|
prompt: 'Create a clean editorial product poster with generous negative space.',
|
|
variables: [],
|
|
images: [pageFixture.items[0].thumbnail],
|
|
requiresReferenceImages: false,
|
|
};
|
|
|
|
describe('Prompt Museum page', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
useImagePromptMuseumStore.getState().clearPendingPrompt();
|
|
fetchPromptMuseumPageMock.mockResolvedValue(pageFixture);
|
|
fetchPromptMuseumEntryMock.mockResolvedValue(entryFixture);
|
|
});
|
|
|
|
it('keeps the inspiration filters compact and below the native title bar', async () => {
|
|
render(
|
|
<MemoryRouter initialEntries={['/image-prompts']}>
|
|
<Routes>
|
|
<Route path="/image-prompts" element={<ImagePromptMuseum />} />
|
|
</Routes>
|
|
</MemoryRouter>,
|
|
);
|
|
|
|
expect(screen.queryByText('提示词博物馆')).not.toBeInTheDocument();
|
|
expect(screen.getByTestId('image-prompt-museum-filters')).toHaveClass('sticky', 'top-0');
|
|
});
|
|
|
|
it('renders server cards, opens attribution details, and sends the exact prompt back to Canvas', async () => {
|
|
render(
|
|
<MemoryRouter initialEntries={['/image-prompts']}>
|
|
<Routes>
|
|
<Route path="/image-prompts" element={<ImagePromptMuseum />} />
|
|
<Route path="/image-canvas" element={<div data-testid="canvas-target">Canvas</div>} />
|
|
</Routes>
|
|
</MemoryRouter>,
|
|
);
|
|
|
|
fireEvent.click(await screen.findByRole('button', { name: '查看 编辑感产品海报' }));
|
|
expect(await screen.findByText('示例作者')).toBeInTheDocument();
|
|
expect(screen.getByText('示例来源')).toBeInTheDocument();
|
|
expect(screen.getByText(entryFixture.prompt)).toBeInTheDocument();
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: '使用此 Prompt' }));
|
|
await waitFor(() => expect(screen.getByTestId('canvas-target')).toBeInTheDocument());
|
|
expect(useImagePromptMuseumStore.getState().pendingPrompt).toEqual({
|
|
promptId: entryFixture.id,
|
|
title: entryFixture.title,
|
|
prompt: entryFixture.prompt,
|
|
});
|
|
});
|
|
|
|
it('loads the next server cursor without bundling another page locally', async () => {
|
|
const secondItem = { ...pageFixture.items[0], id: 'prompt-two', title: '第二条灵感' };
|
|
fetchPromptMuseumPageMock
|
|
.mockResolvedValueOnce({ ...pageFixture, nextCursor: 'cursor-two' })
|
|
.mockResolvedValueOnce({ ...pageFixture, items: [secondItem], nextCursor: null });
|
|
|
|
render(
|
|
<MemoryRouter initialEntries={['/image-prompts']}>
|
|
<Routes>
|
|
<Route path="/image-prompts" element={<ImagePromptMuseum />} />
|
|
</Routes>
|
|
</MemoryRouter>,
|
|
);
|
|
|
|
fireEvent.click(await screen.findByRole('button', { name: '加载更多' }));
|
|
expect(await screen.findByRole('button', { name: '查看 第二条灵感' })).toBeInTheDocument();
|
|
expect(fetchPromptMuseumPageMock).toHaveBeenNthCalledWith(
|
|
2,
|
|
expect.objectContaining({ cursor: 'cursor-two' }),
|
|
);
|
|
});
|
|
});
|