2026-05-02 08:26:18 +12:00
|
|
|
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';
|
|
|
|
|
|
|
|
|
|
describe('BookingSection', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
Object.defineProperty(document, 'referrer', {
|
|
|
|
|
configurable: true,
|
|
|
|
|
value: 'https://www.google.com/'
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('validates the owner details step before progressing', async () => {
|
|
|
|
|
const { container } = render(BookingSection, {
|
|
|
|
|
booking: homepageContent.booking
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('Please enter your full name')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('Please enter a valid email address')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('Please enter your contact number')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('validates the dog details step before submitting', async () => {
|
|
|
|
|
const { container } = render(BookingSection, {
|
|
|
|
|
booking: homepageContent.booking
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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' }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
|
|
|
|
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText("Please enter your dog's name")).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText('Please enter your location')).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 fireEvent.click(screen.getByLabelText('Pack Walks'));
|
|
|
|
|
await fireEvent.click(screen.getByLabelText('Other Services'));
|
|
|
|
|
|
|
|
|
|
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' }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
|
|
|
|
|
|
|
|
|
await fireEvent.input(screen.getByLabelText(/Pet's Name/i), {
|
|
|
|
|
target: { value: 'Maya' }
|
|
|
|
|
});
|
|
|
|
|
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
|
|
|
|
target: { value: 'Kingsland' }
|
|
|
|
|
});
|
|
|
|
|
await fireEvent.input(screen.getByLabelText(/About Your Dog/i), {
|
|
|
|
|
target: { value: 'Loves small group walks.' }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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({
|
|
|
|
|
fullName: 'Alex Walker',
|
|
|
|
|
email: 'alex@example.com',
|
|
|
|
|
phone: '021 123 4567',
|
|
|
|
|
petName: 'Maya',
|
|
|
|
|
location: 'Kingsland',
|
|
|
|
|
message: 'Loves small group walks.',
|
|
|
|
|
services: ['Pack Walks', 'Other Services'],
|
2026-05-02 11:24:11 +12:00
|
|
|
website: '',
|
2026-05-02 08:26:18 +12:00
|
|
|
referrer: 'https://www.google.com/'
|
|
|
|
|
});
|
2026-05-02 11:24:11 +12:00
|
|
|
expect(payload.formStartedAt).toEqual(expect.any(Number));
|
2026-05-02 08:26:18 +12:00
|
|
|
|
|
|
|
|
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('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 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' }
|
|
|
|
|
});
|
|
|
|
|
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
|
|
|
|
|
|
|
|
|
await fireEvent.input(screen.getByLabelText(/Pet's Name/i), {
|
|
|
|
|
target: { value: 'Maya' }
|
|
|
|
|
});
|
|
|
|
|
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
|
|
|
|
target: { value: 'Kingsland' }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
|
|
|
|
|
|
|
|
|
expect(await screen.findByText('Mail API unavailable')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|