@windstream/react-shared-components 0.2.41 → 0.2.43
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/contentful/index.d.ts +1 -1
- package/dist/contentful/index.esm.js +2 -2
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +3 -3
- package/dist/contentful/index.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/next/index.esm.js +1 -1
- package/dist/next/index.esm.js.map +1 -1
- package/dist/next/index.js +1 -1
- package/dist/next/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/index.esm.js +1 -1
- package/dist/utils/index.esm.js.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/animation-wrapper/index.test.tsx +6 -6
- package/src/components/link/index.test.tsx +2 -2
- package/src/components/link/index.tsx +1 -1
- package/src/components/video-link/index.test.tsx +43 -4
- package/src/components/video-link/index.tsx +32 -4
- package/src/components/video-link/types.ts +1 -0
- package/src/contentful/blocks/blogs-grid/index.test.tsx +45 -14
- package/src/contentful/blocks/email-input-block/index.test.tsx +32 -41
- package/src/contentful/blocks/find-kinetic/index.test.tsx +3 -2
- package/src/contentful/blocks/image-promo-bar/index.test.tsx +55 -10
- package/src/contentful/blocks/image-promo-bar/index.tsx +5 -2
- package/src/contentful/blocks/image-promo-bar/types.ts +1 -1
- package/src/contentful/blocks/primary-hero/index.tsx +53 -41
- package/src/hooks/contentful/use-contentful-rich-text.test.tsx +4 -3
- package/src/hooks/contentful/use-contentful-rich-text.tsx +69 -9
package/package.json
CHANGED
|
@@ -359,7 +359,7 @@ describe("AnimationWrapper", () => {
|
|
|
359
359
|
expect(transition.duration).toBe(1);
|
|
360
360
|
});
|
|
361
361
|
|
|
362
|
-
it("
|
|
362
|
+
it("merges custom whileHover with the default animation preset", () => {
|
|
363
363
|
render(
|
|
364
364
|
<AnimationWrapper
|
|
365
365
|
disableAnimation={false}
|
|
@@ -372,10 +372,10 @@ describe("AnimationWrapper", () => {
|
|
|
372
372
|
const whileHover = JSON.parse(
|
|
373
373
|
el.getAttribute("data-while-hover") || "{}"
|
|
374
374
|
);
|
|
375
|
-
expect(whileHover).toEqual({ rotate: 10 });
|
|
375
|
+
expect(whileHover).toEqual({ scale: 1.05, rotate: 10 });
|
|
376
376
|
});
|
|
377
377
|
|
|
378
|
-
it("
|
|
378
|
+
it("merges custom whileTap with the default animation preset", () => {
|
|
379
379
|
render(
|
|
380
380
|
<AnimationWrapper disableAnimation={false} whileTap={{ rotate: -5 }}>
|
|
381
381
|
<div data-testid="child">Tap</div>
|
|
@@ -383,10 +383,10 @@ describe("AnimationWrapper", () => {
|
|
|
383
383
|
);
|
|
384
384
|
const el = screen.getByTestId("child");
|
|
385
385
|
const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
|
|
386
|
-
expect(whileTap).toEqual({ rotate: -5 });
|
|
386
|
+
expect(whileTap).toEqual({ scale: 0.95, rotate: -5 });
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
-
it("
|
|
389
|
+
it("merges custom transition with the default animation preset", () => {
|
|
390
390
|
render(
|
|
391
391
|
<AnimationWrapper
|
|
392
392
|
disableAnimation={false}
|
|
@@ -399,7 +399,7 @@ describe("AnimationWrapper", () => {
|
|
|
399
399
|
const transition = JSON.parse(
|
|
400
400
|
el.getAttribute("data-transition") || "{}"
|
|
401
401
|
);
|
|
402
|
-
expect(transition).toEqual({ delay: 0.5 });
|
|
402
|
+
expect(transition).toEqual({ duration: 0.15, delay: 0.5 });
|
|
403
403
|
});
|
|
404
404
|
});
|
|
405
405
|
|
|
@@ -155,14 +155,14 @@ describe("Link", () => {
|
|
|
155
155
|
expect(el).toHaveAttribute("target", "_blank");
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
it("adds rel=noopener
|
|
158
|
+
it("adds rel=noopener for external links", () => {
|
|
159
159
|
render(
|
|
160
160
|
<Link href="https://example.com" external={true}>
|
|
161
161
|
External
|
|
162
162
|
</Link>
|
|
163
163
|
);
|
|
164
164
|
const el = screen.getByText("External").closest("a");
|
|
165
|
-
expect(el).toHaveAttribute("rel", "noopener
|
|
165
|
+
expect(el).toHaveAttribute("rel", "noopener");
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
it("does not add target for disabled external links", () => {
|
|
@@ -55,20 +55,59 @@ describe("VideoLink", () => {
|
|
|
55
55
|
expect(container.innerHTML).toBe("");
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it("renders nothing when
|
|
58
|
+
it("renders nothing when there is no video and no image to fall back to", () => {
|
|
59
|
+
const { container } = render(<VideoLink videoLink={{}} />);
|
|
60
|
+
expect(container.innerHTML).toBe("");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("renders the video link's own poster as a static image when there is no video", () => {
|
|
64
|
+
render(<VideoLink videoLink={{ image: "thumb.jpg" }} />);
|
|
65
|
+
const img = screen.getByAltText("section-image");
|
|
66
|
+
expect(img).toHaveAttribute("src", "thumb.jpg");
|
|
67
|
+
expect(screen.queryByTestId("play-button")).not.toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("ignores an unrelated fallbackImage-like prop - only videoLink.image is used as the poster", () => {
|
|
71
|
+
const { container } = render(
|
|
72
|
+
// @ts-expect-error - fallbackImage is intentionally not a supported prop
|
|
73
|
+
<VideoLink videoLink={{}} fallbackImage="https://img.test/hero.jpg" />
|
|
74
|
+
);
|
|
75
|
+
expect(container.innerHTML).toBe("");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("renders nothing when videoSourceType is 'contentful' but videoAssetUrl is missing and there is no image", () => {
|
|
59
79
|
const { container } = render(
|
|
60
|
-
<VideoLink videoLink={{
|
|
80
|
+
<VideoLink videoLink={{ videoSourceType: "contentful" }} />
|
|
61
81
|
);
|
|
62
82
|
expect(container.innerHTML).toBe("");
|
|
63
83
|
});
|
|
64
84
|
|
|
65
|
-
it("
|
|
85
|
+
it("falls back to videoLink.link when videoSourceType is 'contentful' but videoAssetUrl is missing", () => {
|
|
66
86
|
const { container } = render(
|
|
87
|
+
<VideoLink
|
|
88
|
+
videoLink={{
|
|
89
|
+
videoSourceType: "contentful",
|
|
90
|
+
link: "https://youtube.com/watch?v=123",
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
clickVideoSection(container);
|
|
95
|
+
expect(screen.getByTestId("youtube-embed")).toHaveAttribute(
|
|
96
|
+
"data-link",
|
|
97
|
+
"https://youtube.com/watch?v=123"
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("falls back to the poster when videoSourceType is 'contentful' but videoAssetUrl is missing", () => {
|
|
102
|
+
render(
|
|
67
103
|
<VideoLink
|
|
68
104
|
videoLink={{ videoSourceType: "contentful", image: "thumb.jpg" }}
|
|
69
105
|
/>
|
|
70
106
|
);
|
|
71
|
-
expect(
|
|
107
|
+
expect(screen.getByAltText("section-image")).toHaveAttribute(
|
|
108
|
+
"src",
|
|
109
|
+
"thumb.jpg"
|
|
110
|
+
);
|
|
72
111
|
});
|
|
73
112
|
|
|
74
113
|
it("renders preview image and play button when videoLink has link and image", () => {
|
|
@@ -14,6 +14,7 @@ export const VideoLink: React.FC<VideoLinkComponentProps> = ({
|
|
|
14
14
|
imageWidth = 660,
|
|
15
15
|
imageHeight = 660,
|
|
16
16
|
useOriginalImageAspectRatio = false,
|
|
17
|
+
isAboveTheFold = false,
|
|
17
18
|
}) => {
|
|
18
19
|
const [activeVideo, setActiveVideo] = useState("");
|
|
19
20
|
const [isHovered, setIsHovered] = useState(false);
|
|
@@ -24,8 +25,35 @@ export const VideoLink: React.FC<VideoLinkComponentProps> = ({
|
|
|
24
25
|
? videoLink?.videoAssetUrl
|
|
25
26
|
: videoLink?.link) ?? "";
|
|
26
27
|
|
|
28
|
+
// The video's own poster - never the promo bar's plain image field.
|
|
29
|
+
const posterImage = videoLink?.image;
|
|
30
|
+
|
|
27
31
|
if (!videoHref) {
|
|
28
|
-
|
|
32
|
+
if (!posterImage) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={cx(
|
|
38
|
+
!useOriginalImageAspectRatio
|
|
39
|
+
? "relative aspect-[16/9] w-full overflow-hidden rounded-image lg:h-[486px] xl:aspect-square xl:max-h-[486px] xl:w-[486px]"
|
|
40
|
+
: "overflow-hidden rounded-image"
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
<NextImage
|
|
44
|
+
src={posterImage}
|
|
45
|
+
alt="section-image"
|
|
46
|
+
priority={isAboveTheFold}
|
|
47
|
+
fill={!useOriginalImageAspectRatio}
|
|
48
|
+
width={!useOriginalImageAspectRatio ? undefined : imageWidth || 660}
|
|
49
|
+
height={!useOriginalImageAspectRatio ? undefined : imageHeight || 660}
|
|
50
|
+
sizes="(max-width: 430px) 100vw, (max-width: 834px) 100vw, 50vw"
|
|
51
|
+
className={cx(
|
|
52
|
+
!useOriginalImageAspectRatio ? "object-cover" : "w-full"
|
|
53
|
+
)}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
29
57
|
}
|
|
30
58
|
|
|
31
59
|
const videoPopup = videoLink?.videoPopup ?? false;
|
|
@@ -46,7 +74,7 @@ export const VideoLink: React.FC<VideoLinkComponentProps> = ({
|
|
|
46
74
|
<ContentfulVideoEmbed
|
|
47
75
|
videoAssetUrl={videoLink?.videoAssetUrl}
|
|
48
76
|
videoAssetType={videoLink?.videoAssetType}
|
|
49
|
-
image={
|
|
77
|
+
image={posterImage}
|
|
50
78
|
title={videoLink?.title}
|
|
51
79
|
description={videoLink?.description}
|
|
52
80
|
autoplay={isPlaying}
|
|
@@ -79,9 +107,9 @@ export const VideoLink: React.FC<VideoLinkComponentProps> = ({
|
|
|
79
107
|
"relative w-full transition-opacity duration-300"
|
|
80
108
|
)}
|
|
81
109
|
>
|
|
82
|
-
{
|
|
110
|
+
{posterImage && (
|
|
83
111
|
<NextImage
|
|
84
|
-
src={
|
|
112
|
+
src={posterImage}
|
|
85
113
|
alt="Video preview"
|
|
86
114
|
width={imageWidth || 660}
|
|
87
115
|
height={imageHeight || 660}
|
|
@@ -14,17 +14,31 @@ jest.mock("next/navigation", () => ({
|
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
jest.mock("@shared/components/pagination", () => ({
|
|
17
|
-
Pagination: ({
|
|
17
|
+
Pagination: ({
|
|
18
|
+
currentPage,
|
|
19
|
+
totalPages,
|
|
20
|
+
onPageChange,
|
|
21
|
+
getPageHref,
|
|
22
|
+
ariaLabel,
|
|
23
|
+
}: any) => (
|
|
18
24
|
<nav aria-label={ariaLabel} data-testid="pagination">
|
|
19
25
|
<span>
|
|
20
26
|
Page {currentPage} of {totalPages}
|
|
21
27
|
</span>
|
|
22
|
-
<
|
|
28
|
+
<a
|
|
29
|
+
data-testid="page-next"
|
|
30
|
+
href={getPageHref?.(2)}
|
|
31
|
+
onClick={() => onPageChange(2)}
|
|
32
|
+
>
|
|
23
33
|
Next
|
|
24
|
-
</
|
|
25
|
-
<
|
|
34
|
+
</a>
|
|
35
|
+
<a
|
|
36
|
+
data-testid="page-first"
|
|
37
|
+
href={getPageHref?.(1)}
|
|
38
|
+
onClick={() => onPageChange(1)}
|
|
39
|
+
>
|
|
26
40
|
First
|
|
27
|
-
</
|
|
41
|
+
</a>
|
|
28
42
|
</nav>
|
|
29
43
|
),
|
|
30
44
|
}));
|
|
@@ -222,21 +236,38 @@ describe("BlogGrid", () => {
|
|
|
222
236
|
});
|
|
223
237
|
|
|
224
238
|
describe("Pagination", () => {
|
|
225
|
-
it("
|
|
239
|
+
it("builds page href with page param", () => {
|
|
226
240
|
render(<BlogGrid {...defaultProps} />);
|
|
227
|
-
|
|
228
|
-
|
|
241
|
+
expect(screen.getByTestId("page-next")).toHaveAttribute(
|
|
242
|
+
"href",
|
|
243
|
+
"/blog?page=2"
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("removes page param when building href for page 1", () => {
|
|
248
|
+
mockSearchParams = new URLSearchParams("page=2");
|
|
249
|
+
render(<BlogGrid {...defaultProps} currentPage={2} />);
|
|
250
|
+
expect(screen.getByTestId("page-first")).toHaveAttribute("href", "/blog");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("keeps other params when building page href", () => {
|
|
254
|
+
mockSearchParams = new URLSearchParams("category=tech");
|
|
255
|
+
render(<BlogGrid {...defaultProps} />);
|
|
256
|
+
expect(screen.getByTestId("page-first")).toHaveAttribute(
|
|
257
|
+
"href",
|
|
258
|
+
"/blog?category=tech"
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("scrolls to top when currentPage changes", () => {
|
|
263
|
+
const { rerender } = render(<BlogGrid {...defaultProps} />);
|
|
264
|
+
expect(window.scrollTo).not.toHaveBeenCalled();
|
|
265
|
+
rerender(<BlogGrid {...defaultProps} currentPage={2} />);
|
|
229
266
|
expect(window.scrollTo).toHaveBeenCalledWith({
|
|
230
267
|
top: 0,
|
|
231
268
|
behavior: "smooth",
|
|
232
269
|
});
|
|
233
270
|
});
|
|
234
|
-
|
|
235
|
-
it("removes page param when navigating to page 1", () => {
|
|
236
|
-
render(<BlogGrid {...defaultProps} currentPage={2} />);
|
|
237
|
-
fireEvent.click(screen.getByTestId("page-first"));
|
|
238
|
-
expect(mockPush).toHaveBeenCalledWith("/blog?", { scroll: false });
|
|
239
|
-
});
|
|
240
271
|
});
|
|
241
272
|
|
|
242
273
|
describe("Cover image handling", () => {
|
|
@@ -99,24 +99,27 @@ describe("EmailInputBlock", () => {
|
|
|
99
99
|
const input = screen.getByPlaceholderText("Enter your email here");
|
|
100
100
|
fireEvent.change(input, { target: { value: "invalid" } });
|
|
101
101
|
fireEvent.click(screen.getByText("Sign me up"));
|
|
102
|
-
expect(
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
expect(screen.getByText("Invalid email address")).toBeInTheDocument();
|
|
103
|
+
expect(defaultProps.onSubmit).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("shows required error for empty email", () => {
|
|
107
|
+
render(<EmailInputBlock {...defaultProps} />);
|
|
108
|
+
fireEvent.click(screen.getByText("Sign me up"));
|
|
109
|
+
expect(screen.getByText("Email is required")).toBeInTheDocument();
|
|
105
110
|
expect(defaultProps.onSubmit).not.toHaveBeenCalled();
|
|
106
111
|
});
|
|
107
112
|
|
|
108
|
-
it("clears error when user types after error", () => {
|
|
113
|
+
it("clears error when user types a valid email after error", () => {
|
|
109
114
|
render(<EmailInputBlock {...defaultProps} />);
|
|
110
115
|
const input = screen.getByPlaceholderText("Enter your email here");
|
|
111
116
|
fireEvent.change(input, { target: { value: "invalid" } });
|
|
112
117
|
fireEvent.click(screen.getByText("Sign me up"));
|
|
113
|
-
expect(
|
|
114
|
-
screen.getByText("Please enter a valid email address.")
|
|
115
|
-
).toBeInTheDocument();
|
|
118
|
+
expect(screen.getByText("Invalid email address")).toBeInTheDocument();
|
|
116
119
|
|
|
117
|
-
fireEvent.change(input, { target: { value: "
|
|
120
|
+
fireEvent.change(input, { target: { value: "user@test.com" } });
|
|
118
121
|
expect(
|
|
119
|
-
screen.queryByText("
|
|
122
|
+
screen.queryByText("Invalid email address")
|
|
120
123
|
).not.toBeInTheDocument();
|
|
121
124
|
});
|
|
122
125
|
|
|
@@ -148,62 +151,50 @@ describe("EmailInputBlock", () => {
|
|
|
148
151
|
expect(screen.queryByText("Thanks!")).not.toBeInTheDocument();
|
|
149
152
|
});
|
|
150
153
|
|
|
151
|
-
it("applies green background by default", () => {
|
|
154
|
+
it("applies green theme background by default", () => {
|
|
152
155
|
const { container } = render(<EmailInputBlock {...defaultProps} />);
|
|
153
|
-
|
|
154
|
-
expect(inner).toBeInTheDocument();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("applies navy background class", () => {
|
|
158
|
-
const { container } = render(
|
|
159
|
-
<EmailInputBlock {...defaultProps} backgroundColor="navy" />
|
|
160
|
-
);
|
|
161
|
-
expect(container.querySelector(".bg-bg-fill-inverse")).toBeInTheDocument();
|
|
156
|
+
expect(container.querySelector(".bg-bg-fill-brand")).toBeInTheDocument();
|
|
162
157
|
});
|
|
163
158
|
|
|
164
|
-
it("applies blue background class", () => {
|
|
159
|
+
it("applies blue theme background class", () => {
|
|
165
160
|
const { container } = render(
|
|
166
|
-
<EmailInputBlock {...defaultProps}
|
|
161
|
+
<EmailInputBlock {...defaultProps} themeColor="blue" />
|
|
167
162
|
);
|
|
168
|
-
expect(
|
|
163
|
+
expect(
|
|
164
|
+
container.querySelector(".bg-bg-fill-brand-supporting")
|
|
165
|
+
).toBeInTheDocument();
|
|
169
166
|
});
|
|
170
167
|
|
|
171
|
-
it("applies
|
|
168
|
+
it("applies purple theme background class", () => {
|
|
172
169
|
const { container } = render(
|
|
173
|
-
<EmailInputBlock {...defaultProps}
|
|
170
|
+
<EmailInputBlock {...defaultProps} themeColor="purple" />
|
|
174
171
|
);
|
|
175
172
|
expect(
|
|
176
|
-
container.querySelector(".bg-bg-fill-brand-
|
|
173
|
+
container.querySelector(".bg-bg-fill-brand-tertiary")
|
|
177
174
|
).toBeInTheDocument();
|
|
178
175
|
});
|
|
179
176
|
|
|
180
|
-
it("applies white background class", () => {
|
|
181
|
-
const { container } = render(
|
|
182
|
-
|
|
183
|
-
);
|
|
184
|
-
expect(container.querySelector(".bg-white")).toBeInTheDocument();
|
|
177
|
+
it("applies white outer background class by default", () => {
|
|
178
|
+
const { container } = render(<EmailInputBlock {...defaultProps} />);
|
|
179
|
+
expect(container.querySelector("section")).toHaveClass("bg-bg");
|
|
185
180
|
});
|
|
186
181
|
|
|
187
|
-
it("uses white text for
|
|
182
|
+
it("uses white text for dark themes", () => {
|
|
188
183
|
render(
|
|
189
|
-
<EmailInputBlock
|
|
190
|
-
{...defaultProps}
|
|
191
|
-
backgroundColor="navy"
|
|
192
|
-
title="Navy title"
|
|
193
|
-
/>
|
|
184
|
+
<EmailInputBlock {...defaultProps} themeColor="blue" title="Blue title" />
|
|
194
185
|
);
|
|
195
|
-
expect(screen.getByText("
|
|
186
|
+
expect(screen.getByText("Blue title").className).toContain("text-white");
|
|
196
187
|
});
|
|
197
188
|
|
|
198
|
-
it("uses dark text for white
|
|
189
|
+
it("uses dark text for the white theme", () => {
|
|
199
190
|
render(
|
|
200
191
|
<EmailInputBlock
|
|
201
192
|
{...defaultProps}
|
|
202
|
-
|
|
203
|
-
title="
|
|
193
|
+
themeColor="white"
|
|
194
|
+
title="White title"
|
|
204
195
|
/>
|
|
205
196
|
);
|
|
206
|
-
expect(screen.getByText("
|
|
197
|
+
expect(screen.getByText("White title").className).toContain("text-text");
|
|
207
198
|
});
|
|
208
199
|
|
|
209
200
|
it("uses custom ctaText", () => {
|
|
@@ -260,12 +260,13 @@ describe("FindKinetic", () => {
|
|
|
260
260
|
expect(screen.queryAllByRole("list")).toHaveLength(0);
|
|
261
261
|
});
|
|
262
262
|
|
|
263
|
-
it("renders two column layout for mobile and
|
|
263
|
+
it("renders two column layout for mobile, three for tablet and four for desktop", () => {
|
|
264
264
|
const { container } = render(<FindKinetic list={mockList} />);
|
|
265
265
|
const lists = container.querySelectorAll("ul");
|
|
266
|
-
expect(lists).toHaveLength(
|
|
266
|
+
expect(lists).toHaveLength(3);
|
|
267
267
|
expect(lists[0]).toHaveClass("grid-cols-2");
|
|
268
268
|
expect(lists[1]).toHaveClass("md:grid-cols-3");
|
|
269
|
+
expect(lists[2]).toHaveClass("xl:grid-cols-4");
|
|
269
270
|
});
|
|
270
271
|
|
|
271
272
|
it("rearranges items in column-major order for 2-col layout", () => {
|
|
@@ -89,7 +89,6 @@ const defaultProps: ImagePromoBarProps = {
|
|
|
89
89
|
cta: null,
|
|
90
90
|
secondaryCta: null,
|
|
91
91
|
ctaDisclaimer: null,
|
|
92
|
-
videoLink: { link: "", image: "", videoPopup: false },
|
|
93
92
|
maxWidth: true,
|
|
94
93
|
color: "light",
|
|
95
94
|
};
|
|
@@ -182,15 +181,15 @@ describe("ImagePromoBar", () => {
|
|
|
182
181
|
const { container } = render(
|
|
183
182
|
<ImagePromoBar {...defaultProps} mediaPosition={true} />
|
|
184
183
|
);
|
|
185
|
-
expect(container.innerHTML).toContain("
|
|
184
|
+
expect(container.innerHTML).toContain("xl:flex-row-reverse");
|
|
186
185
|
});
|
|
187
186
|
|
|
188
187
|
it("applies lg:flex-row when mediaPosition is false", () => {
|
|
189
188
|
const { container } = render(
|
|
190
189
|
<ImagePromoBar {...defaultProps} mediaPosition={false} />
|
|
191
190
|
);
|
|
192
|
-
expect(container.innerHTML).toContain("
|
|
193
|
-
expect(container.innerHTML).not.toContain("
|
|
191
|
+
expect(container.innerHTML).toContain("xl:flex-row");
|
|
192
|
+
expect(container.innerHTML).not.toContain("xl:flex-row-reverse");
|
|
194
193
|
});
|
|
195
194
|
});
|
|
196
195
|
|
|
@@ -276,22 +275,68 @@ describe("ImagePromoBar", () => {
|
|
|
276
275
|
});
|
|
277
276
|
|
|
278
277
|
describe("Image", () => {
|
|
279
|
-
it("renders
|
|
278
|
+
it("renders the plain image when there is no videoLink content", () => {
|
|
280
279
|
render(
|
|
281
280
|
<ImagePromoBar {...defaultProps} image="https://img.test/hero.jpg" />
|
|
282
281
|
);
|
|
283
|
-
|
|
284
|
-
|
|
282
|
+
expect(screen.getByTestId("next-image")).toHaveAttribute(
|
|
283
|
+
"src",
|
|
284
|
+
"https://img.test/hero.jpg"
|
|
285
|
+
);
|
|
285
286
|
});
|
|
286
287
|
|
|
287
|
-
it("does not render image section when image is empty", () => {
|
|
288
|
+
it("does not render the image section when image is empty", () => {
|
|
288
289
|
render(<ImagePromoBar {...defaultProps} image="" />);
|
|
289
290
|
expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
|
|
290
291
|
});
|
|
292
|
+
|
|
293
|
+
it("does not render the plain image when videoLink has a valid href", () => {
|
|
294
|
+
render(
|
|
295
|
+
<ImagePromoBar
|
|
296
|
+
{...defaultProps}
|
|
297
|
+
image="https://img.test/hero.jpg"
|
|
298
|
+
videoLink={{ link: "https://youtube.com/watch?v=123" }}
|
|
299
|
+
/>
|
|
300
|
+
);
|
|
301
|
+
expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("does not render the plain image when videoLink has its own poster image, even without a valid href", () => {
|
|
305
|
+
render(
|
|
306
|
+
<ImagePromoBar
|
|
307
|
+
{...defaultProps}
|
|
308
|
+
image="https://img.test/hero.jpg"
|
|
309
|
+
videoLink={{ image: "thumb.jpg" }}
|
|
310
|
+
/>
|
|
311
|
+
);
|
|
312
|
+
expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("renders the plain image when no videoLink is attached at all", () => {
|
|
316
|
+
render(
|
|
317
|
+
<ImagePromoBar
|
|
318
|
+
{...defaultProps}
|
|
319
|
+
image="https://img.test/hero.jpg"
|
|
320
|
+
videoLink={undefined}
|
|
321
|
+
/>
|
|
322
|
+
);
|
|
323
|
+
expect(screen.getByTestId("next-image")).toBeInTheDocument();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("suppresses the plain image when an empty videoLink object is attached (VideoLink owns its own fallback from there)", () => {
|
|
327
|
+
render(
|
|
328
|
+
<ImagePromoBar
|
|
329
|
+
{...defaultProps}
|
|
330
|
+
image="https://img.test/hero.jpg"
|
|
331
|
+
videoLink={{}}
|
|
332
|
+
/>
|
|
333
|
+
);
|
|
334
|
+
expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
|
|
335
|
+
});
|
|
291
336
|
});
|
|
292
337
|
|
|
293
338
|
describe("Video", () => {
|
|
294
|
-
it("renders VideoLink
|
|
339
|
+
it("always renders VideoLink, passing videoLink through", () => {
|
|
295
340
|
render(
|
|
296
341
|
<ImagePromoBar
|
|
297
342
|
{...defaultProps}
|
|
@@ -317,7 +362,7 @@ describe("ImagePromoBar", () => {
|
|
|
317
362
|
);
|
|
318
363
|
});
|
|
319
364
|
|
|
320
|
-
it("renders VideoLink
|
|
365
|
+
it("still renders VideoLink when videoLink has no valid href (VideoLink owns its own poster fallback)", () => {
|
|
321
366
|
render(<ImagePromoBar {...defaultProps} videoLink={{}} />);
|
|
322
367
|
expect(screen.getByTestId("video-link")).toBeInTheDocument();
|
|
323
368
|
});
|
|
@@ -36,6 +36,8 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
|
|
|
36
36
|
renderCheckPlans,
|
|
37
37
|
enableAnimation = false,
|
|
38
38
|
}) => {
|
|
39
|
+
const hasVideoLink = Boolean(videoLink);
|
|
40
|
+
|
|
39
41
|
return (
|
|
40
42
|
<div className="component-container shared-component-ipb pb-16 pt-16">
|
|
41
43
|
<div
|
|
@@ -161,8 +163,8 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
|
|
|
161
163
|
)}
|
|
162
164
|
</div>
|
|
163
165
|
<aside className="flex w-full flex-1 shrink-0 items-center justify-center lg:w-auto">
|
|
164
|
-
{/* Media Section */}
|
|
165
|
-
{image && (
|
|
166
|
+
{/* Media Section: only when no Video Link is attached at all */}
|
|
167
|
+
{image && !hasVideoLink && (
|
|
166
168
|
<div
|
|
167
169
|
className={cx(
|
|
168
170
|
!useOriginalImageAspectRatio
|
|
@@ -196,6 +198,7 @@ export const ImagePromoBar: React.FC<ImagePromoBarProps> = ({
|
|
|
196
198
|
imageWidth={imageWidth}
|
|
197
199
|
imageHeight={imageHeight}
|
|
198
200
|
useOriginalImageAspectRatio={useOriginalImageAspectRatio}
|
|
201
|
+
isAboveTheFold={isAboveTheFold}
|
|
199
202
|
/>
|
|
200
203
|
</aside>
|
|
201
204
|
</div>
|