import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { TripCardsLayout } from '../TripCardsLayout'; import { TripCardApiData } from '../types'; import { tealiumTrackingHandler } from '../../../helpers/tracking/TrackingHandler'; jest.mock('../../../helpers/tracking/TrackingHandler', () => ({ tealiumTrackingHandler: jest.fn() })); const mockElement = { class: 'trip-cards', tripcards: btoa(JSON.stringify([1, 2])), title: 'Test Cruises', titleurl: 'https://example.com', description: 'Test Description' }; const MockCard = ({ card }: { card?: TripCardApiData }) => (
{card ? card.headline || 'Mock Card' : 'Mock Card'}
); // Mock ResizeObserver global.ResizeObserver = class ResizeObserver { public observe(): void { // Mock implementation } public unobserve(): void { // Mock implementation } public disconnect(): void { // Mock implementation } }; describe('TripCardsLayout', () => { beforeEach(() => { jest.clearAllMocks(); Element.prototype.scrollTo = jest.fn(); // Mock matchMedia for each test Object.defineProperty(window, 'matchMedia', { writable: true, configurable: 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() })) }); }); afterEach(() => { jest.clearAllMocks(); }); describe('Rendering', () => { it('renders with title and description', () => { const items = [ { id: '1', data: undefined }, { id: '2', data: undefined } ]; render( ); expect(screen.getByText('Test Cruises')).toBeInTheDocument(); expect(screen.getByText('Test Description')).toBeInTheDocument(); }); it('renders empty title when not provided', () => { const elementWithoutTitle = { ...mockElement, title: undefined }; const items = [{ id: '1', data: undefined }]; const { container } = render( ); const title = container.querySelector('h2'); expect(title).toBeInTheDocument(); expect(title).toBeEmptyDOMElement(); }); it('renders empty description when not provided', () => { const elementWithoutDesc = { ...mockElement, description: undefined }; const items = [{ id: '1', data: undefined }]; const { container } = render( ); const description = container.querySelector('p'); expect(description).toBeInTheDocument(); expect(description).toBeEmptyDOMElement(); }); it('renders title link when provided', () => { const items = [{ id: '1', data: undefined }]; render( ); const link = screen.getByTestId('trip-cards-title-link'); expect(link).toHaveAttribute('href', 'https://example.com'); expect(link).toHaveAttribute('target', '_blank'); expect(link).toHaveAttribute('rel', 'noopener noreferrer'); }); it('renders two links when titleurl is provided', () => { const items = [{ id: '1', data: undefined }]; render( ); const links = screen.getAllByRole('link'); expect(links).toHaveLength(2); const titleLink = screen.getByTestId('trip-cards-title-link'); const iconLink = screen.getByTestId('title-link'); expect(titleLink).toHaveAttribute('href', 'https://example.com'); expect(iconLink).toHaveAttribute('href', 'https://example.com'); }); it('does not render title link when not provided', () => { const elementWithoutLink = { ...mockElement, titleurl: undefined }; const items = [{ id: '1', data: undefined }]; render( ); const links = screen.queryAllByRole('link'); expect(links.length).toBe(0); }); it('renders ChevronRightIcon in title link', () => { const items = [{ id: '1', data: undefined }]; const { container } = render( ); const svg = container.querySelector('svg'); expect(svg).toBeInTheDocument(); }); }); describe('Static Grid Layout', () => { it('renders static grid for exactly 2 items', () => { const items = [ { id: '1', data: undefined }, { id: '2', data: undefined } ]; render( ); const staticGrid = screen.getByTestId('static-cards-grid'); expect(staticGrid).toBeInTheDocument(); }); it('renders correct number of cards in static grid', () => { const items = [ { id: '1', data: undefined }, { id: '2', data: undefined } ]; render( ); const cards = screen.getAllByTestId('mock-card'); expect(cards.length).toBe(2); }); it('passes correct props to CardComponent in static grid', () => { const mockData: TripCardApiData = { cruise_id: '1', image: 'test.jpg', headline: 'Test Cruise', date: 'Jan 2024', duration: '7 nights', destination: 'Test Route', ship: 'Test Ship', price: '£1000', logo: 'logo.png', logo_url: 'https://example.com', logo_name: 'Test Cruise Line', cta_url: 'https://example.com', cta_text: 'View' }; const items = [{ id: '1', data: mockData }, { id: '2', data: undefined }]; render( ); expect(screen.getByText('Test Cruise')).toBeInTheDocument(); }); }); describe('Carousel Layout', () => { it('renders carousel for more than 2 items', () => { const items = [ { id: '1', data: undefined }, { id: '2', data: undefined }, { id: '3', data: undefined } ]; render( ); const cards = screen.getAllByTestId('mock-card'); expect(cards.length).toBe(3); }); it('renders navigation arrows in carousel', () => { const items = [ { id: '1', data: undefined }, { id: '2', data: undefined }, { id: '3', data: undefined } ]; render( ); expect(screen.getByLabelText('Previous items')).toBeInTheDocument(); expect(screen.getByLabelText('Next items')).toBeInTheDocument(); }); it('renders page dots in carousel', () => { const items = [ { id: '1', data: undefined }, { id: '2', data: undefined }, { id: '3', data: undefined }, { id: '4', data: undefined } ]; render( ); expect(screen.getByLabelText('Previous items')).toBeInTheDocument(); expect(screen.getByLabelText('Next items')).toBeInTheDocument(); // With 4 items and 2 per page, we have 2 pages, so we should have 2 dot buttons const pageDots = screen.getAllByLabelText(/Go to page/i); expect(pageDots.length).toBe(2); }); it('renders carousel for 1 item', () => { const items = [{ id: '1', data: undefined }]; render( ); const cards = screen.getAllByTestId('mock-card'); expect(cards.length).toBe(1); }); it('wraps cards in CarouselItem with correct widthConfig', () => { const items = [ { id: '1', data: undefined }, { id: '2', data: undefined }, { id: '3', data: undefined } ]; render( ); // Verify carousel is rendering by checking for cards const cards = screen.getAllByTestId('mock-card'); expect(cards.length).toBe(3); }); }); describe('Card Rendering', () => { it('renders each item with unique key', () => { const items = [ { id: 'card-1', data: undefined }, { id: 'card-2', data: undefined } ]; render( ); const cards = screen.getAllByTestId('mock-card'); expect(cards.length).toBe(2); }); it('passes data to CardComponent', () => { const mockData: TripCardApiData = { cruise_id: '123', image: 'image.jpg', headline: 'Specific Cruise', date: 'Feb 2024', duration: '10 nights', destination: 'Route A', ship: 'Ship X', price: '£2000', logo: 'logo.png', logo_url: 'https://example.com', logo_name: 'Test Cruise Line', cta_url: 'https://example.com', cta_text: 'Book' }; const items = [ { id: '1', data: mockData }, { id: '2', data: undefined }, { id: '3', data: undefined } ]; render( ); expect(screen.getByText('Specific Cruise')).toBeInTheDocument(); }); it('handles empty items array', () => { const { container } = render( ); expect(container.firstChild).toBeInTheDocument(); const cards = screen.queryAllByTestId('mock-card'); expect(cards.length).toBe(0); }); }); describe('Container Structure', () => { it('renders main container', () => { const items = [{ id: '1', data: undefined }]; render( ); const mainContainer = screen.getByTestId('trip-cards-container'); expect(mainContainer).toBeInTheDocument(); }); it('renders title section', () => { const items = [{ id: '1', data: undefined }]; render( ); const titleSection = screen.getByTestId('title-section'); expect(titleSection).toBeInTheDocument(); }); it('renders title bar with content', () => { const items = [{ id: '1', data: undefined }]; render( ); const titleBar = screen.getByTestId('title-bar'); const titleContent = screen.getByTestId('title-content'); expect(titleBar).toBeInTheDocument(); expect(titleContent).toBeInTheDocument(); }); }); describe('ItemsPerPage', () => { it('respects itemsPerPage prop', () => { const items = Array.from({ length: 6 }, (_, i) => ({ id: `${i}`, data: undefined })); render( ); const cards = screen.getAllByTestId('mock-card'); expect(cards.length).toBe(6); }); it('handles itemsPerPage of 1', () => { const items = [ { id: '1', data: undefined }, { id: '2', data: undefined } ]; render( ); const cards = screen.getAllByTestId('mock-card'); expect(cards.length).toBe(2); }); }); describe('Edge Cases', () => { it('handles large number of items', () => { const items = Array.from({ length: 50 }, (_, i) => ({ id: `${i}`, data: undefined })); render( ); const cards = screen.getAllByTestId('mock-card'); expect(cards.length).toBe(50); }); it('handles empty title and description', () => { const emptyElement = { ...mockElement, title: '', description: '' }; const items = [{ id: '1', data: undefined }]; const { container } = render( ); const title = container.querySelector('h2'); const description = container.querySelector('p'); expect(title).toBeInTheDocument(); expect(description).toBeInTheDocument(); if (title && description) { expect(title.textContent).toBe(''); expect(description.textContent).toBe(''); } }); }); describe('Tracking', () => { it('calls tealiumTrackingHandler when title link is clicked', () => { const items = [{ id: '1', data: undefined }]; render( ); const titleLink = screen.getByTestId('trip-cards-title-link'); fireEvent.click(titleLink); expect(tealiumTrackingHandler).toHaveBeenCalledWith( 'trip cards component: Test Cruises', 'navigation', 'click', undefined, undefined, { app_content_location: 'uk' } ); }); it('calls tealiumTrackingHandler when icon link is clicked', () => { const items = [{ id: '1', data: undefined }]; render( ); const iconLink = screen.getByTestId('title-link'); fireEvent.click(iconLink); expect(tealiumTrackingHandler).toHaveBeenCalledWith( 'trip cards component: Test Cruises', 'navigation', 'click', undefined, undefined, { app_content_location: 'uk' } ); }); it('does not call tealiumTrackingHandler when title link is not clicked', () => { const elementWithoutLink = { ...mockElement, titleurl: undefined }; const items = [{ id: '1', data: undefined }]; render( ); expect(tealiumTrackingHandler).not.toHaveBeenCalled(); }); }); });