import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import BookingSection from './BookingSection.svelte'; import { homepageContent } from '$lib/content/homepage'; async function fillOwnerStep() { await fireEvent.input(screen.getByLabelText(/Full Name/i), { target: { value: 'Alex Walker' } }); await fireEvent.input(screen.getByLabelText(/^Email/i), { target: { value: 'alex@example.com' } }); await fireEvent.input(screen.getByLabelText(/Contact #/i), { target: { value: '021 123 4567' } }); } async function fillDogStep() { await fireEvent.click(screen.getByLabelText('Tiny Gang Pack Walks')); await fireEvent.click(screen.getByLabelText('Other Services')); await fireEvent.input(screen.getByLabelText(/Dog's Name/i), { target: { value: 'Maya' } }); await fireEvent.input(screen.getByLabelText(/Location/i), { target: { value: 'Kingsland' } }); await fireEvent.input(screen.getByLabelText(/Tiny Gang Pack Walks fit/i), { target: { value: 'Loves small group walks.' } }); } async function moveToOwnerStep(container: HTMLElement) { await fillDogStep(); await fireEvent.click(container.querySelector('.booking-next-button')!); } describe('BookingSection', () => { beforeEach(() => { window.sessionStorage.clear(); Object.defineProperty(document, 'referrer', { configurable: true, value: 'https://www.google.com/' }); }); it('validates the dog details step before progressing', async () => { const { container } = render(BookingSection, { booking: homepageContent.booking }); expect(screen.queryByLabelText(/General enquiry/i)).not.toBeInTheDocument(); await fireEvent.click(container.querySelector('.booking-next-button')!); expect(screen.getByText("Please enter your dog's name")).toBeInTheDocument(); expect(screen.getByText('Please enter your location')).toBeInTheDocument(); }); it('validates the owner details step before submitting', async () => { const { container } = render(BookingSection, { booking: homepageContent.booking }); await moveToOwnerStep(container); await fireEvent.click(container.querySelector('.booking-submit-button')!); expect(screen.getByText('Please enter your full name')).toBeInTheDocument(); expect(screen.getByText('Please enter your email address')).toBeInTheDocument(); expect(screen.getByText('Please enter your contact number')).toBeInTheDocument(); }); it('submits the completed booking flow and shows the success modal', async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({}) }); vi.stubGlobal('fetch', fetchMock); const { container } = render(BookingSection, { booking: homepageContent.booking }); await moveToOwnerStep(container); await fillOwnerStep(); await fireEvent.click(container.querySelector('.booking-submit-button')!); await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1)); expect(fetchMock).toHaveBeenCalledWith( '/api/submit', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' } }) ); const payload = JSON.parse(fetchMock.mock.calls[0][1].body as string); expect(payload).toMatchObject({ enquiryType: 'booking', fullName: 'Alex Walker', email: 'alex@example.com', phone: '021 123 4567', petName: 'Maya', location: 'Kingsland', message: 'Loves small group walks.', services: ['Tiny Gang Pack Walks', 'Other Services'], website: '', referrer: 'https://www.google.com/', stepChanges: 1, journey: [window.location.pathname] }); expect(payload.formStartedAt).toEqual(expect.any(Number)); expect(payload.visitStartedAt).toEqual(expect.any(Number)); expect(payload.pageEnteredAt).toEqual(expect.any(Number)); expect(payload.firstInteractionAt).toEqual(expect.any(Number)); expect(payload.sendClickedAt).toEqual(expect.any(Number)); expect(screen.getByRole('dialog', { name: /Booking confirmed/i })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /on our radar/i })).toBeInTheDocument(); await fireEvent.click(screen.getByRole('button', { name: /Sounds great!/i })); await waitFor(() => expect(screen.queryByRole('dialog', { name: /Booking confirmed/i })).not.toBeInTheDocument() ); }); it('allows general enquiries without dog or service details', async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({}) }); vi.stubGlobal('fetch', fetchMock); const { container } = render(BookingSection, { booking: homepageContent.booking, allowGeneralEnquiry: true }); await fireEvent.click(screen.getByLabelText(/General enquiry/i)); expect(screen.queryByLabelText(/Dog's Name/i)).not.toBeInTheDocument(); expect(screen.queryByText('Tiny Gang Pack Walks')).not.toBeInTheDocument(); await fireEvent.click(container.querySelector('.booking-next-button')!); expect(screen.getByText('Please tell us how we can help')).toBeInTheDocument(); await fireEvent.input(screen.getByLabelText(/Your Message/i), { target: { value: 'I would like to discuss a business partnership.' } }); await fireEvent.click(container.querySelector('.booking-next-button')!); await fillOwnerStep(); await fireEvent.click(container.querySelector('.booking-submit-button')!); await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1)); const payload = JSON.parse(fetchMock.mock.calls[0][1].body as string); expect(payload).toMatchObject({ enquiryType: 'general', fullName: 'Alex Walker', email: 'alex@example.com', phone: '021 123 4567', petName: '', location: '', message: 'I would like to discuss a business partnership.', services: [], stepChanges: 1, journey: [window.location.pathname] }); expect(screen.getByRole('dialog', { name: /Enquiry confirmed/i })).toBeInTheDocument(); expect(screen.getByText(/Your message is with us!/i)).toBeInTheDocument(); }); it('shows the API error message when submission fails', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, json: vi.fn().mockResolvedValue({ detail: 'Mail API unavailable' }) }) ); const { container } = render(BookingSection, { booking: homepageContent.booking }); await moveToOwnerStep(container); await fillOwnerStep(); await fireEvent.click(container.querySelector('.booking-submit-button')!); expect(await screen.findByText('Mail API unavailable')).toBeInTheDocument(); }); });