import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import Form from '@/components/ui/Form.vue'
const samplePayload = {
form_id: 'form_abc',
title: 'Preferences',
description: 'Tell us what you need.',
fields: [
{ name: 'email', label: 'Email', type: 'email', required: true },
{ name: 'budget', label: 'Budget', type: 'number', required: true, min: 0, max: 1000000 },
{ name: 'notes', label: 'Notes', type: 'textarea' },
],
submit_label: 'Send',
}
function mountWithProvide(payload = samplePayload, sendFn = vi.fn()) {
return mount(Form, {
props: { data: payload },
global: {
provide: {
wsSend: sendFn,
},
mocks: {
// localStorage is not available in jsdom by default; provide stubs.
localStorage: {
getItem: vi.fn(() => '[]'),
setItem: vi.fn(),
},
},
},
})
}
vi.mock('@/stores/sessions.js', () => ({
useSessionsStore: () => ({
updatePreview: vi.fn(),
updateName: vi.fn(),
sessions: [],
}),
}))
vi.mock('@/api/index.js', () => ({
getSession: vi.fn(),
getFeedback: vi.fn().mockResolvedValue({ feedback: [] }),
setFeedback: vi.fn(),
generateSessionName: vi.fn(),
}))
describe('Form', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.stubGlobal('localStorage', {
getItem: vi.fn(() => '[]'),
setItem: vi.fn(),
})
})
it('renders title, description and fields', () => {
const wrapper = mountWithProvide()
expect(wrapper.text()).toContain('Preferences')
expect(wrapper.text()).toContain('Tell us what you need.')
expect(wrapper.find('input[type="email"]').exists()).toBe(true)
expect(wrapper.find('input[type="number"]').exists()).toBe(true)
expect(wrapper.find('textarea').exists()).toBe(true)
})
it('submit button is disabled while form is invalid', () => {
const wrapper = mountWithProvide()
const btn = wrapper.find('button[type="submit"]')
expect(btn.attributes('disabled')).toBeDefined()
})
it('enables submit button when required fields are valid', async () => {
const wrapper = mountWithProvide()
const email = wrapper.find('input[type="email"]')
const budget = wrapper.find('input[type="number"]')
await email.setValue('test@example.com')
await email.trigger('input')
await budget.setValue('50000')
await budget.trigger('input')
await flushPromises()
const btn = wrapper.find('button[type="submit"]')
expect(btn.attributes('disabled')).toBeUndefined()
})
it('shows validation error for invalid email in real time', async () => {
const wrapper = mountWithProvide()
const email = wrapper.find('input[type="email"]')
await email.setValue('bad-email')
await email.trigger('input')
await email.trigger('blur')
await flushPromises()
expect(wrapper.text()).toContain('valid email')
})
it('sends form_submit payload and switches to summary on submit', async () => {
const sendFn = vi.fn()
const wrapper = mountWithProvide(samplePayload, sendFn)
const email = wrapper.find('input[type="email"]')
const budget = wrapper.find('input[type="number"]')
await email.setValue('test@example.com')
await email.trigger('input')
await budget.setValue('50000')
await budget.trigger('input')
await flushPromises()
await wrapper.find('form').trigger('submit')
await flushPromises()
expect(sendFn).toHaveBeenCalledTimes(1)
const payload = sendFn.mock.calls[0][0]
expect(payload.type).toBe('form_submit')
expect(payload.form_id).toBe('form_abc')
expect(payload.values.email).toBe('test@example.com')
expect(payload.values.budget).toBe(50000)
expect(wrapper.text()).toContain('Submitted')
expect(wrapper.text()).toContain('test@example.com')
expect(wrapper.text()).toContain('50000')
})
it('prevents double submission', async () => {
const sendFn = vi.fn()
const wrapper = mountWithProvide(samplePayload, sendFn)
const email = wrapper.find('input[type="email"]')
const budget = wrapper.find('input[type="number"]')
await email.setValue('test@example.com')
await email.trigger('input')
await budget.setValue('50000')
await budget.trigger('input')
await flushPromises()
await wrapper.find('form').trigger('submit')
await flushPromises()
// After submit the form DOM is replaced by the read-only summary, so a
// second submit attempt is impossible.
expect(wrapper.find('form').exists()).toBe(false)
expect(sendFn).toHaveBeenCalledTimes(1)
})
})