import { submitEnquiry, ApiError } from '../submitEnquiry'; import { VALID_FORM_DATA } from './helpers'; describe('submitEnquiry', () => { const mockFetch = jest.fn(); beforeEach(() => { jest.resetAllMocks(); global.fetch = mockFetch; }); const successResponse = () => ({ ok: true, json: () => Promise.resolve({ success: true }) }); it('should POST to the correct URL when on thetimes domain', async () => { mockFetch.mockResolvedValue(successResponse()); Object.defineProperty(window, 'location', { value: { ...window.location, hostname: 'www.thetimes.com', origin: 'https://www.thetimes.com' }, writable: true }); await submitEnquiry(VALID_FORM_DATA); expect(mockFetch).toHaveBeenCalledWith( 'https://www.thetimes.com/holidays/wp-admin/admin-ajax.php', expect.objectContaining({ method: 'POST', mode: 'cors' }) ); }); it('should POST to staging URL when not on thetimes domain', async () => { mockFetch.mockResolvedValue(successResponse()); Object.defineProperty(window, 'location', { value: { ...window.location, hostname: 'localhost', origin: 'http://localhost' }, writable: true }); await submitEnquiry(VALID_FORM_DATA); expect(mockFetch).toHaveBeenCalledWith( 'https://www.staging-thetimes.com/holidays/wp-admin/admin-ajax.php', expect.anything() ); }); it('should send correct headers', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry(VALID_FORM_DATA); const call = mockFetch.mock.calls[0][1]; expect(call.headers).toEqual({ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' }); }); it('should include form data in the body', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry(VALID_FORM_DATA); const body = mockFetch.mock.calls[0][1].body; const params = new URLSearchParams(body); expect(params.get('action')).toBe('tt_ContactUsHook'); expect(params.get('first_name')).toBe('John'); expect(params.get('last_name')).toBe('Doe'); expect(params.get('telephone_number')).toBe('07700900000'); expect(params.get('email_address')).toBe('john@example.com'); expect(params.get('depart_from')).toBe('Heathrow'); expect(params.get('destination')).toBe('Maldives'); expect(params.get('budget')).toBe('5000,15000'); expect(params.get('discount_code')).toBe('ABC123'); }); it('should send contact preferences as contact_type', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry({ ...VALID_FORM_DATA, contactByTelephone: true, contactByEmail: true }); const body = mockFetch.mock.calls[0][1].body; const params = new URLSearchParams(body); expect(params.get('contact_type')).toBe('Telephone, Email'); }); it('should omit contact_type when no contact preference selected', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry({ ...VALID_FORM_DATA, contactByTelephone: false, contactByEmail: false }); const body = mockFetch.mock.calls[0][1].body; const params = new URLSearchParams(body); expect(params.has('contact_type')).toBe(false); }); it('should send appointment types as appointment_type', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry({ ...VALID_FORM_DATA, appointmentVideoCall: true, appointmentTelephoneCall: true, preferredDate: '2026-04-17', preferredTime: '18:00' }); const body = mockFetch.mock.calls[0][1].body; const params = new URLSearchParams(body); expect(params.get('appointment_type')).toBe('Video call, Telephone call'); expect(params.get('appointment_date')).toBe('2026-04-17'); expect(params.get('appointment_time')).toBe('18:00'); }); it('should omit appointment fields when not set', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry(VALID_FORM_DATA); const body = mockFetch.mock.calls[0][1].body; const params = new URLSearchParams(body); expect(params.has('appointment_type')).toBe(false); expect(params.has('appointment_date')).toBe(false); expect(params.has('appointment_time')).toBe(false); }); it('should include page URL and title in comments', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry(VALID_FORM_DATA); const body = mockFetch.mock.calls[0][1].body; const params = new URLSearchParams(body); const comments = params.get('comments'); expect(comments).toContain('Honeymoon trip'); expect(comments).toContain('Submitted from:'); expect(comments).toContain('Page title:'); }); it('should throw ApiError with backend message on validation error', async () => { mockFetch.mockResolvedValue({ ok: false, status: 401, json: () => Promise.resolve({ success: false, data: 'Please fill in all required fields.' }) }); await expect(submitEnquiry(VALID_FORM_DATA)).rejects.toThrow(ApiError); await expect(submitEnquiry(VALID_FORM_DATA)).rejects.toThrow( 'Please fill in all required fields.' ); }); it('should throw ApiError with backend message on submission error', async () => { mockFetch.mockResolvedValue({ ok: true, json: () => Promise.resolve({ success: false, data: 'Please fill in all fields correctly.' }) }); await expect(submitEnquiry(VALID_FORM_DATA)).rejects.toThrow(ApiError); await expect(submitEnquiry(VALID_FORM_DATA)).rejects.toThrow( 'Please fill in all fields correctly.' ); }); it('should throw a plain Error (not ApiError) when no JSON body', async () => { mockFetch.mockResolvedValue({ ok: false, status: 500, json: () => Promise.reject(new Error('invalid json')) }); const error = await submitEnquiry(VALID_FORM_DATA).catch(e => e); expect(error).not.toBeInstanceOf(ApiError); expect(error.message).toBe('HTTP error 500'); }); it('should throw on network error', async () => { mockFetch.mockRejectedValue(new Error('Network failure')); await expect(submitEnquiry(VALID_FORM_DATA)).rejects.toThrow( 'Network failure' ); }); it('should set credentials to omit', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry(VALID_FORM_DATA); const call = mockFetch.mock.calls[0][1]; expect(call.credentials).toBe('omit'); }); it('should send newsletter=1 when opt-in checked', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry({ ...VALID_FORM_DATA, newsletterOptIn: true }); const body = mockFetch.mock.calls[0][1].body; const params = new URLSearchParams(body); expect(params.get('newsletter')).toBe('1'); }); it('should not send newsletter field when unchecked', async () => { mockFetch.mockResolvedValue(successResponse()); await submitEnquiry({ ...VALID_FORM_DATA, newsletterOptIn: false }); const body = mockFetch.mock.calls[0][1].body; const params = new URLSearchParams(body); expect(params.has('newsletter')).toBe(false); }); });