@times-components/ts-components 1.146.2-a0ffbb6bc2acbec91d140a39f6be06256e702094.5 → 1.146.2-c12ed7999a41984c2ba8c437357e7a5df1914881.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/carousel-component/CarouselComponent.stories.d.ts +1 -0
- package/dist/components/carousel-component/CarouselComponent.stories.js +146 -0
- package/dist/components/carousel-component/CarouselItem.d.ts +3 -0
- package/dist/components/carousel-component/CarouselItem.js +12 -0
- package/dist/components/carousel-component/DefaultNavigationArrow.d.ts +8 -0
- package/dist/components/carousel-component/DefaultNavigationArrow.js +6 -0
- package/dist/components/carousel-component/DefaultPageDot.d.ts +8 -0
- package/dist/components/carousel-component/DefaultPageDot.js +4 -0
- package/dist/components/carousel-component/__tests__/CarouselComponent.test.d.ts +1 -0
- package/dist/components/carousel-component/__tests__/CarouselComponent.test.js +163 -0
- package/dist/components/carousel-component/__tests__/CarouselItem.test.d.ts +1 -0
- package/dist/components/carousel-component/__tests__/CarouselItem.test.js +80 -0
- package/dist/components/carousel-component/__tests__/DefaultNavigationArrow.test.d.ts +1 -0
- package/dist/components/carousel-component/__tests__/DefaultNavigationArrow.test.js +62 -0
- package/dist/components/carousel-component/__tests__/DefaultPageDot.test.d.ts +1 -0
- package/dist/components/carousel-component/__tests__/DefaultPageDot.test.js +68 -0
- package/dist/components/carousel-component/hooks/__tests__/useCarousel.test.d.ts +1 -0
- package/dist/components/carousel-component/hooks/__tests__/useCarousel.test.js +459 -0
- package/dist/components/carousel-component/hooks/useCarousel.d.ts +2 -0
- package/dist/components/carousel-component/hooks/useCarousel.js +167 -0
- package/dist/components/carousel-component/index.d.ts +4 -0
- package/dist/components/carousel-component/index.js +20 -0
- package/dist/components/carousel-component/styles.d.ts +28 -0
- package/dist/components/carousel-component/styles.js +172 -0
- package/dist/components/carousel-component/types.d.ts +53 -0
- package/dist/components/carousel-component/types.js +2 -0
- package/dist/components/social-embed/SocialMediaEmbed.js +15 -122
- package/dist/components/social-embed/SocialVendor.d.ts +16 -1
- package/dist/components/social-embed/SocialVendor.js +2 -41
- package/dist/components/social-embed/__tests__/SocialVendor.test.js +1 -8
- package/dist/components/social-embed/constants.d.ts +0 -1
- package/dist/components/social-embed/constants.js +2 -3
- package/dist/components/social-embed/helpers/socialMediaVendors.js +1 -6
- package/dist/components/social-embed/styles.d.ts +0 -1
- package/dist/components/social-embed/styles.js +1 -28
- package/dist/components/trip-cards/SkeletonCard.d.ts +7 -0
- package/dist/components/trip-cards/SkeletonCard.js +22 -0
- package/dist/components/trip-cards/TripCard.d.ts +3 -0
- package/dist/components/trip-cards/TripCard.js +61 -0
- package/dist/components/trip-cards/TripCards.stories.d.ts +1 -0
- package/dist/components/trip-cards/TripCards.stories.js +159 -0
- package/dist/components/trip-cards/TripCardsLayout.d.ts +3 -0
- package/dist/components/trip-cards/TripCardsLayout.js +63 -0
- package/dist/components/trip-cards/__tests__/SkeletonCard.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/SkeletonCard.test.js +139 -0
- package/dist/components/trip-cards/__tests__/TripCard.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/TripCard.test.js +165 -0
- package/dist/components/trip-cards/__tests__/TripCardsLayout.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/TripCardsLayout.test.js +353 -0
- package/dist/components/trip-cards/__tests__/assets.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/assets.test.js +165 -0
- package/dist/components/trip-cards/__tests__/helpers.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/helpers.test.js +221 -0
- package/dist/components/trip-cards/__tests__/index.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/index.test.js +482 -0
- package/dist/components/trip-cards/__tests__/mockData.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/mockData.test.js +57 -0
- package/dist/components/trip-cards/__tests__/skeletonStyles.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/skeletonStyles.test.js +194 -0
- package/dist/components/trip-cards/assets/BoatIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/BoatIcon.js +4 -0
- package/dist/components/trip-cards/assets/CalendarIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/CalendarIcon.js +4 -0
- package/dist/components/trip-cards/assets/ChevronRightIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/ChevronRightIcon.js +4 -0
- package/dist/components/trip-cards/assets/LocationIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/LocationIcon.js +4 -0
- package/dist/components/trip-cards/assets/MoonIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/MoonIcon.js +4 -0
- package/dist/components/trip-cards/assets/index.d.ts +6 -0
- package/dist/components/trip-cards/assets/index.js +7 -0
- package/dist/components/trip-cards/helpers.d.ts +11 -0
- package/dist/components/trip-cards/helpers.js +144 -0
- package/dist/components/trip-cards/index.d.ts +4 -0
- package/dist/components/trip-cards/index.js +68 -0
- package/dist/components/trip-cards/mockData.d.ts +3 -0
- package/dist/components/trip-cards/mockData.js +317 -0
- package/dist/components/trip-cards/skeletonStyles.d.ts +9 -0
- package/dist/components/trip-cards/skeletonStyles.js +37 -0
- package/dist/components/trip-cards/styles.d.ts +43 -0
- package/dist/components/trip-cards/styles.js +404 -0
- package/dist/components/trip-cards/types.d.ts +121 -0
- package/dist/components/trip-cards/types.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/utils/cookie.d.ts +1 -0
- package/dist/utils/cookie.js +8 -1
- package/package.json +3 -3
- package/rnw.js +1 -1
- package/src/components/carousel-component/CarouselComponent.stories.tsx +220 -0
- package/src/components/carousel-component/CarouselItem.tsx +26 -0
- package/src/components/carousel-component/DefaultNavigationArrow.tsx +37 -0
- package/src/components/carousel-component/DefaultPageDot.tsx +20 -0
- package/src/components/carousel-component/__tests__/CarouselComponent.test.tsx +259 -0
- package/src/components/carousel-component/__tests__/CarouselItem.test.tsx +140 -0
- package/src/components/carousel-component/__tests__/DefaultNavigationArrow.test.tsx +153 -0
- package/src/components/carousel-component/__tests__/DefaultPageDot.test.tsx +105 -0
- package/src/components/carousel-component/hooks/__tests__/useCarousel.test.ts +625 -0
- package/src/components/carousel-component/hooks/useCarousel.ts +229 -0
- package/src/components/carousel-component/index.tsx +92 -0
- package/src/components/carousel-component/styles.ts +188 -0
- package/src/components/carousel-component/types.ts +62 -0
- package/src/components/social-embed/SocialMediaEmbed.tsx +1 -111
- package/src/components/social-embed/SocialVendor.tsx +1 -46
- package/src/components/social-embed/__tests__/SocialVendor.test.tsx +0 -11
- package/src/components/social-embed/constants.ts +1 -2
- package/src/components/social-embed/helpers/socialMediaVendors.ts +0 -5
- package/src/components/social-embed/styles.ts +0 -30
- package/src/components/trip-cards/SkeletonCard.tsx +64 -0
- package/src/components/trip-cards/TripCard.tsx +172 -0
- package/src/components/trip-cards/TripCards.stories.tsx +224 -0
- package/src/components/trip-cards/TripCardsLayout.tsx +162 -0
- package/src/components/trip-cards/__tests__/SkeletonCard.test.tsx +169 -0
- package/src/components/trip-cards/__tests__/TripCard.test.tsx +255 -0
- package/src/components/trip-cards/__tests__/TripCardsLayout.test.tsx +671 -0
- package/src/components/trip-cards/__tests__/assets.test.tsx +206 -0
- package/src/components/trip-cards/__tests__/helpers.test.ts +273 -0
- package/src/components/trip-cards/__tests__/index.test.tsx +554 -0
- package/src/components/trip-cards/__tests__/mockData.test.ts +67 -0
- package/src/components/trip-cards/__tests__/skeletonStyles.test.tsx +256 -0
- package/src/components/trip-cards/assets/BoatIcon.tsx +17 -0
- package/src/components/trip-cards/assets/CalendarIcon.tsx +17 -0
- package/src/components/trip-cards/assets/ChevronRightIcon.tsx +20 -0
- package/src/components/trip-cards/assets/LocationIcon.tsx +17 -0
- package/src/components/trip-cards/assets/MoonIcon.tsx +17 -0
- package/src/components/trip-cards/assets/index.ts +7 -0
- package/src/components/trip-cards/helpers.ts +181 -0
- package/src/components/trip-cards/index.tsx +120 -0
- package/src/components/trip-cards/mockData.ts +345 -0
- package/src/components/trip-cards/skeletonStyles.ts +46 -0
- package/src/components/trip-cards/styles.ts +450 -0
- package/src/components/trip-cards/types.ts +130 -0
- package/src/index.ts +2 -0
- package/src/utils/cookie.ts +8 -0
- package/dist/components/social-embed/components/FacebookComponent.d.ts +0 -6
- package/dist/components/social-embed/components/FacebookComponent.js +0 -74
- package/src/components/social-embed/components/FacebookComponent.tsx +0 -93
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { FC, useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { CarouselComponent, CarouselItem } from '../carousel-component';
|
|
3
|
+
import { TripCardsLayoutProps } from './types';
|
|
4
|
+
import {
|
|
5
|
+
Container,
|
|
6
|
+
TitleSection,
|
|
7
|
+
TitleBar,
|
|
8
|
+
TitleContent,
|
|
9
|
+
Title,
|
|
10
|
+
Subtitle,
|
|
11
|
+
TitleLink,
|
|
12
|
+
StaticCardsGrid,
|
|
13
|
+
StyledLink
|
|
14
|
+
} from './styles';
|
|
15
|
+
import { ChevronRightIcon } from './assets';
|
|
16
|
+
import { tealiumTrackingHandler } from '../../helpers/tracking/TrackingHandler';
|
|
17
|
+
import { getPreferredEdition } from '../../utils/cookie';
|
|
18
|
+
|
|
19
|
+
export const TripCardsLayout: FC<TripCardsLayoutProps> = ({
|
|
20
|
+
element,
|
|
21
|
+
items,
|
|
22
|
+
CardComponent,
|
|
23
|
+
itemsPerPage,
|
|
24
|
+
widthContainerConfig = {},
|
|
25
|
+
widthItemConfig,
|
|
26
|
+
maxWidthItemConfig,
|
|
27
|
+
imgHeight,
|
|
28
|
+
forceStaticGrid
|
|
29
|
+
}) => {
|
|
30
|
+
const { titleurl, title, description } = element;
|
|
31
|
+
const gridRef = useRef<HTMLDivElement>(null);
|
|
32
|
+
const [hasOverflow, setHasOverflow] = useState(false);
|
|
33
|
+
const [isTabletMobile, setIsTabletMobile] = useState(false);
|
|
34
|
+
|
|
35
|
+
const isStaticGrid = items.length === 2 || forceStaticGrid;
|
|
36
|
+
|
|
37
|
+
// Handle responsive breakpoint detection
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const mediaQuery = window.matchMedia('(max-width: 1024px)');
|
|
40
|
+
const handleMediaChange = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
41
|
+
setIsTabletMobile(e.matches);
|
|
42
|
+
};
|
|
43
|
+
handleMediaChange(mediaQuery);
|
|
44
|
+
mediaQuery.addEventListener('change', handleMediaChange);
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
mediaQuery.removeEventListener('change', handleMediaChange);
|
|
48
|
+
};
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
useEffect(
|
|
52
|
+
() => {
|
|
53
|
+
if (!isStaticGrid || !gridRef.current) {
|
|
54
|
+
setHasOverflow(false);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const checkOverflow = () => {
|
|
59
|
+
if (gridRef.current) {
|
|
60
|
+
const isOverflowing =
|
|
61
|
+
gridRef.current.scrollWidth > gridRef.current.clientWidth;
|
|
62
|
+
setHasOverflow(isOverflowing);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
checkOverflow();
|
|
67
|
+
const resizeObserver = new ResizeObserver(checkOverflow);
|
|
68
|
+
resizeObserver.observe(gridRef.current);
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
resizeObserver.disconnect();
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
[isStaticGrid, items.length]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const shouldUseCarousel =
|
|
78
|
+
(isTabletMobile && items.length >= 3) || !isStaticGrid || hasOverflow;
|
|
79
|
+
|
|
80
|
+
const onTitleClick = () => {
|
|
81
|
+
tealiumTrackingHandler(
|
|
82
|
+
`trip cards component ${title}`,
|
|
83
|
+
'navigation',
|
|
84
|
+
'click',
|
|
85
|
+
undefined,
|
|
86
|
+
undefined,
|
|
87
|
+
{
|
|
88
|
+
app_content_location: getPreferredEdition()
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Container data-testid="trip-cards-container" {...widthContainerConfig}>
|
|
95
|
+
<TitleSection data-testid="title-section">
|
|
96
|
+
<TitleBar
|
|
97
|
+
data-testid="title-bar"
|
|
98
|
+
applyRightPadding={!!widthContainerConfig.mobile}
|
|
99
|
+
>
|
|
100
|
+
<TitleContent data-testid="title-content">
|
|
101
|
+
{titleurl ? (
|
|
102
|
+
<StyledLink
|
|
103
|
+
href={titleurl}
|
|
104
|
+
target="_blank"
|
|
105
|
+
rel="noopener noreferrer"
|
|
106
|
+
onClick={onTitleClick}
|
|
107
|
+
data-testid="trip-cards-title-link"
|
|
108
|
+
>
|
|
109
|
+
<Title data-testid="trip-cards-title">{title}</Title>
|
|
110
|
+
</StyledLink>
|
|
111
|
+
) : (
|
|
112
|
+
<Title data-testid="trip-cards-title">{title}</Title>
|
|
113
|
+
)}
|
|
114
|
+
<Subtitle data-testid="trip-cards-subtitle">{description}</Subtitle>
|
|
115
|
+
</TitleContent>
|
|
116
|
+
{titleurl && (
|
|
117
|
+
<TitleLink
|
|
118
|
+
href={titleurl}
|
|
119
|
+
target="_blank"
|
|
120
|
+
rel="noopener noreferrer"
|
|
121
|
+
data-testid="title-link"
|
|
122
|
+
onClick={onTitleClick}
|
|
123
|
+
>
|
|
124
|
+
<ChevronRightIcon />
|
|
125
|
+
</TitleLink>
|
|
126
|
+
)}
|
|
127
|
+
</TitleBar>
|
|
128
|
+
</TitleSection>
|
|
129
|
+
|
|
130
|
+
{!shouldUseCarousel ? (
|
|
131
|
+
<StaticCardsGrid data-testid="static-cards-grid" ref={gridRef}>
|
|
132
|
+
{items.map(item => (
|
|
133
|
+
<CardComponent
|
|
134
|
+
imgHeight={imgHeight}
|
|
135
|
+
key={item.id}
|
|
136
|
+
card={item.data}
|
|
137
|
+
isStaticGrid={true}
|
|
138
|
+
forceStaticGrid={forceStaticGrid}
|
|
139
|
+
/>
|
|
140
|
+
))}
|
|
141
|
+
</StaticCardsGrid>
|
|
142
|
+
) : (
|
|
143
|
+
<CarouselComponent
|
|
144
|
+
items={items.map(item => (
|
|
145
|
+
<CarouselItem
|
|
146
|
+
key={item.id}
|
|
147
|
+
widthItemConfig={widthItemConfig}
|
|
148
|
+
maxWidthItemConfig={maxWidthItemConfig}
|
|
149
|
+
>
|
|
150
|
+
<CardComponent card={item.data} imgHeight={imgHeight} />
|
|
151
|
+
</CarouselItem>
|
|
152
|
+
))}
|
|
153
|
+
options={{
|
|
154
|
+
itemsPerPage
|
|
155
|
+
}}
|
|
156
|
+
showArrows={!isStaticGrid}
|
|
157
|
+
showDots={true}
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
</Container>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { SkeletonCard } from '../SkeletonCard';
|
|
5
|
+
|
|
6
|
+
describe('SkeletonCard', () => {
|
|
7
|
+
it('renders without crashing', () => {
|
|
8
|
+
const { container } = render(<SkeletonCard />);
|
|
9
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('renders placeholder image', () => {
|
|
13
|
+
render(<SkeletonCard />);
|
|
14
|
+
const imageContainer = screen.getByTestId('skeleton-image-container');
|
|
15
|
+
expect(imageContainer).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders skeleton headline', () => {
|
|
19
|
+
render(<SkeletonCard />);
|
|
20
|
+
const skeletonHeadline = screen.getByTestId('skeleton-headline');
|
|
21
|
+
expect(skeletonHeadline).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders four skeleton data points in the list', () => {
|
|
25
|
+
render(<SkeletonCard />);
|
|
26
|
+
const dataPoints = [
|
|
27
|
+
screen.getByTestId('skeleton-data-point-1'),
|
|
28
|
+
screen.getByTestId('skeleton-data-point-2'),
|
|
29
|
+
screen.getByTestId('skeleton-data-point-3'),
|
|
30
|
+
screen.getByTestId('skeleton-data-point-4')
|
|
31
|
+
];
|
|
32
|
+
expect(dataPoints.length).toBe(4);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders skeleton lines for data points', () => {
|
|
36
|
+
render(<SkeletonCard />);
|
|
37
|
+
const skeletonLines = [
|
|
38
|
+
screen.getByTestId('skeleton-line-1'),
|
|
39
|
+
screen.getByTestId('skeleton-line-2'),
|
|
40
|
+
screen.getByTestId('skeleton-line-3'),
|
|
41
|
+
screen.getByTestId('skeleton-line-4'),
|
|
42
|
+
screen.getByTestId('skeleton-price-label')
|
|
43
|
+
];
|
|
44
|
+
expect(skeletonLines.length).toBe(5);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders skeleton price', () => {
|
|
48
|
+
render(<SkeletonCard />);
|
|
49
|
+
const skeletonPrice = screen.getByTestId('skeleton-price');
|
|
50
|
+
expect(skeletonPrice).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('renders skeleton button', () => {
|
|
54
|
+
render(<SkeletonCard />);
|
|
55
|
+
const skeletonButton = screen.getByTestId('skeleton-button');
|
|
56
|
+
expect(skeletonButton).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('has correct card structure', () => {
|
|
60
|
+
render(<SkeletonCard />);
|
|
61
|
+
const cardContainer = screen.getByTestId('skeleton-card-container');
|
|
62
|
+
const cardContent = screen.getByTestId('skeleton-card-content');
|
|
63
|
+
const topContainer = screen.getByTestId('skeleton-top-container');
|
|
64
|
+
const bottomContainer = screen.getByTestId('skeleton-bottom-container');
|
|
65
|
+
|
|
66
|
+
expect(cardContainer).toBeInTheDocument();
|
|
67
|
+
expect(cardContent).toBeInTheDocument();
|
|
68
|
+
expect(topContainer).toBeInTheDocument();
|
|
69
|
+
expect(bottomContainer).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders price section with container', () => {
|
|
73
|
+
render(<SkeletonCard />);
|
|
74
|
+
const priceSection = screen.getByTestId('skeleton-price-section');
|
|
75
|
+
const priceContainer = screen.getByTestId('skeleton-price-container');
|
|
76
|
+
|
|
77
|
+
expect(priceSection).toBeInTheDocument();
|
|
78
|
+
expect(priceContainer).toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('renders data points list', () => {
|
|
82
|
+
render(<SkeletonCard />);
|
|
83
|
+
const dataPointsList = screen.getByTestId('skeleton-data-points-list');
|
|
84
|
+
expect(dataPointsList).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('skeleton lines are rendered for each data point', () => {
|
|
88
|
+
render(<SkeletonCard />);
|
|
89
|
+
const skeletonLines = [
|
|
90
|
+
screen.getByTestId('skeleton-line-1'),
|
|
91
|
+
screen.getByTestId('skeleton-line-2'),
|
|
92
|
+
screen.getByTestId('skeleton-line-3'),
|
|
93
|
+
screen.getByTestId('skeleton-line-4'),
|
|
94
|
+
screen.getByTestId('skeleton-price-label')
|
|
95
|
+
];
|
|
96
|
+
expect(skeletonLines.length).toBe(5);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('renders with correct skeleton styling', () => {
|
|
100
|
+
render(<SkeletonCard />);
|
|
101
|
+
|
|
102
|
+
expect(screen.getByTestId('skeleton-headline')).toBeInTheDocument();
|
|
103
|
+
expect(screen.getByTestId('skeleton-price')).toBeInTheDocument();
|
|
104
|
+
expect(screen.getByTestId('skeleton-button')).toBeInTheDocument();
|
|
105
|
+
// Check that skeleton lines are present
|
|
106
|
+
expect(screen.getByTestId('skeleton-line-1')).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('renders consistent structure', () => {
|
|
110
|
+
render(<SkeletonCard />);
|
|
111
|
+
|
|
112
|
+
expect(screen.getByTestId('skeleton-card-container')).toBeInTheDocument();
|
|
113
|
+
expect(screen.getByTestId('skeleton-image-container')).toBeInTheDocument();
|
|
114
|
+
expect(screen.getByTestId('skeleton-card-content')).toBeInTheDocument();
|
|
115
|
+
expect(screen.getByTestId('skeleton-top-container')).toBeInTheDocument();
|
|
116
|
+
expect(screen.getByTestId('skeleton-bottom-container')).toBeInTheDocument();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('can render multiple skeleton cards', () => {
|
|
120
|
+
render(
|
|
121
|
+
<>
|
|
122
|
+
<SkeletonCard />
|
|
123
|
+
<SkeletonCard />
|
|
124
|
+
<SkeletonCard />
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const cards = screen.getAllByTestId('skeleton-card-container');
|
|
129
|
+
expect(cards.length).toBe(3);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('renders skeleton elements in correct order', () => {
|
|
133
|
+
render(<SkeletonCard />);
|
|
134
|
+
|
|
135
|
+
const cardContent = screen.getByTestId('skeleton-card-content');
|
|
136
|
+
const topContainer = screen.getByTestId('skeleton-top-container');
|
|
137
|
+
const bottomContainer = screen.getByTestId('skeleton-bottom-container');
|
|
138
|
+
|
|
139
|
+
// Verify both containers exist as children of card content
|
|
140
|
+
expect(cardContent.contains(topContainer)).toBe(true);
|
|
141
|
+
expect(cardContent.contains(bottomContainer)).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('skeleton button has correct structure', () => {
|
|
145
|
+
render(<SkeletonCard />);
|
|
146
|
+
const skeletonButton = screen.getByTestId('skeleton-button');
|
|
147
|
+
|
|
148
|
+
expect(skeletonButton.tagName.toLowerCase()).toBe('div');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('skeleton lines have correct width props', () => {
|
|
152
|
+
render(<SkeletonCard />);
|
|
153
|
+
|
|
154
|
+
// Check that the skeleton lines are rendered
|
|
155
|
+
expect(screen.getByTestId('skeleton-line-1')).toBeInTheDocument();
|
|
156
|
+
expect(screen.getByTestId('skeleton-line-2')).toBeInTheDocument();
|
|
157
|
+
expect(screen.getByTestId('skeleton-line-3')).toBeInTheDocument();
|
|
158
|
+
expect(screen.getByTestId('skeleton-line-4')).toBeInTheDocument();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('renders without any interactive elements', () => {
|
|
162
|
+
const { container } = render(<SkeletonCard />);
|
|
163
|
+
const buttons = container.querySelectorAll('button');
|
|
164
|
+
const links = container.querySelectorAll('a');
|
|
165
|
+
|
|
166
|
+
expect(buttons.length).toBe(0);
|
|
167
|
+
expect(links.length).toBe(0);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { TripCard } from '../TripCard';
|
|
5
|
+
import { TripCardApiData } from '../types';
|
|
6
|
+
import { tealiumTrackingHandler } from '../../../helpers/tracking/TrackingHandler';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../../helpers/tracking/TrackingHandler', () => ({
|
|
9
|
+
tealiumTrackingHandler: jest.fn()
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const mockCard: TripCardApiData = {
|
|
13
|
+
cruise_id: '2074350',
|
|
14
|
+
image: 'https://example.com/image.jpg',
|
|
15
|
+
headline: 'Mediterranean from Barcelona',
|
|
16
|
+
date: 'Oct 2025 - Oct 2025',
|
|
17
|
+
duration: 'Seven Days',
|
|
18
|
+
route: 'Barcelona → Marseille',
|
|
19
|
+
ship: 'MSC Seaside',
|
|
20
|
+
price: '£2000',
|
|
21
|
+
logo: 'https://example.com/logo.png',
|
|
22
|
+
logo_url: 'https://example.com',
|
|
23
|
+
logo_name: 'MSC Cruises',
|
|
24
|
+
cta_url: 'https://example.com/cruise',
|
|
25
|
+
cta_text: 'View Itinerary'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe('TripCard', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders card with all required data', () => {
|
|
34
|
+
render(<TripCard card={mockCard} />);
|
|
35
|
+
|
|
36
|
+
expect(
|
|
37
|
+
screen.getByText('Mediterranean from Barcelona')
|
|
38
|
+
).toBeInTheDocument();
|
|
39
|
+
expect(screen.getByText('Oct 2025 - Oct 2025')).toBeInTheDocument();
|
|
40
|
+
expect(screen.getByText('Seven Days')).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByText('Barcelona → Marseille')).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText('MSC Seaside')).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByText('£2000')).toBeInTheDocument();
|
|
44
|
+
expect(screen.getByText('From')).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByText('View Itinerary')).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('renders image with correct src and alt', () => {
|
|
49
|
+
render(<TripCard card={mockCard} />);
|
|
50
|
+
|
|
51
|
+
const image = screen.getByAltText('Mediterranean from Barcelona');
|
|
52
|
+
expect(image).toHaveAttribute('src', 'https://example.com/image.jpg');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders offer label when provided', () => {
|
|
56
|
+
const cardWithOffer = {
|
|
57
|
+
...mockCard,
|
|
58
|
+
offer_label: 'Special Offer'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
render(<TripCard card={cardWithOffer} />);
|
|
62
|
+
|
|
63
|
+
expect(screen.getByText('Special Offer')).toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('renders gift banner when provided', () => {
|
|
67
|
+
const cardWithGift = {
|
|
68
|
+
...mockCard,
|
|
69
|
+
gift_banner: 'Free gift with purchase'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
render(<TripCard card={cardWithGift} />);
|
|
73
|
+
|
|
74
|
+
expect(screen.getByText('Free gift with purchase')).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('renders original price when provided', () => {
|
|
78
|
+
const cardWithOriginalPrice = {
|
|
79
|
+
...mockCard,
|
|
80
|
+
original_price: '£2500'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
render(<TripCard card={cardWithOriginalPrice} />);
|
|
84
|
+
|
|
85
|
+
expect(screen.getByText('£2500')).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not render "From" when price is "Enquire now"', () => {
|
|
89
|
+
const cardWithEnquire = {
|
|
90
|
+
...mockCard,
|
|
91
|
+
price: 'Enquire now'
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
render(<TripCard card={cardWithEnquire} />);
|
|
95
|
+
|
|
96
|
+
expect(screen.queryByText('From')).not.toBeInTheDocument();
|
|
97
|
+
expect(screen.getByText('Enquire now')).toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('renders all CTA links with correct href and target', () => {
|
|
101
|
+
render(<TripCard card={mockCard} />);
|
|
102
|
+
|
|
103
|
+
const links = screen.getAllByRole('link');
|
|
104
|
+
const imageLink = links[0];
|
|
105
|
+
const headlineLink = links[1];
|
|
106
|
+
const ctaButton = screen.getByText('View Itinerary').closest('a');
|
|
107
|
+
|
|
108
|
+
expect(imageLink).toHaveAttribute('href', 'https://example.com/cruise');
|
|
109
|
+
expect(imageLink).toHaveAttribute('target', '_blank');
|
|
110
|
+
expect(headlineLink).toHaveAttribute('href', 'https://example.com/cruise');
|
|
111
|
+
expect(ctaButton).toHaveAttribute('href', 'https://example.com/cruise');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('renders logo with link when provided', () => {
|
|
115
|
+
render(<TripCard card={mockCard} />);
|
|
116
|
+
const logoLink = screen.getByRole('link', { name: /partner logo/i });
|
|
117
|
+
expect(logoLink).toHaveAttribute('href', 'https://example.com');
|
|
118
|
+
const logoImg = screen.getByAltText('Partner logo');
|
|
119
|
+
expect(logoImg).toHaveAttribute('src', 'https://example.com/logo.png');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('shows card image after it loads', () => {
|
|
123
|
+
render(<TripCard card={mockCard} />);
|
|
124
|
+
|
|
125
|
+
const cardImage = screen.getByAltText('Mediterranean from Barcelona');
|
|
126
|
+
expect(cardImage).toHaveStyle({ display: 'none' });
|
|
127
|
+
fireEvent.load(cardImage);
|
|
128
|
+
expect(cardImage).toHaveStyle({ display: 'block' });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('Tracking', () => {
|
|
132
|
+
it('calls tealiumTrackingHandler when headline is clicked', () => {
|
|
133
|
+
render(<TripCard card={mockCard} />);
|
|
134
|
+
|
|
135
|
+
const headlineLink = screen.getAllByRole('link')[1];
|
|
136
|
+
fireEvent.click(headlineLink);
|
|
137
|
+
|
|
138
|
+
expect(tealiumTrackingHandler).toHaveBeenCalledWith(
|
|
139
|
+
'trip card: Mediterranean from Barcelona',
|
|
140
|
+
'navigation',
|
|
141
|
+
'click',
|
|
142
|
+
'Mediterranean from Barcelona',
|
|
143
|
+
undefined,
|
|
144
|
+
{
|
|
145
|
+
app_content_location: expect.any(String)
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('calls tealiumTrackingHandler when image is clicked', () => {
|
|
151
|
+
render(<TripCard card={mockCard} />);
|
|
152
|
+
|
|
153
|
+
const imageLink = screen.getAllByRole('link')[0];
|
|
154
|
+
fireEvent.click(imageLink);
|
|
155
|
+
|
|
156
|
+
expect(tealiumTrackingHandler).toHaveBeenCalledWith(
|
|
157
|
+
'trip card: img click',
|
|
158
|
+
'navigation',
|
|
159
|
+
'click',
|
|
160
|
+
'Mediterranean from Barcelona',
|
|
161
|
+
undefined,
|
|
162
|
+
{
|
|
163
|
+
app_content_location: expect.any(String)
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('calls tealiumTrackingHandler when CTA button is clicked', () => {
|
|
169
|
+
render(<TripCard card={mockCard} />);
|
|
170
|
+
|
|
171
|
+
const ctaButton = screen.getByText('View Itinerary');
|
|
172
|
+
fireEvent.click(ctaButton);
|
|
173
|
+
|
|
174
|
+
expect(tealiumTrackingHandler).toHaveBeenCalledWith(
|
|
175
|
+
'trip card: View Itinerary',
|
|
176
|
+
'navigation',
|
|
177
|
+
'click',
|
|
178
|
+
'Mediterranean from Barcelona',
|
|
179
|
+
undefined,
|
|
180
|
+
{
|
|
181
|
+
app_content_location: expect.any(String)
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('calls tealiumTrackingHandler with default CTA text when not provided', () => {
|
|
187
|
+
const cardWithoutCTAText = {
|
|
188
|
+
...mockCard,
|
|
189
|
+
cta_text: undefined
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
render(<TripCard card={cardWithoutCTAText} />);
|
|
193
|
+
|
|
194
|
+
const ctaButton = screen.getByText('View Itinerary');
|
|
195
|
+
fireEvent.click(ctaButton);
|
|
196
|
+
|
|
197
|
+
expect(tealiumTrackingHandler).toHaveBeenCalledWith(
|
|
198
|
+
'trip card: View Itinerary',
|
|
199
|
+
'navigation',
|
|
200
|
+
'click',
|
|
201
|
+
'Mediterranean from Barcelona',
|
|
202
|
+
undefined,
|
|
203
|
+
{
|
|
204
|
+
app_content_location: expect.any(String)
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('calls tealiumTrackingHandler when logo is clicked with empty logo_name', () => {
|
|
210
|
+
const cardWithoutLogoName = {
|
|
211
|
+
...mockCard,
|
|
212
|
+
logo_name: ''
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
render(<TripCard card={cardWithoutLogoName} />);
|
|
216
|
+
|
|
217
|
+
const logoLink = screen.getByRole('link', { name: /partner logo/i });
|
|
218
|
+
fireEvent.click(logoLink);
|
|
219
|
+
|
|
220
|
+
expect(tealiumTrackingHandler).toHaveBeenCalledWith(
|
|
221
|
+
'trip card: ',
|
|
222
|
+
'navigation',
|
|
223
|
+
'click',
|
|
224
|
+
'Mediterranean from Barcelona',
|
|
225
|
+
undefined,
|
|
226
|
+
{
|
|
227
|
+
app_content_location: expect.any(String)
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('calls tealiumTrackingHandler with logo_name when provided', () => {
|
|
233
|
+
const cardWithLogoName = {
|
|
234
|
+
...mockCard,
|
|
235
|
+
logo_name: 'MSC Cruises'
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
render(<TripCard card={cardWithLogoName} />);
|
|
239
|
+
|
|
240
|
+
const logoLink = screen.getByRole('link', { name: /partner logo/i });
|
|
241
|
+
fireEvent.click(logoLink);
|
|
242
|
+
|
|
243
|
+
expect(tealiumTrackingHandler).toHaveBeenCalledWith(
|
|
244
|
+
'trip card: MSC Cruises',
|
|
245
|
+
'navigation',
|
|
246
|
+
'click',
|
|
247
|
+
'Mediterranean from Barcelona',
|
|
248
|
+
undefined,
|
|
249
|
+
{
|
|
250
|
+
app_content_location: expect.any(String)
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|