@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,521 @@
1
+ import React from "react";
2
+ import { Pagination } from "./index";
3
+
4
+ import { fireEvent, render, screen } from "@testing-library/react";
5
+
6
+ // Mock dependencies
7
+ jest.mock("@shared/components/button", () => ({
8
+ Button: ({ children, onClick, disabled, className, ...rest }: any) => (
9
+ <button
10
+ onClick={onClick}
11
+ disabled={disabled}
12
+ className={className}
13
+ {...rest}
14
+ >
15
+ {children}
16
+ </button>
17
+ ),
18
+ }));
19
+
20
+ jest.mock("@shared/components/material-icon", () => ({
21
+ MaterialIcon: ({ name, ...rest }: any) => (
22
+ <span data-testid={`icon-${name}`} {...rest}>
23
+ {name}
24
+ </span>
25
+ ),
26
+ }));
27
+
28
+ describe("Pagination", () => {
29
+ const mockOnPageChange = jest.fn();
30
+
31
+ beforeEach(() => {
32
+ mockOnPageChange.mockClear();
33
+ });
34
+
35
+ describe("Rendering", () => {
36
+ it("renders the pagination nav with default aria-label", () => {
37
+ render(
38
+ <Pagination
39
+ currentPage={1}
40
+ totalPages={5}
41
+ onPageChange={mockOnPageChange}
42
+ />
43
+ );
44
+ expect(screen.getByRole("navigation")).toHaveAttribute(
45
+ "aria-label",
46
+ "Pagination"
47
+ );
48
+ });
49
+
50
+ it("renders with custom aria-label", () => {
51
+ render(
52
+ <Pagination
53
+ currentPage={1}
54
+ totalPages={5}
55
+ onPageChange={mockOnPageChange}
56
+ ariaLabel="Blog pages"
57
+ />
58
+ );
59
+ expect(screen.getByRole("navigation")).toHaveAttribute(
60
+ "aria-label",
61
+ "Blog pages"
62
+ );
63
+ });
64
+
65
+ it("renders previous and next navigation buttons", () => {
66
+ render(
67
+ <Pagination
68
+ currentPage={3}
69
+ totalPages={5}
70
+ onPageChange={mockOnPageChange}
71
+ />
72
+ );
73
+ expect(
74
+ screen.getByRole("button", { name: "Go to previous page" })
75
+ ).toBeInTheDocument();
76
+ expect(
77
+ screen.getByRole("button", { name: "Go to next page" })
78
+ ).toBeInTheDocument();
79
+ });
80
+
81
+ it("renders chevron icons in nav buttons", () => {
82
+ render(
83
+ <Pagination
84
+ currentPage={1}
85
+ totalPages={5}
86
+ onPageChange={mockOnPageChange}
87
+ />
88
+ );
89
+ expect(screen.getByTestId("icon-chevron_left")).toBeInTheDocument();
90
+ expect(screen.getByTestId("icon-chevron_right")).toBeInTheDocument();
91
+ });
92
+ });
93
+
94
+ describe("Button states", () => {
95
+ it("disables previous button on first page", () => {
96
+ render(
97
+ <Pagination
98
+ currentPage={1}
99
+ totalPages={5}
100
+ onPageChange={mockOnPageChange}
101
+ />
102
+ );
103
+ expect(
104
+ screen.getByRole("button", { name: "Go to previous page" })
105
+ ).toBeDisabled();
106
+ });
107
+
108
+ it("enables previous button when not on first page", () => {
109
+ render(
110
+ <Pagination
111
+ currentPage={3}
112
+ totalPages={5}
113
+ onPageChange={mockOnPageChange}
114
+ />
115
+ );
116
+ expect(
117
+ screen.getByRole("button", { name: "Go to previous page" })
118
+ ).not.toBeDisabled();
119
+ });
120
+
121
+ it("disables next button on last page", () => {
122
+ render(
123
+ <Pagination
124
+ currentPage={5}
125
+ totalPages={5}
126
+ onPageChange={mockOnPageChange}
127
+ />
128
+ );
129
+ expect(
130
+ screen.getByRole("button", { name: "Go to next page" })
131
+ ).toBeDisabled();
132
+ });
133
+
134
+ it("enables next button when not on last page", () => {
135
+ render(
136
+ <Pagination
137
+ currentPage={3}
138
+ totalPages={5}
139
+ onPageChange={mockOnPageChange}
140
+ />
141
+ );
142
+ expect(
143
+ screen.getByRole("button", { name: "Go to next page" })
144
+ ).not.toBeDisabled();
145
+ });
146
+
147
+ it("marks current page with aria-current='page'", () => {
148
+ render(
149
+ <Pagination
150
+ currentPage={3}
151
+ totalPages={5}
152
+ onPageChange={mockOnPageChange}
153
+ />
154
+ );
155
+ expect(
156
+ screen.getByRole("button", { name: "Go to page 3" })
157
+ ).toHaveAttribute("aria-current", "page");
158
+ });
159
+
160
+ it("does not set aria-current on non-current pages", () => {
161
+ render(
162
+ <Pagination
163
+ currentPage={3}
164
+ totalPages={5}
165
+ onPageChange={mockOnPageChange}
166
+ />
167
+ );
168
+ expect(
169
+ screen.getByRole("button", { name: "Go to page 1" })
170
+ ).not.toHaveAttribute("aria-current");
171
+ });
172
+
173
+ it("applies active class to current page button", () => {
174
+ render(
175
+ <Pagination
176
+ currentPage={2}
177
+ totalPages={5}
178
+ onPageChange={mockOnPageChange}
179
+ />
180
+ );
181
+ const activeBtn = screen.getByRole("button", { name: "Go to page 2" });
182
+ expect(activeBtn.className).toContain("bg-bg-surface-active");
183
+ expect(activeBtn.className).toContain("font-bold");
184
+ });
185
+
186
+ it("does not apply active class to non-current page buttons", () => {
187
+ render(
188
+ <Pagination
189
+ currentPage={2}
190
+ totalPages={5}
191
+ onPageChange={mockOnPageChange}
192
+ />
193
+ );
194
+ const inactiveBtn = screen.getByRole("button", {
195
+ name: "Go to page 1",
196
+ });
197
+ expect(inactiveBtn.className).not.toContain("bg-bg-surface-active");
198
+ });
199
+ });
200
+
201
+ describe("Page navigation clicks", () => {
202
+ it("calls onPageChange with previous page when previous button clicked", () => {
203
+ render(
204
+ <Pagination
205
+ currentPage={3}
206
+ totalPages={5}
207
+ onPageChange={mockOnPageChange}
208
+ />
209
+ );
210
+ fireEvent.click(
211
+ screen.getByRole("button", { name: "Go to previous page" })
212
+ );
213
+ expect(mockOnPageChange).toHaveBeenCalledWith(2);
214
+ });
215
+
216
+ it("calls onPageChange with next page when next button clicked", () => {
217
+ render(
218
+ <Pagination
219
+ currentPage={3}
220
+ totalPages={5}
221
+ onPageChange={mockOnPageChange}
222
+ />
223
+ );
224
+ fireEvent.click(screen.getByRole("button", { name: "Go to next page" }));
225
+ expect(mockOnPageChange).toHaveBeenCalledWith(4);
226
+ });
227
+
228
+ it("calls onPageChange with clicked page number", () => {
229
+ render(
230
+ <Pagination
231
+ currentPage={1}
232
+ totalPages={5}
233
+ onPageChange={mockOnPageChange}
234
+ />
235
+ );
236
+ fireEvent.click(screen.getByRole("button", { name: "Go to page 4" }));
237
+ expect(mockOnPageChange).toHaveBeenCalledWith(4);
238
+ });
239
+
240
+ it("does not call onPageChange when disabled previous button is clicked", () => {
241
+ render(
242
+ <Pagination
243
+ currentPage={1}
244
+ totalPages={5}
245
+ onPageChange={mockOnPageChange}
246
+ />
247
+ );
248
+ fireEvent.click(
249
+ screen.getByRole("button", { name: "Go to previous page" })
250
+ );
251
+ expect(mockOnPageChange).not.toHaveBeenCalled();
252
+ });
253
+
254
+ it("does not call onPageChange when disabled next button is clicked", () => {
255
+ render(
256
+ <Pagination
257
+ currentPage={5}
258
+ totalPages={5}
259
+ onPageChange={mockOnPageChange}
260
+ />
261
+ );
262
+ fireEvent.click(screen.getByRole("button", { name: "Go to next page" }));
263
+ expect(mockOnPageChange).not.toHaveBeenCalled();
264
+ });
265
+ });
266
+
267
+ describe("buildPageItems algorithm — total pages <= 7", () => {
268
+ it("renders all pages for totalPages=1", () => {
269
+ render(
270
+ <Pagination
271
+ currentPage={1}
272
+ totalPages={1}
273
+ onPageChange={mockOnPageChange}
274
+ />
275
+ );
276
+ expect(
277
+ screen.getByRole("button", { name: "Go to page 1" })
278
+ ).toBeInTheDocument();
279
+ expect(screen.queryByText("…")).not.toBeInTheDocument();
280
+ });
281
+
282
+ it("renders all pages for totalPages=5", () => {
283
+ render(
284
+ <Pagination
285
+ currentPage={1}
286
+ totalPages={5}
287
+ onPageChange={mockOnPageChange}
288
+ />
289
+ );
290
+ for (let i = 1; i <= 5; i++) {
291
+ expect(
292
+ screen.getByRole("button", { name: `Go to page ${i}` })
293
+ ).toBeInTheDocument();
294
+ }
295
+ expect(screen.queryByText("…")).not.toBeInTheDocument();
296
+ });
297
+
298
+ it("renders all pages for totalPages=7", () => {
299
+ render(
300
+ <Pagination
301
+ currentPage={4}
302
+ totalPages={7}
303
+ onPageChange={mockOnPageChange}
304
+ />
305
+ );
306
+ for (let i = 1; i <= 7; i++) {
307
+ expect(
308
+ screen.getByRole("button", { name: `Go to page ${i}` })
309
+ ).toBeInTheDocument();
310
+ }
311
+ expect(screen.queryByText("…")).not.toBeInTheDocument();
312
+ });
313
+ });
314
+
315
+ describe("buildPageItems algorithm — current near start (current <= 4)", () => {
316
+ it("shows pages 1-5 and ellipsis then last page when on page 1", () => {
317
+ render(
318
+ <Pagination
319
+ currentPage={1}
320
+ totalPages={10}
321
+ onPageChange={mockOnPageChange}
322
+ />
323
+ );
324
+ for (let i = 1; i <= 5; i++) {
325
+ expect(
326
+ screen.getByRole("button", { name: `Go to page ${i}` })
327
+ ).toBeInTheDocument();
328
+ }
329
+ expect(screen.getByText("…")).toBeInTheDocument();
330
+ expect(
331
+ screen.getByRole("button", { name: "Go to page 10" })
332
+ ).toBeInTheDocument();
333
+ expect(
334
+ screen.queryByRole("button", { name: "Go to page 6" })
335
+ ).not.toBeInTheDocument();
336
+ });
337
+
338
+ it("shows pages 1-5, ellipsis, last for page 4 of 10", () => {
339
+ render(
340
+ <Pagination
341
+ currentPage={4}
342
+ totalPages={10}
343
+ onPageChange={mockOnPageChange}
344
+ />
345
+ );
346
+ for (let i = 1; i <= 5; i++) {
347
+ expect(
348
+ screen.getByRole("button", { name: `Go to page ${i}` })
349
+ ).toBeInTheDocument();
350
+ }
351
+ expect(screen.getByText("…")).toBeInTheDocument();
352
+ expect(
353
+ screen.getByRole("button", { name: "Go to page 10" })
354
+ ).toBeInTheDocument();
355
+ });
356
+ });
357
+
358
+ describe("buildPageItems algorithm — current near end (current >= total - 3)", () => {
359
+ it("shows first page, ellipsis, last 5 pages when on last page", () => {
360
+ render(
361
+ <Pagination
362
+ currentPage={10}
363
+ totalPages={10}
364
+ onPageChange={mockOnPageChange}
365
+ />
366
+ );
367
+ expect(
368
+ screen.getByRole("button", { name: "Go to page 1" })
369
+ ).toBeInTheDocument();
370
+ expect(screen.getByText("…")).toBeInTheDocument();
371
+ for (let i = 6; i <= 10; i++) {
372
+ expect(
373
+ screen.getByRole("button", { name: `Go to page ${i}` })
374
+ ).toBeInTheDocument();
375
+ }
376
+ });
377
+
378
+ it("shows first, ellipsis, last 5 for page 7 of 10", () => {
379
+ render(
380
+ <Pagination
381
+ currentPage={7}
382
+ totalPages={10}
383
+ onPageChange={mockOnPageChange}
384
+ />
385
+ );
386
+ expect(
387
+ screen.getByRole("button", { name: "Go to page 1" })
388
+ ).toBeInTheDocument();
389
+ expect(screen.getByText("…")).toBeInTheDocument();
390
+ for (let i = 6; i <= 10; i++) {
391
+ expect(
392
+ screen.getByRole("button", { name: `Go to page ${i}` })
393
+ ).toBeInTheDocument();
394
+ }
395
+ });
396
+ });
397
+
398
+ describe("buildPageItems algorithm — current in middle", () => {
399
+ it("shows first, ellipsis, current-1/current/current+1, ellipsis, last", () => {
400
+ render(
401
+ <Pagination
402
+ currentPage={5}
403
+ totalPages={10}
404
+ onPageChange={mockOnPageChange}
405
+ />
406
+ );
407
+ expect(
408
+ screen.getByRole("button", { name: "Go to page 1" })
409
+ ).toBeInTheDocument();
410
+ expect(
411
+ screen.getByRole("button", { name: "Go to page 4" })
412
+ ).toBeInTheDocument();
413
+ expect(
414
+ screen.getByRole("button", { name: "Go to page 5" })
415
+ ).toBeInTheDocument();
416
+ expect(
417
+ screen.getByRole("button", { name: "Go to page 6" })
418
+ ).toBeInTheDocument();
419
+ expect(
420
+ screen.getByRole("button", { name: "Go to page 10" })
421
+ ).toBeInTheDocument();
422
+ const ellipses = screen.getAllByText("…");
423
+ expect(ellipses).toHaveLength(2);
424
+ });
425
+
426
+ it("renders ellipsis as non-interactive with aria-hidden", () => {
427
+ render(
428
+ <Pagination
429
+ currentPage={5}
430
+ totalPages={10}
431
+ onPageChange={mockOnPageChange}
432
+ />
433
+ );
434
+ const ellipses = screen.getAllByText("…");
435
+ ellipses.forEach(el => {
436
+ expect(el).toHaveAttribute("aria-hidden", "true");
437
+ expect(el.tagName).toBe("SPAN");
438
+ });
439
+ });
440
+
441
+ it("shows correct pages for page 6 of 10", () => {
442
+ render(
443
+ <Pagination
444
+ currentPage={6}
445
+ totalPages={10}
446
+ onPageChange={mockOnPageChange}
447
+ />
448
+ );
449
+ expect(
450
+ screen.getByRole("button", { name: "Go to page 1" })
451
+ ).toBeInTheDocument();
452
+ expect(
453
+ screen.getByRole("button", { name: "Go to page 5" })
454
+ ).toBeInTheDocument();
455
+ expect(
456
+ screen.getByRole("button", { name: "Go to page 6" })
457
+ ).toBeInTheDocument();
458
+ expect(
459
+ screen.getByRole("button", { name: "Go to page 7" })
460
+ ).toBeInTheDocument();
461
+ expect(
462
+ screen.getByRole("button", { name: "Go to page 10" })
463
+ ).toBeInTheDocument();
464
+ expect(screen.getAllByText("…")).toHaveLength(2);
465
+ });
466
+ });
467
+
468
+ describe("Edge cases", () => {
469
+ it("handles single page (both nav buttons disabled)", () => {
470
+ render(
471
+ <Pagination
472
+ currentPage={1}
473
+ totalPages={1}
474
+ onPageChange={mockOnPageChange}
475
+ />
476
+ );
477
+ expect(
478
+ screen.getByRole("button", { name: "Go to previous page" })
479
+ ).toBeDisabled();
480
+ expect(
481
+ screen.getByRole("button", { name: "Go to next page" })
482
+ ).toBeDisabled();
483
+ });
484
+
485
+ it("handles totalPages=8 with current=4 (near-start boundary)", () => {
486
+ render(
487
+ <Pagination
488
+ currentPage={4}
489
+ totalPages={8}
490
+ onPageChange={mockOnPageChange}
491
+ />
492
+ );
493
+ for (let i = 1; i <= 5; i++) {
494
+ expect(
495
+ screen.getByRole("button", { name: `Go to page ${i}` })
496
+ ).toBeInTheDocument();
497
+ }
498
+ expect(
499
+ screen.getByRole("button", { name: "Go to page 8" })
500
+ ).toBeInTheDocument();
501
+ });
502
+
503
+ it("handles totalPages=8 with current=5 (near-end boundary)", () => {
504
+ render(
505
+ <Pagination
506
+ currentPage={5}
507
+ totalPages={8}
508
+ onPageChange={mockOnPageChange}
509
+ />
510
+ );
511
+ expect(
512
+ screen.getByRole("button", { name: "Go to page 1" })
513
+ ).toBeInTheDocument();
514
+ for (let i = 4; i <= 8; i++) {
515
+ expect(
516
+ screen.getByRole("button", { name: `Go to page ${i}` })
517
+ ).toBeInTheDocument();
518
+ }
519
+ });
520
+ });
521
+ });
@@ -0,0 +1,151 @@
1
+ import { RadioButton } from "./index";
2
+
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+
5
+ describe("RadioButton", () => {
6
+ const defaultProps = {
7
+ name: "plan",
8
+ label: "Option A",
9
+ };
10
+
11
+ it("renders with label text", () => {
12
+ render(<RadioButton {...defaultProps} />);
13
+ expect(screen.getByText("Option A")).toBeInTheDocument();
14
+ });
15
+
16
+ it("renders a hidden radio input", () => {
17
+ const { container } = render(<RadioButton {...defaultProps} />);
18
+ const input = container.querySelector('input[type="radio"]');
19
+ expect(input).toBeInTheDocument();
20
+ expect(input).toHaveClass("hidden");
21
+ });
22
+
23
+ it("sets name and value on the input", () => {
24
+ const { container } = render(
25
+ <RadioButton {...defaultProps} value="optA" />
26
+ );
27
+ const input = container.querySelector('input[type="radio"]');
28
+ expect(input).toHaveAttribute("name", "plan");
29
+ expect(input).toHaveAttribute("value", "optA");
30
+ });
31
+
32
+ it("calls onChange with value when clicked", () => {
33
+ const onChange = jest.fn();
34
+ render(<RadioButton {...defaultProps} value="optA" onChange={onChange} />);
35
+ const buttons = screen.getAllByRole("button");
36
+ fireEvent.click(buttons[0]);
37
+ expect(onChange).toHaveBeenCalledWith("optA");
38
+ });
39
+
40
+ it("calls onChange with name when no value is provided", () => {
41
+ const onChange = jest.fn();
42
+ render(<RadioButton {...defaultProps} onChange={onChange} />);
43
+ const buttons = screen.getAllByRole("button");
44
+ fireEvent.click(buttons[0]);
45
+ expect(onChange).toHaveBeenCalledWith("plan");
46
+ });
47
+
48
+ it("calls onChange when label button is clicked", () => {
49
+ const onChange = jest.fn();
50
+ render(<RadioButton {...defaultProps} value="optA" onChange={onChange} />);
51
+ const buttons = screen.getAllByRole("button");
52
+ fireEvent.click(buttons[1]);
53
+ expect(onChange).toHaveBeenCalledWith("optA");
54
+ });
55
+
56
+ it("does not throw when onChange is not provided", () => {
57
+ render(<RadioButton {...defaultProps} />);
58
+ const buttons = screen.getAllByRole("button");
59
+ expect(() => fireEvent.click(buttons[0])).not.toThrow();
60
+ });
61
+
62
+ it("shows checked indicator when checked", () => {
63
+ const { container } = render(
64
+ <RadioButton {...defaultProps} checked={true} />
65
+ );
66
+ const indicator = container.querySelector(".bg-bg-fill-brand");
67
+ expect(indicator).toBeInTheDocument();
68
+ });
69
+
70
+ it("does not show checked indicator when unchecked", () => {
71
+ const { container } = render(
72
+ <RadioButton {...defaultProps} checked={false} />
73
+ );
74
+ const indicator = container.querySelector(".bg-bg-fill-brand");
75
+ expect(indicator).not.toBeInTheDocument();
76
+ });
77
+
78
+ it("applies border-brand class to outer button when checked", () => {
79
+ render(<RadioButton {...defaultProps} checked={true} />);
80
+ const buttons = screen.getAllByRole("button");
81
+ expect(buttons[0].className).toContain("border-border-brand");
82
+ });
83
+
84
+ it("renders subText", () => {
85
+ render(<RadioButton {...defaultProps} subText="Extra info" />);
86
+ expect(screen.getByText("Extra info")).toBeInTheDocument();
87
+ });
88
+
89
+ it("applies custom className to the label wrapper", () => {
90
+ const { container } = render(
91
+ <RadioButton {...defaultProps} className="custom-wrapper" />
92
+ );
93
+ const label = container.querySelector("label");
94
+ expect(label?.className).toContain("custom-wrapper");
95
+ });
96
+
97
+ it("applies custom labelClassName", () => {
98
+ render(<RadioButton {...defaultProps} labelClassName="text-bold" />);
99
+ const labelDiv = screen.getByText("Option A");
100
+ expect(labelDiv.className).toContain("text-bold");
101
+ });
102
+
103
+ it("passes data-cy to the label", () => {
104
+ const { container } = render(
105
+ <RadioButton {...defaultProps} data-cy="radio-cy" />
106
+ );
107
+ const label = container.querySelector('[data-cy="radio-cy"]');
108
+ expect(label).toBeInTheDocument();
109
+ });
110
+
111
+ it("passes labelProps to the label element", () => {
112
+ const { container } = render(
113
+ <RadioButton {...defaultProps} labelProps={{ id: "custom-label-id" }} />
114
+ );
115
+ const label = container.querySelector("#custom-label-id");
116
+ expect(label).toBeInTheDocument();
117
+ });
118
+
119
+ it("disables buttons and input when disabled", () => {
120
+ const { container } = render(
121
+ <RadioButton {...defaultProps} disabled={true} />
122
+ );
123
+ const input = container.querySelector('input[type="radio"]');
124
+ const buttons = screen.getAllByRole("button");
125
+ expect(input).toBeDisabled();
126
+ expect(buttons[0]).toBeDisabled();
127
+ expect(buttons[1]).toBeDisabled();
128
+ });
129
+
130
+ it("applies opacity-50 class when disabled", () => {
131
+ render(<RadioButton {...defaultProps} disabled={true} />);
132
+ const buttons = screen.getAllByRole("button");
133
+ expect(buttons[0].className).toContain("opacity-50");
134
+ });
135
+
136
+ it("sets tabIndex -1 on label button when no label is provided", () => {
137
+ render(<RadioButton name="plan" />);
138
+ const buttons = screen.getAllByRole("button");
139
+ expect(buttons[1]).toHaveAttribute("tabindex", "-1");
140
+ });
141
+
142
+ it("sets tabIndex 0 on label button when label is provided", () => {
143
+ render(<RadioButton {...defaultProps} />);
144
+ const buttons = screen.getAllByRole("button");
145
+ expect(buttons[1]).toHaveAttribute("tabindex", "0");
146
+ });
147
+
148
+ it("has the correct displayName", () => {
149
+ expect(RadioButton.displayName).toBe("RadioButton");
150
+ });
151
+ });