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(); 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( } /> , ); 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( } /> Canvas} /> , ); 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( } /> , ); fireEvent.click(await screen.findByRole('button', { name: '加载更多' })); expect(await screen.findByRole('button', { name: '查看 第二条灵感' })).toBeInTheDocument(); expect(fetchPromptMuseumPageMock).toHaveBeenNthCalledWith( 2, expect.objectContaining({ cursor: 'cursor-two' }), ); }); });