import { render, screen, fireEvent } from '@testing-library/react' import { GlobalStylesPanel } from '../GlobalStylesPanel' import type { GlobalStyles } from '../../types' import { DEFAULT_GLOBAL_STYLES } from '../../types' // --------------------------------------------------------------------------- // Fixtures // --------------------------------------------------------------------------- const baseStyles: GlobalStyles = { backgroundColor: '#f3f4f6', contentWidth: 600, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', theme: 'clean', } function renderPanel(styles: GlobalStyles = baseStyles, onChange = jest.fn()) { return render() } // --------------------------------------------------------------------------- // Renders style inputs // --------------------------------------------------------------------------- describe('GlobalStylesPanel — renders style inputs', () => { it('renders the Background Color label', () => { renderPanel() expect(screen.getByText('Background Color')).toBeInTheDocument() }) it('renders the background color text input with current value', () => { renderPanel() const inputs = screen.getAllByDisplayValue('#f3f4f6') // At least one text input shows the background color value expect(inputs.length).toBeGreaterThan(0) }) it('renders the Content Width input', () => { renderPanel() expect(screen.getByText('Content Width (px)')).toBeInTheDocument() }) it('renders content width number input with current value', () => { renderPanel() expect(screen.getByDisplayValue('600')).toBeInTheDocument() }) it('renders the Font Family label', () => { renderPanel() expect(screen.getByText('Font Family')).toBeInTheDocument() }) it('renders the Themes section heading', () => { renderPanel() expect(screen.getByText('Themes')).toBeInTheDocument() }) it('renders Clean theme preset button', () => { renderPanel() expect(screen.getByText('Clean')).toBeInTheDocument() }) it('renders Minimal theme preset button', () => { renderPanel() expect(screen.getByText('Minimal')).toBeInTheDocument() }) it('renders Bold theme preset button', () => { renderPanel() expect(screen.getByText('Bold')).toBeInTheDocument() }) it('renders theme description text for each preset', () => { renderPanel() expect(screen.getByText('White background with subtle gray accents')).toBeInTheDocument() expect(screen.getByText('Pure white with thin borders')).toBeInTheDocument() expect(screen.getByText('Dark background with vivid accents')).toBeInTheDocument() }) it('renders color picker input for background color', () => { const { container } = renderPanel() const colorInput = container.querySelector('input[type="color"]') as HTMLInputElement expect(colorInput).toBeInTheDocument() expect(colorInput.value).toBe('#f3f4f6') }) it('renders Email Design section heading', () => { renderPanel() expect(screen.getByText('Email Design')).toBeInTheDocument() }) }) // --------------------------------------------------------------------------- // onChange fires with updated styles // --------------------------------------------------------------------------- describe('GlobalStylesPanel — onChange fires with updated styles', () => { it('calls onChange when background color text input changes', () => { const onChange = jest.fn() renderPanel(baseStyles, onChange) const inputs = screen.getAllByDisplayValue('#f3f4f6') // Find the text input (not color picker) — the Input component has type="text" by default const textInput = inputs.find( (el) => (el as HTMLInputElement).type !== 'color' ) as HTMLInputElement fireEvent.change(textInput, { target: { value: '#ffffff' } }) expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenCalledWith( expect.objectContaining({ backgroundColor: '#ffffff' }) ) }) it('calls onChange when color picker changes', () => { const onChange = jest.fn() const { container } = renderPanel(baseStyles, onChange) const colorInput = container.querySelector('input[type="color"]') as HTMLInputElement fireEvent.change(colorInput, { target: { value: '#123456' } }) expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenCalledWith( expect.objectContaining({ backgroundColor: '#123456' }) ) }) it('calls onChange with parsed integer when content width changes', () => { const onChange = jest.fn() renderPanel(baseStyles, onChange) const widthInput = screen.getByDisplayValue('600') fireEvent.change(widthInput, { target: { value: '700' } }) expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenCalledWith( expect.objectContaining({ contentWidth: 700 }) ) }) it('defaults contentWidth to 600 when invalid value entered', () => { const onChange = jest.fn() renderPanel(baseStyles, onChange) const widthInput = screen.getByDisplayValue('600') fireEvent.change(widthInput, { target: { value: 'abc' } }) expect(onChange).toHaveBeenCalledWith( expect.objectContaining({ contentWidth: 600 }) ) }) it('calls onChange with Clean theme preset globalStyles on Clean button click', () => { const onChange = jest.fn() renderPanel(baseStyles, onChange) fireEvent.click(screen.getByText('Clean')) expect(onChange).toHaveBeenCalledTimes(1) const called = onChange.mock.calls[0][0] as GlobalStyles expect(called.theme).toBe('clean') }) it('calls onChange with Minimal theme preset globalStyles on Minimal button click', () => { const onChange = jest.fn() renderPanel(baseStyles, onChange) fireEvent.click(screen.getByText('Minimal')) expect(onChange).toHaveBeenCalledTimes(1) const called = onChange.mock.calls[0][0] as GlobalStyles expect(called.theme).toBe('minimal') expect(called.backgroundColor).toBe('#ffffff') }) it('calls onChange with Bold theme preset globalStyles on Bold button click', () => { const onChange = jest.fn() renderPanel(baseStyles, onChange) fireEvent.click(screen.getByText('Bold')) expect(onChange).toHaveBeenCalledTimes(1) const called = onChange.mock.calls[0][0] as GlobalStyles expect(called.theme).toBe('bold') expect(called.backgroundColor).toBe('#1f2937') }) it('preserves all other globalStyles fields when only backgroundColor changes', () => { const onChange = jest.fn() renderPanel(baseStyles, onChange) const inputs = screen.getAllByDisplayValue('#f3f4f6') const textInput = inputs.find( (el) => (el as HTMLInputElement).type !== 'color' ) as HTMLInputElement fireEvent.change(textInput, { target: { value: '#aaaaaa' } }) const result = onChange.mock.calls[0][0] as GlobalStyles expect(result.contentWidth).toBe(baseStyles.contentWidth) expect(result.fontFamily).toBe(baseStyles.fontFamily) expect(result.theme).toBe(baseStyles.theme) }) }) // --------------------------------------------------------------------------- // Active theme highlighting // --------------------------------------------------------------------------- describe('GlobalStylesPanel — active theme highlight', () => { it('applies active border class to the currently selected theme button', () => { const { container } = renderPanel({ ...baseStyles, theme: 'clean' }) const cleanButton = screen.getByText('Clean').closest('button') as HTMLElement expect(cleanButton.className).toContain('border-primary') }) it('does not apply active border class to non-selected theme buttons', () => { const { container } = renderPanel({ ...baseStyles, theme: 'clean' }) const minimalButton = screen.getByText('Minimal').closest('button') as HTMLElement expect(minimalButton.className).not.toContain('border-primary') }) it('applies active class to Minimal when minimal theme is selected', () => { renderPanel({ ...baseStyles, theme: 'minimal' }) const minimalButton = screen.getByText('Minimal').closest('button') as HTMLElement expect(minimalButton.className).toContain('border-primary') }) }) // --------------------------------------------------------------------------- // DEFAULT_GLOBAL_STYLES as initial values // --------------------------------------------------------------------------- describe('GlobalStylesPanel — DEFAULT_GLOBAL_STYLES compatibility', () => { it('renders correctly with DEFAULT_GLOBAL_STYLES', () => { expect(() => renderPanel(DEFAULT_GLOBAL_STYLES)).not.toThrow() }) it('shows default content width 600', () => { renderPanel(DEFAULT_GLOBAL_STYLES) expect(screen.getByDisplayValue('600')).toBeInTheDocument() }) })