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