Files
gw-svelte/src/lib/components/BookingSection.test.ts
T

196 lines
6.7 KiB
TypeScript
Raw Normal View History

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';
2026-05-06 11:36:19 +12:00
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() {
2026-05-15 01:28:10 +12:00
await fireEvent.click(screen.getByLabelText('Tiny Gang Pack Walks'));
2026-05-06 11:36:19 +12:00
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' }
});
2026-05-15 01:28:10 +12:00
await fireEvent.input(screen.getByLabelText(/Tiny Gang Pack Walks fit/i), {
2026-05-06 11:36:19 +12:00
target: { value: 'Loves small group walks.' }
});
}
async function moveToOwnerStep(container: HTMLElement) {
await fillDogStep();
await fireEvent.click(container.querySelector('.booking-next-button')!);
}
2026-05-02 08:26:18 +12:00
describe('BookingSection', () => {
beforeEach(() => {
window.sessionStorage.clear();
2026-05-02 08:26:18 +12:00
Object.defineProperty(document, 'referrer', {
configurable: true,
value: 'https://www.google.com/'
});
});
2026-05-03 11:49:59 +12:00
it('validates the dog details step before progressing', async () => {
2026-05-02 08:26:18 +12:00
const { container } = render(BookingSection, {
booking: homepageContent.booking
});
2026-05-04 20:32:24 +12:00
expect(screen.queryByLabelText(/General enquiry/i)).not.toBeInTheDocument();
2026-05-02 08:26:18 +12:00
await fireEvent.click(container.querySelector('.booking-next-button')!);
2026-05-03 11:49:59 +12:00
expect(screen.getByText("Please enter your dog's name")).toBeInTheDocument();
expect(screen.getByText('Please enter your location')).toBeInTheDocument();
2026-05-02 08:26:18 +12:00
});
2026-05-03 11:49:59 +12:00
it('validates the owner details step before submitting', async () => {
2026-05-02 08:26:18 +12:00
const { container } = render(BookingSection, {
booking: homepageContent.booking
});
2026-05-06 11:36:19 +12:00
await moveToOwnerStep(container);
2026-05-02 08:26:18 +12:00
await fireEvent.click(container.querySelector('.booking-submit-button')!);
2026-05-03 11:49:59 +12:00
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();
2026-05-02 08:26:18 +12:00
});
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
});
2026-05-06 11:36:19 +12:00
await moveToOwnerStep(container);
await fillOwnerStep();
2026-05-03 11:49:59 +12:00
2026-05-02 08:26:18 +12:00
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({
2026-05-04 20:32:24 +12:00
enquiryType: 'booking',
2026-05-02 08:26:18 +12:00
fullName: 'Alex Walker',
email: 'alex@example.com',
phone: '021 123 4567',
petName: 'Maya',
location: 'Kingsland',
message: 'Loves small group walks.',
2026-05-15 01:28:10 +12:00
services: ['Tiny Gang Pack Walks', 'Other Services'],
website: '',
referrer: 'https://www.google.com/',
stepChanges: 1,
journey: [window.location.pathname]
2026-05-02 08:26:18 +12:00
});
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));
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()
);
});
2026-05-04 20:32:24 +12:00
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();
2026-05-15 01:28:10 +12:00
expect(screen.queryByText('Tiny Gang Pack Walks')).not.toBeInTheDocument();
2026-05-04 20:32:24 +12:00
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')!);
2026-05-06 11:36:19 +12:00
await fillOwnerStep();
2026-05-04 20:32:24 +12:00
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]
2026-05-04 20:32:24 +12:00
});
expect(screen.getByRole('dialog', { name: /Enquiry confirmed/i })).toBeInTheDocument();
expect(screen.getByText(/Your message is with us!/i)).toBeInTheDocument();
});
2026-05-02 08:26:18 +12:00
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
});
2026-05-06 11:36:19 +12:00
await moveToOwnerStep(container);
await fillOwnerStep();
2026-05-02 08:26:18 +12:00
await fireEvent.click(container.querySelector('.booking-submit-button')!);
expect(await screen.findByText('Mail API unavailable')).toBeInTheDocument();
});
});