// @vitest-environment jsdom import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ExamplesTab } from '../../src/components/ExamplesTab'; import { fetchSkillExample } from '../../src/providers/registry'; import { exportAsHtml, exportAsPdf, exportAsZip, openSandboxedPreviewInNewTab, } from '../../src/runtime/exports'; import type { SkillSummary } from '../../src/types'; vi.mock('../../src/providers/registry', () => ({ fetchSkillExample: vi.fn(async (id: string) => ({ html: `

${id} preview

`, })), })); vi.mock('../../src/runtime/exports', () => ({ exportAsHtml: vi.fn(), exportAsPdf: vi.fn(), exportAsZip: vi.fn(), openSandboxedPreviewInNewTab: vi.fn(), })); const originalIntersectionObserver = globalThis.IntersectionObserver; class IdleIntersectionObserver { observe() {} disconnect() {} unobserve() {} } beforeEach(() => { globalThis.IntersectionObserver = IdleIntersectionObserver as unknown as typeof IntersectionObserver; }); afterEach(() => { cleanup(); vi.clearAllMocks(); globalThis.IntersectionObserver = originalIntersectionObserver; }); function skill(overrides: Partial & Pick): SkillSummary { return { id: overrides.id, name: overrides.name, description: overrides.description ?? `${overrides.name} example`, triggers: overrides.triggers ?? [], mode: overrides.mode ?? 'prototype', surface: overrides.surface ?? 'web', platform: overrides.platform ?? 'desktop', scenario: overrides.scenario ?? 'general', previewType: overrides.previewType ?? 'html', designSystemRequired: overrides.designSystemRequired ?? false, defaultFor: overrides.defaultFor ?? [], upstream: overrides.upstream ?? null, featured: overrides.featured ?? null, fidelity: overrides.fidelity ?? null, speakerNotes: overrides.speakerNotes ?? null, animations: overrides.animations ?? null, craftRequires: overrides.craftRequires ?? [], hasBody: overrides.hasBody ?? true, examplePrompt: overrides.examplePrompt ?? `Build ${overrides.name}.`, aggregatesExamples: overrides.aggregatesExamples ?? false, }; } const skills: SkillSummary[] = [ skill({ id: 'live-dashboard', name: 'live-dashboard', description: 'Notion style workspace dashboard', examplePrompt: 'Build me a Notion-style team dashboard.', scenario: 'operations', featured: 1, }), skill({ id: 'open-design-landing', name: 'open-design-landing', description: 'Editorial marketing landing page', examplePrompt: 'Produce a world-class single-page editorial landing site.', scenario: 'marketing', featured: 2, }), skill({ id: 'mobile-checkout', name: 'mobile-checkout', description: 'Mobile checkout prototype', mode: 'prototype', platform: 'mobile', scenario: 'product', }), skill({ id: 'brand-deck', name: 'brand-deck', description: 'Slides for brand strategy', mode: 'deck', scenario: 'marketing', }), skill({ id: 'hero-image', name: 'hero-image', description: 'Image generation prompt', mode: 'image', surface: 'image', platform: null, scenario: 'design', }), skill({ id: 'launch-video', name: 'launch-video', description: 'Video generation prompt', mode: 'video', surface: 'video', platform: null, scenario: 'marketing', }), skill({ id: 'brief-template', name: 'brief-template', description: 'Reusable project brief template', examplePrompt: 'Create a reusable project brief from this template.', mode: 'template', surface: 'web', platform: null, scenario: 'operations', }), ]; function renderExamples(onUsePrompt = vi.fn()) { render(); return { onUsePrompt }; } function filterRow(name: string) { return screen.getByRole('tablist', { name }); } describe('ExamplesTab', () => { it('shows the empty skills state when the catalog is unavailable', () => { render(); expect(screen.getByText('No skills available. Is the daemon running?')).toBeTruthy(); }); it('deduplicates duplicate skill ids so each example card renders once (#2889)', () => { const onUsePrompt = vi.fn(); render( , ); expect(screen.getAllByTestId('example-card-xhs-white-editorial')).toHaveLength(1); expect(screen.getByTestId('example-card-open-design-landing')).toBeTruthy(); fireEvent.click(screen.getByTestId('example-use-prompt-xhs-white-editorial')); expect(onUsePrompt).toHaveBeenCalledWith( expect.objectContaining({ id: 'xhs-white-editorial', examplePrompt: 'First prompt', }), ); }); it('filters examples by free-text search and shows an empty match state', () => { renderExamples(); fireEvent.change(screen.getByRole('searchbox', { name: 'Search examples by name' }), { target: { value: 'notion' }, }); expect(screen.getByTestId('example-card-live-dashboard')).toBeTruthy(); expect(screen.queryByTestId('example-card-open-design-landing')).toBeNull(); fireEvent.change(screen.getByRole('searchbox', { name: 'Search examples by name' }), { target: { value: 'no matching example' }, }); expect(screen.getByText('No examples match these filters.')).toBeTruthy(); }); it('narrows by surface, type, and scenario filter pills', () => { renderExamples(); fireEvent.click(within(filterRow('Surface')).getByRole('tab', { name: /Image1/ })); expect(screen.getByTestId('example-card-hero-image')).toBeTruthy(); expect(screen.queryByTestId('example-card-live-dashboard')).toBeNull(); fireEvent.click(within(filterRow('Surface')).getByRole('tab', { name: /All7/ })); fireEvent.click(within(filterRow('Type')).getByRole('tab', { name: /Prototypes · Mobile1/ })); expect(screen.getByTestId('example-card-mobile-checkout')).toBeTruthy(); expect(screen.queryByTestId('example-card-live-dashboard')).toBeNull(); fireEvent.click(within(filterRow('Type')).getByRole('tab', { name: /All7/ })); fireEvent.click(within(filterRow('Scenario')).getByRole('button', { name: /Marketing3/ })); expect(screen.getByTestId('example-card-open-design-landing')).toBeTruthy(); expect(screen.getByTestId('example-card-brand-deck')).toBeTruthy(); expect(screen.getByTestId('example-card-launch-video')).toBeTruthy(); expect(screen.queryByTestId('example-card-live-dashboard')).toBeNull(); }); it('filters Docs & templates examples and uses the selected template prompt', () => { const { onUsePrompt } = renderExamples(); fireEvent.click(within(filterRow('Type')).getByRole('tab', { name: /Docs & templates1/ })); expect(screen.getByTestId('example-card-brief-template')).toBeTruthy(); expect(screen.getByText('Template')).toBeTruthy(); expect(screen.queryByTestId('example-card-live-dashboard')).toBeNull(); fireEvent.click(screen.getByTestId('example-use-prompt-brief-template')); expect(onUsePrompt).toHaveBeenCalledTimes(1); expect(onUsePrompt).toHaveBeenCalledWith( expect.objectContaining({ id: 'brief-template', mode: 'template', examplePrompt: 'Create a reusable project brief from this template.', }), ); }); it('passes the selected example to the Use this prompt callback', () => { const { onUsePrompt } = renderExamples(); fireEvent.click(screen.getByTestId('example-use-prompt-open-design-landing')); expect(onUsePrompt).toHaveBeenCalledTimes(1); expect(onUsePrompt).toHaveBeenCalledWith( expect.objectContaining({ id: 'open-design-landing', examplePrompt: 'Produce a world-class single-page editorial landing site.', }), ); }); it('loads previews on demand and enables the share export menu', async () => { renderExamples(); const card = screen.getByTestId('example-card-live-dashboard'); const shareButton = within(card).getByRole('button', { name: 'Share ▾' }) as HTMLButtonElement; expect(shareButton.disabled).toBe(true); fireEvent.mouseEnter(card); await waitFor(() => { expect(fetchSkillExample).toHaveBeenCalledWith('live-dashboard', 'html'); expect(shareButton.disabled).toBe(false); }); fireEvent.click(shareButton); fireEvent.click(screen.getByRole('menuitem', { name: /Export as PDF/i })); expect(exportAsPdf).toHaveBeenCalledWith( '

live-dashboard preview

', 'live-dashboard', { deck: false }, ); fireEvent.click(shareButton); fireEvent.click(screen.getByRole('menuitem', { name: /Download as \.zip/i })); expect(exportAsZip).toHaveBeenCalledWith( '

live-dashboard preview

', 'live-dashboard', ); fireEvent.click(shareButton); fireEvent.click(screen.getByRole('menuitem', { name: /Export as standalone HTML/i })); expect(exportAsHtml).toHaveBeenCalledWith( '

live-dashboard preview

', 'live-dashboard', ); }); it('opens the full preview modal and exercises its toolbar actions', async () => { renderExamples(); const card = screen.getByTestId('example-card-live-dashboard'); fireEvent.click(within(card).getByRole('button', { name: /Open preview/ })); const dialog = await screen.findByRole('dialog', { name: 'live-dashboard preview' }); await waitFor(() => { expect(screen.getByTitle('live-dashboard Preview')).toBeTruthy(); }); fireEvent.click(within(dialog).getByRole('button', { name: /Fullscreen/i })); const modal = dialog.querySelector('.ds-modal') as HTMLElement; expect(modal.classList.contains('ds-modal-fullscreen')).toBe(true); expect(within(dialog).getByRole('button', { name: /Exit/i })).toBeTruthy(); fireEvent.keyDown(document, { key: 'Escape' }); expect(modal.classList.contains('ds-modal-fullscreen')).toBe(false); expect(screen.getByRole('dialog', { name: 'live-dashboard preview' })).toBeTruthy(); fireEvent.click(within(dialog).getByRole('button', { name: /Fullscreen/i })); expect(modal.classList.contains('ds-modal-fullscreen')).toBe(true); fireEvent.click(within(dialog).getByRole('button', { name: /Exit/i })); expect(modal.classList.contains('ds-modal-fullscreen')).toBe(false); expect(within(dialog).getByRole('button', { name: /Fullscreen/i })).toBeTruthy(); const shareButton = within(dialog).getByRole('button', { name: /Share/i }); fireEvent.click(shareButton); fireEvent.click(within(dialog).getByRole('menuitem', { name: /Export as PDF/i })); expect(exportAsPdf).toHaveBeenCalledWith( '

live-dashboard preview

', 'live-dashboard', { deck: false }, ); fireEvent.click(shareButton); fireEvent.click(within(dialog).getByRole('menuitem', { name: /Download as \.zip/i })); expect(exportAsZip).toHaveBeenCalledWith( '

live-dashboard preview

', 'live-dashboard', ); fireEvent.click(shareButton); fireEvent.click(within(dialog).getByRole('menuitem', { name: /Export as standalone HTML/i })); expect(exportAsHtml).toHaveBeenCalledWith( '

live-dashboard preview

', 'live-dashboard', ); fireEvent.click(shareButton); fireEvent.click(within(dialog).getByRole('menuitem', { name: /Open in new tab/i })); expect(openSandboxedPreviewInNewTab).toHaveBeenCalledWith( '

live-dashboard preview

', 'live-dashboard', { deck: false }, ); fireEvent.click(within(dialog).getByRole('button', { name: 'Close' })); expect(screen.queryByRole('dialog', { name: 'live-dashboard preview' })).toBeNull(); }); });