@windstream/react-shared-components 0.1.93 → 0.1.95

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.
Files changed (112) hide show
  1. package/dist/contentful/index.esm.js +2 -2
  2. package/dist/contentful/index.esm.js.map +1 -1
  3. package/dist/contentful/index.js +3 -3
  4. package/dist/contentful/index.js.map +1 -1
  5. package/dist/core.d.ts +2 -2
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.esm.js +1 -1
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/index.js +3 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/styles.css +1 -1
  12. package/dist/utils/index.esm.js +1 -1
  13. package/dist/utils/index.js +1 -1
  14. package/package.json +14 -8
  15. package/src/components/accordion/index.test.tsx +270 -0
  16. package/src/components/alert-card/index.test.tsx +152 -0
  17. package/src/components/animation-wrapper/index.test.tsx +424 -0
  18. package/src/components/brand-button/index.test.tsx +292 -0
  19. package/src/components/button/index.test.tsx +91 -0
  20. package/src/components/call-button/index.test.tsx +260 -0
  21. package/src/components/checkbox/index.test.tsx +252 -0
  22. package/src/components/checklist/index.test.tsx +231 -0
  23. package/src/components/checklist/index.tsx +64 -29
  24. package/src/components/checklist/types.ts +7 -1
  25. package/src/components/collapse/index.test.tsx +277 -0
  26. package/src/components/collapse/index.tsx +1 -0
  27. package/src/components/divider/index.test.tsx +53 -0
  28. package/src/components/image/index.test.tsx +174 -0
  29. package/src/components/input/index.test.tsx +348 -0
  30. package/src/components/link/index.test.tsx +199 -0
  31. package/src/components/list/index.test.tsx +166 -0
  32. package/src/components/material-icon/index.test.tsx +130 -0
  33. package/src/components/modal/index.test.tsx +310 -0
  34. package/src/components/next-image/index.test.tsx +406 -0
  35. package/src/components/pagination/index.test.tsx +521 -0
  36. package/src/components/radio-button/index.test.tsx +151 -0
  37. package/src/components/see-more/index.test.tsx +96 -0
  38. package/src/components/select/index.test.tsx +256 -0
  39. package/src/components/select-plan-button/index.test.tsx +173 -0
  40. package/src/components/skeleton/index.test.tsx +74 -0
  41. package/src/components/spinner/index.test.tsx +76 -0
  42. package/src/components/text/index.test.tsx +65 -0
  43. package/src/components/tooltip/index.test.tsx +50 -0
  44. package/src/components/view-cart-button/index.test.tsx +57 -0
  45. package/src/contentful/blocks/accordion/index.test.tsx +218 -0
  46. package/src/contentful/blocks/accordion/index.tsx +3 -1
  47. package/src/contentful/blocks/address-input-banner/index.test.tsx +132 -0
  48. package/src/contentful/blocks/anchored-bottom-banner/index.test.tsx +287 -0
  49. package/src/contentful/blocks/blogs-grid/BlogGrid.stories.tsx +5 -4
  50. package/src/contentful/blocks/blogs-grid/index.test.tsx +355 -0
  51. package/src/contentful/blocks/blogs-grid-base/index.test.tsx +274 -0
  52. package/src/contentful/blocks/breadcrumbs/index.test.tsx +281 -0
  53. package/src/contentful/blocks/button/index.test.tsx +339 -0
  54. package/src/contentful/blocks/callout/index.test.tsx +539 -0
  55. package/src/contentful/blocks/cards/blog-card/index.test.tsx +218 -0
  56. package/src/contentful/blocks/cards/floating-image-card/index.test.tsx +201 -0
  57. package/src/contentful/blocks/cards/full-image-card/index.test.tsx +216 -0
  58. package/src/contentful/blocks/cards/index.test.tsx +39 -0
  59. package/src/contentful/blocks/cards/product-card/index.test.tsx +263 -0
  60. package/src/contentful/blocks/cards/simple-card/index.test.tsx +364 -0
  61. package/src/contentful/blocks/cards/simple-card/index.tsx +1 -1
  62. package/src/contentful/blocks/cards/testimonial-card/index.test.tsx +180 -0
  63. package/src/contentful/blocks/carousel/helper.test.tsx +539 -0
  64. package/src/contentful/blocks/carousel/index.test.tsx +308 -0
  65. package/src/contentful/blocks/carousel/types.test.ts +16 -0
  66. package/src/contentful/blocks/cart-retention-banner/index.test.tsx +409 -0
  67. package/src/contentful/blocks/cart-retention-banner/index.tsx +4 -4
  68. package/src/contentful/blocks/comparison-table/index.test.tsx +114 -0
  69. package/src/contentful/blocks/cookiebanner/index.test.tsx +277 -0
  70. package/src/contentful/blocks/cta-callout/index.test.tsx +244 -0
  71. package/src/contentful/blocks/dynamic-tabs/index.test.tsx +240 -0
  72. package/src/contentful/blocks/email-input-block/index.test.tsx +213 -0
  73. package/src/contentful/blocks/email-input-block/index.tsx +40 -35
  74. package/src/contentful/blocks/find-kinetic/index.test.tsx +269 -0
  75. package/src/contentful/blocks/floating-banner/index.test.tsx +246 -0
  76. package/src/contentful/blocks/footer/index.test.tsx +302 -0
  77. package/src/contentful/blocks/image-promo-bar/helper.test.tsx +61 -0
  78. package/src/contentful/blocks/image-promo-bar/index.test.tsx +467 -0
  79. package/src/contentful/blocks/image-promo-bar/index.tsx +248 -246
  80. package/src/contentful/blocks/image-promo-bar/vimeo-embed.test.tsx +142 -0
  81. package/src/contentful/blocks/image-promo-bar/youtube-embed.test.tsx +104 -0
  82. package/src/contentful/blocks/modal/index.test.tsx +209 -0
  83. package/src/contentful/blocks/navigation/desktop-link-groups.tsx/index.test.tsx +208 -0
  84. package/src/contentful/blocks/navigation/index.test.tsx +924 -0
  85. package/src/contentful/blocks/navigation/mobile-link-groups.tsx/index.test.tsx +131 -0
  86. package/src/contentful/blocks/primary-hero/index.test.tsx +286 -0
  87. package/src/contentful/blocks/primary-hero/index.tsx +7 -4
  88. package/src/contentful/blocks/search-block/index.test.tsx +268 -0
  89. package/src/contentful/blocks/shape-background-wrapper/index.test.tsx +284 -0
  90. package/src/contentful/blocks/text/index.test.tsx +36 -0
  91. package/src/contentful/index.test.ts +45 -0
  92. package/src/global-mocks/contentful/to-document.ts +25 -0
  93. package/src/global-mocks/cookie.ts +48 -0
  94. package/src/global-mocks/cx.ts +37 -0
  95. package/src/global-mocks/index.ts +89 -0
  96. package/src/global-mocks/speed-card-bg.ts +27 -0
  97. package/src/global-mocks/utm.ts +49 -0
  98. package/src/hooks/contentful/use-contentful-rich-text.test.tsx +1758 -0
  99. package/src/hooks/contentful/use-contentful-rich-text.tsx +1 -1
  100. package/src/hooks/contentful/use-processed-check-list.test.tsx +277 -0
  101. package/src/hooks/use-body-scroll-lock.test.ts +134 -0
  102. package/src/hooks/use-carousel-swipe.test.ts +393 -0
  103. package/src/hooks/use-outside-click.test.ts +142 -0
  104. package/src/index.ts +1 -1
  105. package/src/next/index.test.ts +7 -0
  106. package/src/setupTests.ts +17 -11
  107. package/src/utils/contentful/to-document.test.ts +85 -0
  108. package/src/utils/cookie.test.ts +180 -0
  109. package/src/utils/cx.test.ts +90 -0
  110. package/src/utils/index.test.ts +115 -0
  111. package/src/utils/speed-card-bg.test.ts +46 -0
  112. package/src/utils/utm.test.ts +359 -0
@@ -0,0 +1,406 @@
1
+ import "@testing-library/jest-dom";
2
+
3
+ import React, { createRef, forwardRef } from "react";
4
+ import { NextImage } from "./index";
5
+
6
+ import { render, screen } from "@testing-library/react";
7
+
8
+ // Mock next/image - must use require-style reference due to hoisting
9
+ const mockNextImage = jest.fn();
10
+
11
+ jest.mock("next/image", () => {
12
+ const MockNextImage = forwardRef(function MockNextImage(
13
+ props: any,
14
+ ref: any
15
+ ) {
16
+ mockNextImage(props);
17
+ const rest = { ...props };
18
+ const loader = rest.loader;
19
+ delete rest.loader;
20
+ delete rest.unoptimized;
21
+ delete rest.priority;
22
+ delete rest.fill;
23
+ delete rest.sizes;
24
+ return (
25
+ <img
26
+ ref={ref}
27
+ data-testid="next-image"
28
+ data-unoptimized={props.unoptimized?.toString() || "false"}
29
+ data-has-loader={loader ? "true" : "false"}
30
+ {...rest}
31
+ />
32
+ );
33
+ });
34
+ return { __esModule: true, default: MockNextImage };
35
+ });
36
+
37
+ jest.mock("@shared/utils", () => ({
38
+ cx: (...args: any[]) => args.filter(Boolean).join(" "),
39
+ }));
40
+
41
+ describe("NextImage", () => {
42
+ beforeEach(() => {
43
+ mockNextImage.mockClear();
44
+ });
45
+
46
+ it("has displayName set to NextImage", () => {
47
+ expect(NextImage.displayName).toBe("NextImage");
48
+ });
49
+
50
+ describe("basic rendering", () => {
51
+ it("renders an image element", () => {
52
+ render(<NextImage src="/test.png" alt="Test" width={100} height={100} />);
53
+ expect(screen.getByTestId("next-image")).toBeInTheDocument();
54
+ });
55
+
56
+ it("passes src, alt, width, height props", () => {
57
+ render(
58
+ <NextImage src="/photo.jpg" alt="Photo" width={200} height={150} />
59
+ );
60
+ const img = screen.getByTestId("next-image");
61
+ expect(img).toHaveAttribute("src", "/photo.jpg");
62
+ expect(img).toHaveAttribute("alt", "Photo");
63
+ expect(img).toHaveAttribute("width", "200");
64
+ expect(img).toHaveAttribute("height", "150");
65
+ });
66
+
67
+ it("passes className to image", () => {
68
+ render(
69
+ <NextImage
70
+ src="/img.png"
71
+ alt="Img"
72
+ width={50}
73
+ height={50}
74
+ className="rounded-lg"
75
+ />
76
+ );
77
+ const img = screen.getByTestId("next-image");
78
+ expect(img).toHaveClass("rounded-lg");
79
+ });
80
+
81
+ it("passes additional props through", () => {
82
+ render(
83
+ <NextImage
84
+ src="/img.png"
85
+ alt="Img"
86
+ width={50}
87
+ height={50}
88
+ priority={true}
89
+ />
90
+ );
91
+ expect(mockNextImage).toHaveBeenCalledWith(
92
+ expect.objectContaining({ priority: true })
93
+ );
94
+ });
95
+ });
96
+
97
+ describe("ref forwarding", () => {
98
+ it("forwards ref to the underlying image", () => {
99
+ const ref = createRef<HTMLImageElement>();
100
+ render(
101
+ <NextImage ref={ref} src="/img.png" alt="Ref" width={50} height={50} />
102
+ );
103
+ expect(ref.current).toBeInstanceOf(HTMLImageElement);
104
+ });
105
+ });
106
+
107
+ describe("Contentful image detection", () => {
108
+ it("detects Contentful images by domain", () => {
109
+ render(
110
+ <NextImage
111
+ src="https://images.ctfassets.net/abc/image.png"
112
+ alt="Contentful"
113
+ width={100}
114
+ height={100}
115
+ />
116
+ );
117
+ const img = screen.getByTestId("next-image");
118
+ expect(img).toHaveAttribute("data-has-loader", "true");
119
+ expect(img).toHaveAttribute("data-unoptimized", "false");
120
+ });
121
+
122
+ it("does not use contentful loader for non-Contentful images", () => {
123
+ render(
124
+ <NextImage
125
+ src="https://example.com/photo.jpg"
126
+ alt="External"
127
+ width={100}
128
+ height={100}
129
+ />
130
+ );
131
+ const img = screen.getByTestId("next-image");
132
+ expect(img).toHaveAttribute("data-has-loader", "false");
133
+ });
134
+
135
+ it("does not use contentful loader for relative paths", () => {
136
+ render(
137
+ <NextImage
138
+ src="/local/image.png"
139
+ alt="Local"
140
+ width={100}
141
+ height={100}
142
+ />
143
+ );
144
+ const img = screen.getByTestId("next-image");
145
+ expect(img).toHaveAttribute("data-has-loader", "false");
146
+ });
147
+
148
+ it("handles non-string src gracefully", () => {
149
+ const staticImport = { src: "/static.png", height: 100, width: 100 };
150
+ render(
151
+ <NextImage
152
+ src={staticImport as any}
153
+ alt="Static"
154
+ width={100}
155
+ height={100}
156
+ />
157
+ );
158
+ const img = screen.getByTestId("next-image");
159
+ expect(img).toHaveAttribute("data-has-loader", "false");
160
+ });
161
+ });
162
+
163
+ describe("custom loader behavior", () => {
164
+ it("passes a loader function for Contentful images", () => {
165
+ render(
166
+ <NextImage
167
+ src="https://images.ctfassets.net/space/image.jpg"
168
+ alt="CF"
169
+ width={100}
170
+ height={100}
171
+ />
172
+ );
173
+ expect(mockNextImage).toHaveBeenCalledWith(
174
+ expect.objectContaining({
175
+ loader: expect.any(Function),
176
+ unoptimized: false,
177
+ })
178
+ );
179
+ });
180
+
181
+ it("loader appends width, quality, and format params", () => {
182
+ render(
183
+ <NextImage
184
+ src="https://images.ctfassets.net/space/photo.jpg"
185
+ alt="CF"
186
+ width={100}
187
+ height={100}
188
+ />
189
+ );
190
+ const callProps = mockNextImage.mock.calls[0][0];
191
+ const loader = callProps.loader;
192
+ const result = loader({
193
+ src: "https://images.ctfassets.net/space/photo.jpg",
194
+ width: 800,
195
+ quality: 75,
196
+ });
197
+ expect(result).toContain("w=800");
198
+ expect(result).toContain("q=75");
199
+ expect(result).toContain("fm=webp");
200
+ });
201
+
202
+ it("loader uses default quality of 90 when not specified", () => {
203
+ render(
204
+ <NextImage
205
+ src="https://images.ctfassets.net/space/photo.jpg"
206
+ alt="CF"
207
+ width={100}
208
+ height={100}
209
+ />
210
+ );
211
+ const callProps = mockNextImage.mock.calls[0][0];
212
+ const loader = callProps.loader;
213
+ const result = loader({
214
+ src: "https://images.ctfassets.net/space/photo.jpg",
215
+ width: 400,
216
+ quality: undefined,
217
+ });
218
+ expect(result).toContain("q=90");
219
+ });
220
+
221
+ it("loader preserves existing URL params", () => {
222
+ render(
223
+ <NextImage
224
+ src="https://images.ctfassets.net/space/photo.jpg?fit=fill"
225
+ alt="CF"
226
+ width={100}
227
+ height={100}
228
+ />
229
+ );
230
+ const callProps = mockNextImage.mock.calls[0][0];
231
+ const loader = callProps.loader;
232
+ const result = loader({
233
+ src: "https://images.ctfassets.net/space/photo.jpg?fit=fill",
234
+ width: 600,
235
+ quality: 80,
236
+ });
237
+ expect(result).toContain("fit=fill");
238
+ expect(result).toContain("w=600");
239
+ expect(result).toContain("q=80");
240
+ expect(result).toContain("fm=webp");
241
+ });
242
+ });
243
+
244
+ describe("SVG optimization handling", () => {
245
+ it("marks SVG from Contentful as unoptimized", () => {
246
+ render(
247
+ <NextImage
248
+ src="https://images.ctfassets.net/space/logo.svg"
249
+ alt="SVG"
250
+ width={100}
251
+ height={100}
252
+ />
253
+ );
254
+ const img = screen.getByTestId("next-image");
255
+ expect(img).toHaveAttribute("data-unoptimized", "true");
256
+ expect(img).toHaveAttribute("data-has-loader", "false");
257
+ });
258
+
259
+ it("does not use contentful loader for SVG images", () => {
260
+ render(
261
+ <NextImage
262
+ src="https://images.ctfassets.net/space/icon.svg"
263
+ alt="SVG Icon"
264
+ width={24}
265
+ height={24}
266
+ />
267
+ );
268
+ expect(mockNextImage).toHaveBeenCalledWith(
269
+ expect.objectContaining({
270
+ unoptimized: true,
271
+ })
272
+ );
273
+ expect(mockNextImage).not.toHaveBeenCalledWith(
274
+ expect.objectContaining({
275
+ loader: expect.any(Function),
276
+ })
277
+ );
278
+ });
279
+
280
+ it("handles SVG with query params from Contentful", () => {
281
+ render(
282
+ <NextImage
283
+ src="https://images.ctfassets.net/space/icon.svg?v=1"
284
+ alt="SVG"
285
+ width={50}
286
+ height={50}
287
+ />
288
+ );
289
+ const img = screen.getByTestId("next-image");
290
+ expect(img).toHaveAttribute("data-unoptimized", "true");
291
+ expect(img).toHaveAttribute("data-has-loader", "false");
292
+ });
293
+
294
+ it("handles SVG with uppercase extension from Contentful", () => {
295
+ render(
296
+ <NextImage
297
+ src="https://images.ctfassets.net/space/Logo.SVG"
298
+ alt="SVG"
299
+ width={100}
300
+ height={100}
301
+ />
302
+ );
303
+ const img = screen.getByTestId("next-image");
304
+ expect(img).toHaveAttribute("data-unoptimized", "true");
305
+ });
306
+
307
+ it("does not treat non-Contentful SVG specially", () => {
308
+ render(
309
+ <NextImage
310
+ src="https://example.com/icon.svg"
311
+ alt="Ext SVG"
312
+ width={24}
313
+ height={24}
314
+ />
315
+ );
316
+ const img = screen.getByTestId("next-image");
317
+ // Non-contentful SVG: no loader, unoptimized is false (falsy)
318
+ expect(img).toHaveAttribute("data-has-loader", "false");
319
+ expect(img).toHaveAttribute("data-unoptimized", "false");
320
+ });
321
+ });
322
+
323
+ describe("non-Contentful images", () => {
324
+ it("does not set unoptimized for regular external images", () => {
325
+ render(
326
+ <NextImage
327
+ src="https://cdn.example.com/photo.webp"
328
+ alt="WebP"
329
+ width={400}
330
+ height={300}
331
+ />
332
+ );
333
+ const img = screen.getByTestId("next-image");
334
+ expect(img).toHaveAttribute("data-has-loader", "false");
335
+ });
336
+
337
+ it("does not set unoptimized for local images", () => {
338
+ render(
339
+ <NextImage
340
+ src="/images/hero.png"
341
+ alt="Hero"
342
+ width={1200}
343
+ height={600}
344
+ />
345
+ );
346
+ const img = screen.getByTestId("next-image");
347
+ expect(img).toHaveAttribute("data-unoptimized");
348
+ });
349
+ });
350
+ });
351
+
352
+ describe("resolveDefaultExport (CJS/ESM interop)", () => {
353
+ afterEach(() => {
354
+ jest.resetModules();
355
+ });
356
+
357
+ it("unwraps doubly-wrapped default export", async () => {
358
+ jest.doMock("next/image", () => {
359
+ function MockImage(props: any) {
360
+ return <img {...props} />;
361
+ }
362
+ return { __esModule: true, default: { default: MockImage } };
363
+ });
364
+ jest.doMock("@shared/utils", () => ({
365
+ cx: (...args: any[]) => args.filter(Boolean).join(" "),
366
+ }));
367
+ const { NextImage } = await import("./index");
368
+ expect(NextImage).toBeDefined();
369
+ });
370
+
371
+ it("resolves when default export is already a function", async () => {
372
+ jest.doMock("next/image", () => {
373
+ function MockImage(props: any) {
374
+ return <img {...props} />;
375
+ }
376
+ return { __esModule: true, default: MockImage };
377
+ });
378
+ jest.doMock("@shared/utils", () => ({
379
+ cx: (...args: any[]) => args.filter(Boolean).join(" "),
380
+ }));
381
+ const { NextImage } = await import("./index");
382
+ expect(NextImage).toBeDefined();
383
+ });
384
+
385
+ it("returns current when loop exhausts iterations", async () => {
386
+ jest.doMock("next/image", () => {
387
+ const nested = {
388
+ default: {
389
+ default: {
390
+ default: {
391
+ default: {
392
+ default: { default: "not-a-function" },
393
+ },
394
+ },
395
+ },
396
+ },
397
+ };
398
+ return { __esModule: true, default: nested };
399
+ });
400
+ jest.doMock("@shared/utils", () => ({
401
+ cx: (...args: any[]) => args.filter(Boolean).join(" "),
402
+ }));
403
+ const { NextImage } = await import("./index");
404
+ expect(NextImage).toBeDefined();
405
+ });
406
+ });