@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.
- 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/core.d.ts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/index.esm.js +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +14 -8
- package/src/components/accordion/index.test.tsx +270 -0
- package/src/components/alert-card/index.test.tsx +152 -0
- package/src/components/animation-wrapper/index.test.tsx +424 -0
- package/src/components/brand-button/index.test.tsx +292 -0
- package/src/components/button/index.test.tsx +91 -0
- package/src/components/call-button/index.test.tsx +260 -0
- package/src/components/checkbox/index.test.tsx +252 -0
- package/src/components/checklist/index.test.tsx +231 -0
- package/src/components/checklist/index.tsx +64 -29
- package/src/components/checklist/types.ts +7 -1
- package/src/components/collapse/index.test.tsx +277 -0
- package/src/components/collapse/index.tsx +1 -0
- package/src/components/divider/index.test.tsx +53 -0
- package/src/components/image/index.test.tsx +174 -0
- package/src/components/input/index.test.tsx +348 -0
- package/src/components/link/index.test.tsx +199 -0
- package/src/components/list/index.test.tsx +166 -0
- package/src/components/material-icon/index.test.tsx +130 -0
- package/src/components/modal/index.test.tsx +310 -0
- package/src/components/next-image/index.test.tsx +406 -0
- package/src/components/pagination/index.test.tsx +521 -0
- package/src/components/radio-button/index.test.tsx +151 -0
- package/src/components/see-more/index.test.tsx +96 -0
- package/src/components/select/index.test.tsx +256 -0
- package/src/components/select-plan-button/index.test.tsx +173 -0
- package/src/components/skeleton/index.test.tsx +74 -0
- package/src/components/spinner/index.test.tsx +76 -0
- package/src/components/text/index.test.tsx +65 -0
- package/src/components/tooltip/index.test.tsx +50 -0
- package/src/components/view-cart-button/index.test.tsx +57 -0
- package/src/contentful/blocks/accordion/index.test.tsx +218 -0
- package/src/contentful/blocks/accordion/index.tsx +3 -1
- package/src/contentful/blocks/address-input-banner/index.test.tsx +132 -0
- package/src/contentful/blocks/anchored-bottom-banner/index.test.tsx +287 -0
- package/src/contentful/blocks/blogs-grid/BlogGrid.stories.tsx +5 -4
- package/src/contentful/blocks/blogs-grid/index.test.tsx +355 -0
- package/src/contentful/blocks/blogs-grid-base/index.test.tsx +274 -0
- package/src/contentful/blocks/breadcrumbs/index.test.tsx +281 -0
- package/src/contentful/blocks/button/index.test.tsx +339 -0
- package/src/contentful/blocks/callout/index.test.tsx +539 -0
- package/src/contentful/blocks/cards/blog-card/index.test.tsx +218 -0
- package/src/contentful/blocks/cards/floating-image-card/index.test.tsx +201 -0
- package/src/contentful/blocks/cards/full-image-card/index.test.tsx +216 -0
- package/src/contentful/blocks/cards/index.test.tsx +39 -0
- package/src/contentful/blocks/cards/product-card/index.test.tsx +263 -0
- package/src/contentful/blocks/cards/simple-card/index.test.tsx +364 -0
- package/src/contentful/blocks/cards/simple-card/index.tsx +1 -1
- package/src/contentful/blocks/cards/testimonial-card/index.test.tsx +180 -0
- package/src/contentful/blocks/carousel/helper.test.tsx +539 -0
- package/src/contentful/blocks/carousel/index.test.tsx +308 -0
- package/src/contentful/blocks/carousel/types.test.ts +16 -0
- package/src/contentful/blocks/cart-retention-banner/index.test.tsx +409 -0
- package/src/contentful/blocks/cart-retention-banner/index.tsx +4 -4
- package/src/contentful/blocks/comparison-table/index.test.tsx +114 -0
- package/src/contentful/blocks/cookiebanner/index.test.tsx +277 -0
- package/src/contentful/blocks/cta-callout/index.test.tsx +244 -0
- package/src/contentful/blocks/dynamic-tabs/index.test.tsx +240 -0
- package/src/contentful/blocks/email-input-block/index.test.tsx +213 -0
- package/src/contentful/blocks/email-input-block/index.tsx +40 -35
- package/src/contentful/blocks/find-kinetic/index.test.tsx +269 -0
- package/src/contentful/blocks/floating-banner/index.test.tsx +246 -0
- package/src/contentful/blocks/footer/index.test.tsx +302 -0
- package/src/contentful/blocks/image-promo-bar/helper.test.tsx +61 -0
- package/src/contentful/blocks/image-promo-bar/index.test.tsx +467 -0
- package/src/contentful/blocks/image-promo-bar/index.tsx +248 -246
- package/src/contentful/blocks/image-promo-bar/vimeo-embed.test.tsx +142 -0
- package/src/contentful/blocks/image-promo-bar/youtube-embed.test.tsx +104 -0
- package/src/contentful/blocks/modal/index.test.tsx +209 -0
- package/src/contentful/blocks/navigation/desktop-link-groups.tsx/index.test.tsx +208 -0
- package/src/contentful/blocks/navigation/index.test.tsx +924 -0
- package/src/contentful/blocks/navigation/mobile-link-groups.tsx/index.test.tsx +131 -0
- package/src/contentful/blocks/primary-hero/index.test.tsx +286 -0
- package/src/contentful/blocks/primary-hero/index.tsx +7 -4
- package/src/contentful/blocks/search-block/index.test.tsx +268 -0
- package/src/contentful/blocks/shape-background-wrapper/index.test.tsx +284 -0
- package/src/contentful/blocks/text/index.test.tsx +36 -0
- package/src/contentful/index.test.ts +45 -0
- package/src/global-mocks/contentful/to-document.ts +25 -0
- package/src/global-mocks/cookie.ts +48 -0
- package/src/global-mocks/cx.ts +37 -0
- package/src/global-mocks/index.ts +89 -0
- package/src/global-mocks/speed-card-bg.ts +27 -0
- package/src/global-mocks/utm.ts +49 -0
- package/src/hooks/contentful/use-contentful-rich-text.test.tsx +1758 -0
- package/src/hooks/contentful/use-contentful-rich-text.tsx +1 -1
- package/src/hooks/contentful/use-processed-check-list.test.tsx +277 -0
- package/src/hooks/use-body-scroll-lock.test.ts +134 -0
- package/src/hooks/use-carousel-swipe.test.ts +393 -0
- package/src/hooks/use-outside-click.test.ts +142 -0
- package/src/index.ts +1 -1
- package/src/next/index.test.ts +7 -0
- package/src/setupTests.ts +17 -11
- package/src/utils/contentful/to-document.test.ts +85 -0
- package/src/utils/cookie.test.ts +180 -0
- package/src/utils/cx.test.ts +90 -0
- package/src/utils/index.test.ts +115 -0
- package/src/utils/speed-card-bg.test.ts +46 -0
- 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
|
+
});
|