import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { Code, CodeGroup, Embed, Pre } from '../code' vi.mock('@/utils/clipboard', () => ({ writeTextToClipboard: vi.fn().mockResolvedValue(undefined), })) describe('code.tsx components', () => { beforeEach(() => { vi.clearAllMocks() vi.spyOn(console, 'error').mockImplementation(() => {}) vi.useFakeTimers({ shouldAdvanceTime: true }) // jsdom does not implement scrollBy; mock it to prevent stderr noise window.scrollBy = vi.fn() }) afterEach(() => { vi.runOnlyPendingTimers() vi.useRealTimers() vi.restoreAllMocks() }) describe('Code', () => { it('should render children as a code element', () => { render(const x = 1) const codeElement = screen.getByText('const x = 1') expect(codeElement.tagName).toBe('CODE') }) it('should pass through additional props', () => { render(snippet) const codeElement = screen.getByTestId('custom-code') expect(codeElement).toHaveClass('custom-class') }) it('should render with complex children', () => { render( part1 part2 , ) expect(screen.getByText('part1')).toBeInTheDocument() expect(screen.getByText('part2')).toBeInTheDocument() }) }) describe('Embed', () => { it('should render value prop as a span element', () => { render(ignored children) const span = screen.getByText('embedded content') expect(span.tagName).toBe('SPAN') }) it('should pass through additional props', () => { render(children) const embed = screen.getByTestId('embed-test') expect(embed).toHaveClass('embed-class') }) it('should render only value, not children', () => { render(hidden children) expect(screen.getByText('shown')).toBeInTheDocument() expect(screen.queryByText('hidden children')).not.toBeInTheDocument() }) }) describe('CodeGroup', () => { describe('with string targetCode', () => { it('should render code from targetCode string', () => { render(
fallback
, ) expect(screen.getByText('const hello = \'world\'')).toBeInTheDocument() }) }) describe('with array targetCode', () => { it('should render single code example without tabs', () => { const examples = [{ code: 'single example' }] render(
fallback
, ) expect(screen.getByText('single example')).toBeInTheDocument() }) it('should render multiple code examples with tabs', () => { const examples = [ { title: 'JavaScript', code: 'console.log("js")' }, { title: 'Python', code: 'print("py")' }, ] render(
fallback
, ) expect(screen.getByRole('tab', { name: 'JavaScript' })).toBeInTheDocument() expect(screen.getByRole('tab', { name: 'Python' })).toBeInTheDocument() }) it('should show first tab content by default', () => { const examples = [ { title: 'Tab1', code: 'first content' }, { title: 'Tab2', code: 'second content' }, ] render(
fallback
, ) expect(screen.getByText('first content')).toBeInTheDocument() }) it('should switch tabs on click', async () => { const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }) const examples = [ { title: 'Tab1', code: 'first content' }, { title: 'Tab2', code: 'second content' }, ] render(
fallback
, ) await act(async () => { vi.runAllTimers() }) const tab2 = screen.getByRole('tab', { name: 'Tab2' }) await act(async () => { await user.click(tab2) }) await waitFor(() => { expect(screen.getByText('second content')).toBeInTheDocument() }) }) it('should use "Code" as default title when title not provided', () => { const examples = [ { code: 'example 1' }, { code: 'example 2' }, ] render(
fallback
, ) const codeTabs = screen.getAllByRole('tab', { name: 'Code' }) expect(codeTabs).toHaveLength(2) }) }) describe('with title prop', () => { it('should render title in an h3 heading', () => { render(
fallback
, ) const h3 = screen.getByRole('heading', { level: 3 }) expect(h3).toHaveTextContent('API Example') }) }) describe('with tag and label props', () => { it('should render tag in code panel header', () => { render(
fallback
, ) expect(screen.getByText('GET')).toBeInTheDocument() }) it('should render label in code panel header', () => { render(
fallback
, ) expect(screen.getByText('/api/users')).toBeInTheDocument() }) it('should render both tag and label together', () => { render(
fallback
, ) expect(screen.getByText('POST')).toBeInTheDocument() expect(screen.getByText('/api/create')).toBeInTheDocument() }) }) describe('CopyButton functionality', () => { it('should show "Copy" text initially', () => { render(
fallback
, ) expect(screen.getByText('Copy')).toBeInTheDocument() }) it('should show "Copied!" after clicking copy button', async () => { const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }) const { writeTextToClipboard } = await import('@/utils/clipboard') render(
fallback
, ) await act(async () => { vi.runAllTimers() }) const copyButton = screen.getByRole('button') await act(async () => { await user.click(copyButton) }) await waitFor(() => { expect(writeTextToClipboard).toHaveBeenCalledWith('code to copy') }) expect(screen.getByText('Copied!')).toBeInTheDocument() }) it('should reset copy state after timeout', async () => { const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }) render(
fallback
, ) await act(async () => { vi.runAllTimers() }) const copyButton = screen.getByRole('button') await act(async () => { await user.click(copyButton) }) await waitFor(() => { expect(screen.getByText('Copied!')).toBeInTheDocument() }) await act(async () => { vi.advanceTimersByTime(1500) }) await waitFor(() => { expect(screen.getByText('Copy')).toBeInTheDocument() }) }) }) describe('without targetCode (using children)', () => { it('should render children when no targetCode provided', () => { render(
child code content
, ) expect(screen.getByText('child code content')).toBeInTheDocument() }) }) }) describe('Pre', () => { it('should wrap children in CodeGroup when outside CodeGroup context', () => { render(
          
code
, ) expect(screen.getByText('Pre Title')).toBeInTheDocument() }) it('should return children directly when inside CodeGroup context', () => { render(
            inner code
          
, ) expect(screen.getByText('outer code')).toBeInTheDocument() }) }) describe('CodePanelHeader (via CodeGroup)', () => { it('should render when tag is provided', () => { render(
fallback
, ) expect(screen.getByText('GET')).toBeInTheDocument() }) it('should render when label is provided', () => { render(
fallback
, ) expect(screen.getByText('/api/endpoint')).toBeInTheDocument() }) }) describe('CodeGroupHeader (via CodeGroup with multiple tabs)', () => { it('should render tab list for multiple examples', () => { const examples = [ { title: 'cURL', code: 'curl example' }, { title: 'Node.js', code: 'node example' }, ] render(
fallback
, ) expect(screen.getByRole('tablist')).toBeInTheDocument() }) }) describe('CodePanel (via CodeGroup)', () => { it('should render code in a pre element', () => { render(
fallback
, ) const preElement = screen.getByText('pre content').closest('pre') expect(preElement).toBeInTheDocument() }) }) describe('ClipboardIcon (via CopyButton)', () => { it('should render clipboard SVG icon in copy button', () => { render(
fallback
, ) const copyButton = screen.getByRole('button') const svg = copyButton.querySelector('svg') expect(svg).toBeInTheDocument() expect(svg).toHaveAttribute('viewBox', '0 0 20 20') }) }) describe('Edge Cases', () => { it('should handle empty string targetCode', () => { render(
fallback
, ) expect(screen.getByRole('button')).toBeInTheDocument() }) it('should handle targetCode with special characters', () => { const specialCode = '
&
' render(
fallback
, ) expect(screen.getByText(specialCode)).toBeInTheDocument() }) it('should handle multiline targetCode', () => { const multilineCode = `line1 line2 line3` render(
fallback
, ) expect(screen.getByText(/line1/)).toBeInTheDocument() expect(screen.getByText(/line2/)).toBeInTheDocument() expect(screen.getByText(/line3/)).toBeInTheDocument() }) it('should handle examples with tag property', () => { const examples = [ { title: 'Example', tag: 'v1', code: 'versioned code' }, ] render(
fallback
, ) expect(screen.getByText('versioned code')).toBeInTheDocument() }) }) })