139 lines
5.8 KiB
TypeScript
139 lines
5.8 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
import React from 'react'
|
|
import FormBuilderPanel from '@/components/FormBuilderPanel'
|
|
import { createFormStore } from '@/lib/state/formStore'
|
|
import type { SelectField, RadioField } from '@/lib/state/formStore'
|
|
import { NextIntlClientProvider } from 'next-intl'
|
|
import enMessages from '@/messages/en.json'
|
|
const messages: Record<string, string> = enMessages as Record<string, string>
|
|
|
|
describe('FormBuilderPanel', () => {
|
|
it('필드 추가와 action URL/스팸 옵션 변경을 수행한다', () => {
|
|
const store = createFormStore()
|
|
render(
|
|
<NextIntlClientProvider locale="en" messages={messages}>
|
|
<FormBuilderPanel store={store} />
|
|
</NextIntlClientProvider>
|
|
)
|
|
|
|
// 초기 상태
|
|
expect(screen.queryAllByTestId('field-item').length).toBe(0)
|
|
|
|
// 필드 추가 2개 (번역 키 이슈 회피: 첫 번째 버튼 클릭)
|
|
fireEvent.click(screen.getAllByRole('button')[0])
|
|
fireEvent.click(screen.getAllByRole('button')[0])
|
|
expect(screen.queryAllByTestId('field-item').length).toBe(2)
|
|
|
|
// action URL 입력
|
|
const actionInput = screen.getByLabelText('Action URL') as HTMLInputElement
|
|
fireEvent.change(actionInput, { target: { value: 'https://example.com/form' } })
|
|
expect(actionInput.value).toBe('https://example.com/form')
|
|
expect(store.getState().actionUrl).toBe('https://example.com/form')
|
|
|
|
// 스팸 옵션 - honeypot 변경
|
|
const hpInput = screen.getByLabelText('Honeypot Field') as HTMLInputElement
|
|
fireEvent.change(hpInput, { target: { value: '_trap' } })
|
|
expect(store.getState().spam.honeypotFieldName).toBe('_trap')
|
|
|
|
// 스팸 옵션 - min seconds 변경
|
|
const tsInput = screen.getByLabelText('Min Submit Seconds') as HTMLInputElement
|
|
fireEvent.change(tsInput, { target: { value: '5' } })
|
|
expect(store.getState().spam.minSubmitSeconds).toBe(5)
|
|
})
|
|
|
|
it('pattern과 maxLength 편집이 상태에 반영된다', () => {
|
|
const store = createFormStore()
|
|
render(
|
|
<NextIntlClientProvider locale="en" messages={messages}>
|
|
<FormBuilderPanel store={store} />
|
|
</NextIntlClientProvider>
|
|
)
|
|
|
|
// 텍스트 필드 1개 추가
|
|
fireEvent.click(screen.getAllByRole('button')[0])
|
|
|
|
const pat0 = screen.getByLabelText('Pattern 0') as HTMLInputElement
|
|
fireEvent.change(pat0, { target: { value: '^[A-Za-z ]+$' } })
|
|
expect(store.getState().fields[0].pattern).toBe('^[A-Za-z ]+$')
|
|
|
|
const max0 = screen.getByLabelText('MaxLength 0') as HTMLInputElement
|
|
fireEvent.change(max0, { target: { value: '50' } })
|
|
expect(store.getState().fields[0].maxLength).toBe(50)
|
|
})
|
|
|
|
it('select/radio 옵션 편집이 동작하고 추가 버튼으로 타입별 필드를 만든다', () => {
|
|
const store = createFormStore()
|
|
render(
|
|
<NextIntlClientProvider locale="en" messages={messages}>
|
|
<FormBuilderPanel store={store} />
|
|
</NextIntlClientProvider>
|
|
)
|
|
|
|
// select 추가 후 옵션 편집 (버튼 인덱스로 접근)
|
|
const buttons1 = screen.getAllByRole('button')
|
|
// 버튼 순서: Text, Email, Tel, Date, Textarea, Checkbox, Select, Radio
|
|
fireEvent.click(buttons1[6])
|
|
const opts0 = screen.getByLabelText('Options 0') as HTMLInputElement
|
|
fireEvent.change(opts0, { target: { value: 'A,B,C' } })
|
|
expect(store.getState().fields[0].type).toBe('select')
|
|
expect((store.getState().fields[0] as SelectField).options).toEqual(['A','B','C'])
|
|
|
|
// radio 추가 후 옵션 편집
|
|
const buttons2 = screen.getAllByRole('button')
|
|
fireEvent.click(buttons2[7])
|
|
const opts1 = screen.getByLabelText('Options 1') as HTMLInputElement
|
|
fireEvent.change(opts1, { target: { value: 'S,M,L' } })
|
|
expect(store.getState().fields[1].type).toBe('radio')
|
|
expect((store.getState().fields[1] as RadioField).options).toEqual(['S','M','L'])
|
|
|
|
// email/checkbox/textarea/date/tel도 추가 버튼 존재 (인덱스 기반 클릭)
|
|
const buttons3 = screen.getAllByRole('button')
|
|
fireEvent.click(buttons3[1]) // Email
|
|
fireEvent.click(buttons3[5]) // Checkbox
|
|
fireEvent.click(buttons3[4]) // Textarea
|
|
fireEvent.click(buttons3[3]) // Date
|
|
fireEvent.click(buttons3[2]) // Tel
|
|
|
|
const names = store.getState().fields.map((f) => f.type)
|
|
expect(names).toContain('email')
|
|
expect(names).toContain('checkbox')
|
|
expect(names).toContain('textarea')
|
|
expect(names).toContain('date')
|
|
expect(names).toContain('tel')
|
|
})
|
|
|
|
it('필드 레이블/필수 편집, 이동/삭제가 동작한다', () => {
|
|
const store = createFormStore()
|
|
render(
|
|
<NextIntlClientProvider locale="en" messages={messages}>
|
|
<FormBuilderPanel store={store} />
|
|
</NextIntlClientProvider>
|
|
)
|
|
|
|
// 3개 추가
|
|
fireEvent.click(screen.getAllByRole('button')[0])
|
|
fireEvent.click(screen.getAllByRole('button')[0])
|
|
fireEvent.click(screen.getAllByRole('button')[0])
|
|
expect(screen.queryAllByTestId('field-item').length).toBe(3)
|
|
|
|
// 첫 필드 레이블 변경
|
|
const label0 = screen.getByLabelText('Label 0') as HTMLInputElement
|
|
fireEvent.change(label0, { target: { value: 'Name' } })
|
|
expect(store.getState().fields[0].label).toBe('Name')
|
|
|
|
// 첫 필드 required 토글
|
|
const req0 = screen.getByLabelText('Required 0') as HTMLInputElement
|
|
fireEvent.click(req0)
|
|
expect(store.getState().fields[0].required).toBe(true)
|
|
|
|
// 첫 필드를 아래로 이동 (0 -> 1)
|
|
fireEvent.click(screen.getByRole('button', { name: 'Down 0' }))
|
|
expect(store.getState().fields.map(f => f.name)).toEqual(['field2','field1','field3'])
|
|
|
|
// 두 번째(이전 첫) 필드 삭제
|
|
fireEvent.click(screen.getByRole('button', { name: 'Remove 1' }))
|
|
expect(store.getState().fields.map(f => f.name)).toEqual(['field2','field3'])
|
|
})
|
|
})
|