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);
});
});
});