import React from 'react'; import '@testing-library/jest-dom'; import { render, screen, act, fireEvent } from '@testing-library/react'; import { TravelMiniCTA } from '../index'; describe('TravelMiniCTA', () => { const mockIntersectionObserverInstance: IntersectionObserver = { root: null, rootMargin: '', thresholds: [], observe: jest.fn(), unobserve: jest.fn(), disconnect: jest.fn(), takeRecords: jest.fn() }; let mockObserve: jest.Mock; let mockDisconnect: jest.Mock; let mockIntersectionObserver: jest.Mock; let mockIntersectionObserve: jest.Mock; let mockIntersectionUnobserve: jest.Mock; beforeEach(() => { jest.clearAllMocks(); Object.defineProperty(window, 'matchMedia', { writable: true, value: jest.fn().mockImplementation(query => ({ matches: false, media: query, onchange: null, addListener: jest.fn(), removeListener: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), dispatchEvent: jest.fn() })) }); mockIntersectionObserve = jest.fn(); mockIntersectionUnobserve = jest.fn(); const mockDisconnectIntersection = jest.fn(); mockIntersectionObserver = jest.fn(_ => ({ observe: mockIntersectionObserve, unobserve: mockIntersectionUnobserve, disconnect: mockDisconnectIntersection })); window.IntersectionObserver = mockIntersectionObserver as any; mockObserve = jest.fn(); mockDisconnect = jest.fn(); global.MutationObserver = jest.fn(() => { return { observe: mockObserve, disconnect: mockDisconnect, takeRecords: jest.fn() }; }) as any; (global as any).lpTag = { newPage: jest.fn(), loaded: true }; (window as any).lpTag = { newPage: jest.fn(), loaded: true }; (window as any).utag = { link: jest.fn() }; }); afterEach(() => { delete (global as any).lpTag; delete (window as any).lpTag; delete (window as any).utag; }); it('should render with all props', () => { const { container } = render(); expect(container).toMatchSnapshot(); }); it('should render phone label and number', () => { render(); fireEvent.click(screen.getByTestId('travel-learn-more-button')); expect(screen.getByText(/Call us on/)).toBeInTheDocument(); expect(screen.getByText('0808 1342945')).toBeInTheDocument(); }); it('should render phone number as a tel link', () => { render(); fireEvent.click(screen.getByTestId('travel-learn-more-button')); const phoneLink = screen.getByText('0808 1342945').closest('a'); expect(phoneLink).toHaveAttribute('href', 'tel:08081342945'); expect( screen.getByRole('heading', { name: 'Call us on 0808 1342945' }) ).toContainElement(phoneLink); }); it('should render working hours', () => { render(); fireEvent.click(screen.getByTestId('travel-learn-more-button')); expect( screen.queryByRole('heading', { name: 'Opening hours:' }) ).not.toBeInTheDocument(); expect(screen.getByText(/Opening hours:/)).toHaveTextContent( 'Opening hours: Mon-Fri: 9am-6pm Sat & Sun: 10am-5pm' ); }); it('should render primary button with correct text and URL', () => { render(); const primaryButton = screen.getByText('Enquire now'); expect(primaryButton).toBeInTheDocument(); }); it('should render with minimal props', () => { render(); expect(screen.getByTestId('travel-mini-cta')).toBeInTheDocument(); }); it('should have correct container test id', () => { render(); expect(screen.getByTestId('travel-mini-cta')).toBeInTheDocument(); }); it('should render with isApp prop', () => { const { container } = render(); expect(container).toMatchSnapshot(); }); it('should pass isApp prop to styled components', () => { render(); const container = screen.getByTestId('travel-mini-cta'); expect(container).toBeInTheDocument(); }); describe('Tracking', () => { it('should fire tracking event when phone number is clicked', () => { render(); fireEvent.click(screen.getByTestId('travel-learn-more-button')); const phoneLink = screen.getByText('0808 1342945'); fireEvent.click(phoneLink); expect(phoneLink).toBeInTheDocument(); expect((window as any).utag.link).toHaveBeenCalledWith( expect.objectContaining({ event_navigation_action: 'navigation', event_navigation_name: 'mini slice: phone number click', event_navigation_browsing_method: 'click' }) ); }); it('should handle component unmount and cleanup intersection observer', () => { const { unmount } = render(); expect(mockIntersectionObserver).toHaveBeenCalled(); unmount(); expect(mockIntersectionObserver).toHaveBeenCalledTimes(1); }); it('should fire tracking event when primary button is clicked', () => { render(); const primaryButton = screen.getByText('Enquire now'); fireEvent.click(primaryButton); expect(primaryButton).toHaveAttribute( 'href', 'https://www.thetimes.com/holidays/contact-us/' ); }); it('should fire impression tracking event when component is visible', () => { const { container } = render(); expect(mockIntersectionObserver).toHaveBeenCalled(); const observerCallback = mockIntersectionObserver.mock.calls[0][0]; const mockEntry = { isIntersecting: true, target: container.firstChild }; act(() => { observerCallback([mockEntry], mockIntersectionObserverInstance); }); expect(container.firstChild).toBeInTheDocument(); }); it('should not fire impression tracking event when component is not visible', () => { const { container } = render(); expect(mockIntersectionObserver).toHaveBeenCalled(); const observerCallback = mockIntersectionObserver.mock.calls[0][0]; const mockEntry = { isIntersecting: false, target: container.firstChild }; act(() => { observerCallback([mockEntry], mockIntersectionObserverInstance); }); expect(container.firstChild).toBeInTheDocument(); }); it('should fire tracking event when Enquire now is clicked (non-app)', () => { render(); fireEvent.click(screen.getByTestId('travel-enquire-button')); expect((window as any).utag.link).toHaveBeenCalledWith( expect.objectContaining({ event_navigation_name: 'mini slice: enquire now' }) ); }); it('should toggle Learn more label when clicked', () => { render(); fireEvent.click(screen.getByTestId('travel-learn-more-button')); expect(screen.getAllByText('Close').length).toBeGreaterThan(0); }); }); });