import { render, screen, fireEvent } from '@testing-library/react'
import { BlockPropertyPanel } from '../BlockPropertyPanel'
import type {
  TextBlock,
  DividerBlock,
  CTABlock,
  ImageBlock,
  SpacerBlock,
  SocialBlock,
  HeaderBlock,
  FooterBlock,
  Block,
} from '../../types'

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------

const textBlock: TextBlock = {
  id: 'text-1',
  type: 'text',
  content: '<p>Hello</p>',
  fontSize: 16,
  lineHeight: 1.6,
  textColor: '#1f2937',
}

const dividerBlock: DividerBlock = {
  id: 'divider-1',
  type: 'divider',
  dividerStyle: 'solid',
  color: '#d1d5db',
  thickness: 1,
  width: 100,
}

const ctaBlock: CTABlock = {
  id: 'cta-1',
  type: 'cta',
  text: 'Buy Now',
  url: 'https://buy.example.com',
  buttonColor: '#2563eb',
  textColor: '#ffffff',
  borderRadius: 6,
  paddingH: 24,
  paddingV: 12,
  alignment: 'center',
}

const imageBlock: ImageBlock = {
  id: 'image-1',
  type: 'image',
  url: 'https://img.example.com/pic.jpg',
  alt: 'My image',
  alignment: 'center',
  width: 100,
}

const spacerBlock: SpacerBlock = {
  id: 'spacer-1',
  type: 'spacer',
  height: 32,
}

const socialBlock: SocialBlock = {
  id: 'social-1',
  type: 'social',
  links: [
    { id: 'sl1', platform: 'linkedin', url: 'https://linkedin.com' },
  ],
  iconSize: 24,
  alignment: 'center',
}

const headerBlock: HeaderBlock = {
  id: 'header-1',
  type: 'header',
  companyName: 'PropCo',
  logoUrl: '',
  alignment: 'center',
}

const footerBlock: FooterBlock = {
  id: 'footer-1',
  type: 'footer',
  companyName: 'PropCo',
  address: '1 Main St',
  showUnsubscribe: true,
  unsubscribeUrl: 'https://unsub.example.com',
  alignment: 'center',
}

function renderPanel(block: Block, onChange = jest.fn()) {
  return { ...render(<BlockPropertyPanel block={block} onChange={onChange} />), onChange }
}

// ---------------------------------------------------------------------------
// Renders type label header
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — type label header', () => {
  it('shows "text Settings" heading for text block', () => {
    renderPanel(textBlock)
    expect(screen.getByText('text Settings')).toBeInTheDocument()
  })

  it('shows "divider Settings" heading for divider block', () => {
    renderPanel(dividerBlock)
    expect(screen.getByText('divider Settings')).toBeInTheDocument()
  })

  it('shows "cta Settings" heading for cta block', () => {
    renderPanel(ctaBlock)
    expect(screen.getByText('cta Settings')).toBeInTheDocument()
  })

  it('shows "image Settings" heading for image block', () => {
    renderPanel(imageBlock)
    expect(screen.getByText('image Settings')).toBeInTheDocument()
  })

  it('shows "spacer Settings" heading for spacer block', () => {
    renderPanel(spacerBlock)
    expect(screen.getByText('spacer Settings')).toBeInTheDocument()
  })

  it('shows "social Settings" heading for social block', () => {
    renderPanel(socialBlock)
    expect(screen.getByText('social Settings')).toBeInTheDocument()
  })

  it('shows "header Settings" heading for header block', () => {
    renderPanel(headerBlock)
    expect(screen.getByText('header Settings')).toBeInTheDocument()
  })

  it('shows "footer Settings" heading for footer block', () => {
    renderPanel(footerBlock)
    expect(screen.getByText('footer Settings')).toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// Common spacing / background section (present for all block types)
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — common spacing section', () => {
  it('renders Spacing heading for text block', () => {
    renderPanel(textBlock)
    expect(screen.getByText('Spacing')).toBeInTheDocument()
  })

  it('renders Pad Top input for any block', () => {
    renderPanel(spacerBlock)
    expect(screen.getByText('Pad Top')).toBeInTheDocument()
  })

  it('renders Pad Bottom input for any block', () => {
    renderPanel(ctaBlock)
    expect(screen.getByText('Pad Bottom')).toBeInTheDocument()
  })

  it('renders Background label for any block', () => {
    renderPanel(headerBlock)
    expect(screen.getByText('Background')).toBeInTheDocument()
  })

  it('calls onChange with updated paddingTop when Pad Top input changes', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={textBlock} onChange={onChange} />)
    // Both Pad Top and Pad Bottom show "0"; take the first (Pad Top)
    const padTopInput = screen.getAllByDisplayValue('0')[0]
    fireEvent.change(padTopInput, { target: { value: '12' } })
    expect(onChange).toHaveBeenCalledTimes(1)
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({
        style: expect.objectContaining({ paddingTop: 12 }),
      })
    )
  })

  it('calls onChange with updated paddingBottom when Pad Bottom input changes', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={textBlock} onChange={onChange} />)
    const inputs = screen.getAllByDisplayValue('0')
    // Second "0" input is Pad Bottom
    fireEvent.change(inputs[1], { target: { value: '8' } })
    expect(onChange).toHaveBeenCalledTimes(1)
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({
        style: expect.objectContaining({ paddingBottom: 8 }),
      })
    )
  })

  it('calls onChange with updated backgroundColor from background text input', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={textBlock} onChange={onChange} />)
    const bgInput = screen.getByPlaceholderText('transparent')
    fireEvent.change(bgInput, { target: { value: '#eeeeee' } })
    expect(onChange).toHaveBeenCalledTimes(1)
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({
        style: expect.objectContaining({ backgroundColor: '#eeeeee' }),
      })
    )
  })
})

// ---------------------------------------------------------------------------
// TextSettings — correct property editors rendered
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — TextSettings for text block', () => {
  it('renders Font Size input', () => {
    renderPanel(textBlock)
    expect(screen.getByText('Font Size (px)')).toBeInTheDocument()
  })

  it('renders Line Height input', () => {
    renderPanel(textBlock)
    expect(screen.getByText('Line Height')).toBeInTheDocument()
  })

  it('renders Text Color label', () => {
    renderPanel(textBlock)
    expect(screen.getAllByText('Text Color').length).toBeGreaterThan(0)
  })

  it('renders Font Family label', () => {
    renderPanel(textBlock)
    expect(screen.getAllByText('Font Family').length).toBeGreaterThan(0)
  })

  it('calls onChange with updated fontSize when Font Size input changes', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={textBlock} onChange={onChange} />)
    const fontSizeInput = screen.getByDisplayValue('16')
    fireEvent.change(fontSizeInput, { target: { value: '20' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'text', fontSize: 20 })
    )
  })

  it('calls onChange with updated lineHeight when Line Height input changes', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={textBlock} onChange={onChange} />)
    const lineHeightInput = screen.getByDisplayValue('1.6')
    fireEvent.change(lineHeightInput, { target: { value: '2' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'text', lineHeight: 2 })
    )
  })

  it('calls onChange with updated textColor when text color input changes', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={textBlock} onChange={onChange} />)
    const colorInputs = screen.getAllByDisplayValue('#1f2937')
    const textInput = colorInputs.find(
      (el) => (el as HTMLInputElement).type !== 'color'
    ) as HTMLInputElement
    fireEvent.change(textInput, { target: { value: '#ff0000' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'text', textColor: '#ff0000' })
    )
  })

  it('does not render DividerSettings for text block', () => {
    renderPanel(textBlock)
    expect(screen.queryByText('Thickness (px)')).not.toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// DividerSettings
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — DividerSettings for divider block', () => {
  it('renders Style select', () => {
    renderPanel(dividerBlock)
    expect(screen.getByText('Style')).toBeInTheDocument()
  })

  it('renders Thickness input', () => {
    renderPanel(dividerBlock)
    expect(screen.getByText('Thickness (px)')).toBeInTheDocument()
  })

  it('renders Width input', () => {
    renderPanel(dividerBlock)
    // There are potentially two "Width" labels but divider shows "Width (%)"
    expect(screen.getByText('Width (%)')).toBeInTheDocument()
  })

  it('calls onChange with updated thickness', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={dividerBlock} onChange={onChange} />)
    const thicknessInput = screen.getByDisplayValue('1')
    fireEvent.change(thicknessInput, { target: { value: '3' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'divider', thickness: 3 })
    )
  })

  it('calls onChange with updated width percentage', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={dividerBlock} onChange={onChange} />)
    const widthInput = screen.getByDisplayValue('100')
    fireEvent.change(widthInput, { target: { value: '80' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'divider', width: 80 })
    )
  })

  it('calls onChange with updated color from text input', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={dividerBlock} onChange={onChange} />)
    const colorInputs = screen.getAllByDisplayValue('#d1d5db')
    const textInput = colorInputs.find(
      (el) => (el as HTMLInputElement).type !== 'color'
    ) as HTMLInputElement
    fireEvent.change(textInput, { target: { value: '#000000' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'divider', color: '#000000' })
    )
  })

  it('does not render TextSettings for divider block', () => {
    renderPanel(dividerBlock)
    expect(screen.queryByText('Font Size (px)')).not.toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// ButtonSettings (CTABlock)
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — ButtonSettings for cta block', () => {
  it('renders Button Text input', () => {
    renderPanel(ctaBlock)
    expect(screen.getByText('Button Text')).toBeInTheDocument()
  })

  it('renders URL input', () => {
    renderPanel(ctaBlock)
    expect(screen.getByText('URL')).toBeInTheDocument()
  })

  it('renders Button Color label', () => {
    renderPanel(ctaBlock)
    expect(screen.getByText('Button Color')).toBeInTheDocument()
  })

  it('renders Border Radius input', () => {
    renderPanel(ctaBlock)
    expect(screen.getByText('Border Radius (px)')).toBeInTheDocument()
  })

  it('renders Pad H and Pad V inputs', () => {
    renderPanel(ctaBlock)
    expect(screen.getByText('Pad H')).toBeInTheDocument()
    expect(screen.getByText('Pad V')).toBeInTheDocument()
  })

  it('shows button text value in input', () => {
    renderPanel(ctaBlock)
    expect(screen.getByDisplayValue('Buy Now')).toBeInTheDocument()
  })

  it('calls onChange with updated text when Button Text input changes', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={ctaBlock} onChange={onChange} />)
    const textInput = screen.getByDisplayValue('Buy Now')
    fireEvent.change(textInput, { target: { value: 'Shop Now' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'cta', text: 'Shop Now' })
    )
  })

  it('calls onChange with updated URL when URL input changes', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={ctaBlock} onChange={onChange} />)
    const urlInput = screen.getByDisplayValue('https://buy.example.com')
    fireEvent.change(urlInput, { target: { value: 'https://new.example.com' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'cta', url: 'https://new.example.com' })
    )
  })

  it('calls onChange with updated borderRadius', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={ctaBlock} onChange={onChange} />)
    const radiusInput = screen.getByDisplayValue('6')
    fireEvent.change(radiusInput, { target: { value: '12' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'cta', borderRadius: 12 })
    )
  })
})

// ---------------------------------------------------------------------------
// ImageSettings
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — ImageSettings for image block', () => {
  it('renders Image URL label', () => {
    renderPanel(imageBlock)
    expect(screen.getByText('Image URL')).toBeInTheDocument()
  })

  it('renders Alt Text label', () => {
    renderPanel(imageBlock)
    expect(screen.getByText('Alt Text')).toBeInTheDocument()
  })

  it('renders Caption label', () => {
    renderPanel(imageBlock)
    expect(screen.getByText('Caption')).toBeInTheDocument()
  })

  it('renders Link URL label', () => {
    renderPanel(imageBlock)
    expect(screen.getByText('Link URL (wraps image)')).toBeInTheDocument()
  })

  it('renders Width (%) input', () => {
    renderPanel(imageBlock)
    expect(screen.getByText('Width (%)')).toBeInTheDocument()
  })

  it('shows image URL value in input', () => {
    renderPanel(imageBlock)
    expect(screen.getByDisplayValue('https://img.example.com/pic.jpg')).toBeInTheDocument()
  })

  it('calls onChange with updated URL', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={imageBlock} onChange={onChange} />)
    const urlInput = screen.getByDisplayValue('https://img.example.com/pic.jpg')
    fireEvent.change(urlInput, { target: { value: 'https://new.img.com/x.png' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'image', url: 'https://new.img.com/x.png' })
    )
  })

  it('calls onChange with updated alt text', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={imageBlock} onChange={onChange} />)
    const altInput = screen.getByDisplayValue('My image')
    fireEvent.change(altInput, { target: { value: 'Updated alt' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'image', alt: 'Updated alt' })
    )
  })

  it('calls onChange with updated width percentage', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={imageBlock} onChange={onChange} />)
    const widthInput = screen.getByDisplayValue('100')
    fireEvent.change(widthInput, { target: { value: '75' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'image', width: 75 })
    )
  })
})

// ---------------------------------------------------------------------------
// SpacerSettings
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — SpacerSettings for spacer block', () => {
  it('renders Height (px) label', () => {
    renderPanel(spacerBlock)
    expect(screen.getByText('Height (px)')).toBeInTheDocument()
  })

  it('shows spacer height value in input', () => {
    renderPanel(spacerBlock)
    expect(screen.getByDisplayValue('32')).toBeInTheDocument()
  })

  it('calls onChange with updated height', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={spacerBlock} onChange={onChange} />)
    const heightInput = screen.getByDisplayValue('32')
    fireEvent.change(heightInput, { target: { value: '64' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'spacer', height: 64 })
    )
  })

  it('does not render text-specific inputs for spacer', () => {
    renderPanel(spacerBlock)
    expect(screen.queryByText('Font Size (px)')).not.toBeInTheDocument()
    expect(screen.queryByText('Button Text')).not.toBeInTheDocument()
  })
})

// ---------------------------------------------------------------------------
// SocialSettings
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — SocialSettings for social block', () => {
  it('renders Alignment label', () => {
    renderPanel(socialBlock)
    expect(screen.getByText('Alignment')).toBeInTheDocument()
  })

  it('renders Icon Size label', () => {
    renderPanel(socialBlock)
    expect(screen.getByText('Icon Size')).toBeInTheDocument()
  })

  it('renders Links label', () => {
    renderPanel(socialBlock)
    expect(screen.getByText('Links')).toBeInTheDocument()
  })

  it('renders Add Link button', () => {
    renderPanel(socialBlock)
    expect(screen.getByText('Add Link')).toBeInTheDocument()
  })

  it('calls onChange when Add Link is clicked', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={socialBlock} onChange={onChange} />)
    fireEvent.click(screen.getByText('Add Link'))
    expect(onChange).toHaveBeenCalledTimes(1)
    const updated = onChange.mock.calls[0][0] as SocialBlock
    expect(updated.links).toHaveLength(2)
  })

  it('calls onChange when remove link button is clicked', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={socialBlock} onChange={onChange} />)
    // The Trash2 icon button — there is one link so one remove button
    const removeButton = screen.getAllByRole('button').find(
      (btn) => btn.querySelector('svg') !== null && !btn.textContent?.includes('Add')
    ) as HTMLElement
    fireEvent.click(removeButton)
    expect(onChange).toHaveBeenCalledTimes(1)
    const updated = onChange.mock.calls[0][0] as SocialBlock
    expect(updated.links).toHaveLength(0)
  })

  it('shows existing link URL in input', () => {
    renderPanel(socialBlock)
    expect(screen.getByDisplayValue('https://linkedin.com')).toBeInTheDocument()
  })

  it('calls onChange when link URL input changes', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={socialBlock} onChange={onChange} />)
    const urlInput = screen.getByDisplayValue('https://linkedin.com')
    fireEvent.change(urlInput, { target: { value: 'https://new-linkedin.com' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({
        type: 'social',
        links: expect.arrayContaining([
          expect.objectContaining({ url: 'https://new-linkedin.com' }),
        ]),
      })
    )
  })

  it('calls onChange with updated iconSize', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={socialBlock} onChange={onChange} />)
    const iconSizeInput = screen.getByDisplayValue('24')
    fireEvent.change(iconSizeInput, { target: { value: '32' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'social', iconSize: 32 })
    )
  })
})

// ---------------------------------------------------------------------------
// HeaderSettings
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — HeaderSettings for header block', () => {
  it('renders Company Name label', () => {
    renderPanel(headerBlock)
    expect(screen.getByText('Company Name')).toBeInTheDocument()
  })

  it('renders Logo URL label', () => {
    renderPanel(headerBlock)
    expect(screen.getByText('Logo URL')).toBeInTheDocument()
  })

  it('shows company name value in input', () => {
    renderPanel(headerBlock)
    expect(screen.getByDisplayValue('PropCo')).toBeInTheDocument()
  })

  it('calls onChange with updated companyName', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={headerBlock} onChange={onChange} />)
    const nameInput = screen.getByDisplayValue('PropCo')
    fireEvent.change(nameInput, { target: { value: 'NewCo' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'header', companyName: 'NewCo' })
    )
  })

  it('calls onChange with updated logoUrl', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={headerBlock} onChange={onChange} />)
    // logoUrl input has placeholder "https://..."
    const logoInputs = screen.getAllByPlaceholderText('https://...')
    fireEvent.change(logoInputs[0], { target: { value: 'https://cdn.example.com/logo.png' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'header', logoUrl: 'https://cdn.example.com/logo.png' })
    )
  })
})

// ---------------------------------------------------------------------------
// FooterSettings
// ---------------------------------------------------------------------------

describe('BlockPropertyPanel — FooterSettings for footer block', () => {
  it('renders Company Name label', () => {
    renderPanel(footerBlock)
    // The footer has its own Company Name, header might share the name
    const labels = screen.getAllByText('Company Name')
    expect(labels.length).toBeGreaterThan(0)
  })

  it('renders Address label', () => {
    renderPanel(footerBlock)
    expect(screen.getByText('Address')).toBeInTheDocument()
  })

  it('shows address value in input', () => {
    renderPanel(footerBlock)
    expect(screen.getByDisplayValue('1 Main St')).toBeInTheDocument()
  })

  it('renders "Show unsubscribe link" checkbox', () => {
    renderPanel(footerBlock)
    expect(screen.getByText('Show unsubscribe link')).toBeInTheDocument()
  })

  it('renders Unsubscribe URL input when showUnsubscribe is true', () => {
    renderPanel(footerBlock)
    expect(screen.getByText('Unsubscribe URL')).toBeInTheDocument()
  })

  it('does not render Unsubscribe URL input when showUnsubscribe is false', () => {
    const block: FooterBlock = { ...footerBlock, showUnsubscribe: false }
    renderPanel(block)
    expect(screen.queryByText('Unsubscribe URL')).not.toBeInTheDocument()
  })

  it('calls onChange with updated companyName', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={footerBlock} onChange={onChange} />)
    const nameInput = screen.getByDisplayValue('PropCo')
    fireEvent.change(nameInput, { target: { value: 'Updated Co' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'footer', companyName: 'Updated Co' })
    )
  })

  it('calls onChange when showUnsubscribe checkbox is toggled off', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={footerBlock} onChange={onChange} />)
    const checkbox = screen.getByRole('checkbox')
    fireEvent.click(checkbox)
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'footer', showUnsubscribe: false })
    )
  })

  it('calls onChange when showUnsubscribe checkbox is toggled on from false state', () => {
    const onChange = jest.fn()
    const block: FooterBlock = { ...footerBlock, showUnsubscribe: false }
    render(<BlockPropertyPanel block={block} onChange={onChange} />)
    const checkbox = screen.getByRole('checkbox')
    fireEvent.click(checkbox)
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'footer', showUnsubscribe: true })
    )
  })

  it('calls onChange with updated address', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={footerBlock} onChange={onChange} />)
    const addressInput = screen.getByDisplayValue('1 Main St')
    fireEvent.change(addressInput, { target: { value: '2 New Rd' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'footer', address: '2 New Rd' })
    )
  })

  it('calls onChange with updated unsubscribeUrl', () => {
    const onChange = jest.fn()
    render(<BlockPropertyPanel block={footerBlock} onChange={onChange} />)
    const unsubInput = screen.getByDisplayValue('https://unsub.example.com')
    fireEvent.change(unsubInput, { target: { value: 'https://new-unsub.com' } })
    expect(onChange).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'footer', unsubscribeUrl: 'https://new-unsub.com' })
    )
  })
})
