import { describe, it, expect, vi } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { createRouter, createMemoryHistory } from 'vue-router' import EventCreateView from '../EventCreateView.vue' import { api } from '@/api/client' vi.mock('@/api/client', () => ({ api: { POST: vi.fn(), }, })) vi.mock('@/composables/useEventStorage', () => ({ useEventStorage: vi.fn(() => ({ saveCreatedEvent: vi.fn(), getStoredEvents: vi.fn(() => []), getOrganizerToken: vi.fn(), saveRsvp: vi.fn(), getRsvp: vi.fn(), })), })) function createTestRouter() { return createRouter({ history: createMemoryHistory(), routes: [ { path: '/', name: 'home', component: { template: '
' } }, { path: '/create', name: 'create-event', component: EventCreateView }, { path: '/events/:eventToken', name: 'event', component: { template: '
' } }, ], }) } describe('EventCreateView', () => { it('renders all form fields', async () => { const router = createTestRouter() await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) expect(wrapper.find('#title').exists()).toBe(true) expect(wrapper.find('#description').exists()).toBe(true) expect(wrapper.find('#dateTime').exists()).toBe(true) expect(wrapper.find('#location').exists()).toBe(true) }) it('has required attribute on required fields', async () => { const router = createTestRouter() await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) expect(wrapper.find('#title').attributes('required')).toBeDefined() expect(wrapper.find('#dateTime').attributes('required')).toBeDefined() }) it('does not have required attribute on optional fields', async () => { const router = createTestRouter() await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) expect(wrapper.find('#description').attributes('required')).toBeUndefined() expect(wrapper.find('#location').attributes('required')).toBeUndefined() }) it('has a submit button', async () => { const router = createTestRouter() await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) const button = wrapper.find('button[type="submit"]') expect(button.exists()).toBe(true) expect(button.text()).toBe('Create Event') }) it('shows server error when network request fails', async () => { vi.mocked(api.POST).mockRejectedValueOnce(new TypeError('Failed to fetch')) const router = createTestRouter() await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) // Fill required fields await wrapper.find('#title').setValue('My Event') await wrapper.find('#dateTime').setValue('2026-12-25T18:00') await wrapper.find('form').trigger('submit') await flushPromises() const alerts = wrapper.findAll('[role="alert"]').map((el) => el.text()).filter((t) => t.length > 0) expect(alerts).toContain('Could not reach the server. Please try again.') // Submit button should not remain disabled expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBeUndefined() }) it('clears field error when user types into that field', async () => { const router = createTestRouter() await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) // Submit empty form to trigger validation errors await wrapper.find('form').trigger('submit') const errorsBefore = wrapper.findAll('[role="alert"]').map((el) => el.text()).filter((t) => t.length > 0) expect(errorsBefore.length).toBeGreaterThanOrEqual(2) // Type into title field await wrapper.find('#title').setValue('My Event') // Title error should be cleared (span removed from DOM), but other errors should remain const titleError = wrapper.find('#title').element.closest('.form-group')!.querySelector('[role="alert"]') expect(titleError).toBeNull() const dateTimeError = wrapper.find('#dateTime').element.closest('.form-group')!.querySelector('[role="alert"]')! expect(dateTimeError.textContent).not.toBe('') }) it('shows validation errors when submitting empty form', async () => { const router = createTestRouter() await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) await wrapper.find('form').trigger('submit') const errorElements = wrapper.findAll('[role="alert"]') const errorTexts = errorElements.map((el) => el.text()).filter((t) => t.length > 0) expect(errorTexts.length).toBeGreaterThanOrEqual(2) }) it('submits successfully, saves to storage, and navigates to event page', async () => { const mockSave = vi.fn() vi.mocked(vi.importActual) const { useEventStorage } = await import('@/composables/useEventStorage') vi.mocked(useEventStorage).mockReturnValue({ saveCreatedEvent: mockSave, getStoredEvents: vi.fn(() => []), getOrganizerToken: vi.fn(), saveRsvp: vi.fn(), getRsvp: vi.fn(), removeRsvp: vi.fn(), saveWatch: vi.fn(), isStored: vi.fn(() => false), removeEvent: vi.fn(), }) vi.mocked(api.POST).mockResolvedValueOnce({ data: { eventToken: 'abc-123', organizerToken: 'org-456', title: 'Birthday Party', dateTime: '2026-12-25T18:00:00+01:00', timezone: 'Europe/Berlin', }, error: undefined, response: new Response(), }) const router = createTestRouter() const pushSpy = vi.spyOn(router, 'push') await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) await wrapper.find('#title').setValue('Birthday Party') await wrapper.find('#description').setValue('Come celebrate!') await wrapper.find('#dateTime').setValue('2026-12-25T18:00') await wrapper.find('#location').setValue('Berlin') await wrapper.find('form').trigger('submit') await flushPromises() expect(vi.mocked(api.POST)).toHaveBeenCalledWith('/events', { body: expect.objectContaining({ title: 'Birthday Party', description: 'Come celebrate!', location: 'Berlin', }), }) expect(mockSave).toHaveBeenCalledWith({ eventToken: 'abc-123', organizerToken: 'org-456', title: 'Birthday Party', dateTime: '2026-12-25T18:00:00+01:00', }) expect(pushSpy).toHaveBeenCalledWith({ name: 'event', params: { eventToken: 'abc-123' }, }) }) it('displays server-side field errors on the correct fields', async () => { vi.mocked(api.POST).mockResolvedValueOnce({ data: undefined, error: { fieldErrors: [{ field: 'title', message: 'Title already taken' }], }, response: new Response(), } as ReturnType extends Promise ? R : never) const router = createTestRouter() await router.push('/create') await router.isReady() const wrapper = mount(EventCreateView, { global: { plugins: [router] }, }) await wrapper.find('#title').setValue('Duplicate Event') await wrapper.find('#dateTime').setValue('2026-12-25T18:00') await wrapper.find('form').trigger('submit') await flushPromises() const titleError = wrapper.find('#title-error') expect(titleError.exists()).toBe(true) expect(titleError.text()).toBe('Title already taken') // Other field errors should not be present expect(wrapper.find('#dateTime-error').exists()).toBe(false) }) })