@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,96 @@
1
+ import { SeeMore } from "./index";
2
+
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+
5
+ describe("SeeMore", () => {
6
+ const defaultList = ["Item 1", "Item 2", "Item 3"];
7
+
8
+ it("renders with default text when no text prop is provided", () => {
9
+ render(<SeeMore list={defaultList} />);
10
+ expect(screen.getByText("See details")).toBeInTheDocument();
11
+ });
12
+
13
+ it("renders with custom text when text prop is provided", () => {
14
+ render(<SeeMore list={defaultList} text="Show more info" />);
15
+ expect(screen.getByText("Show more info")).toBeInTheDocument();
16
+ });
17
+
18
+ it("renders the checklist items", () => {
19
+ render(<SeeMore list={defaultList} />);
20
+ defaultList.forEach(item => {
21
+ expect(screen.getByText(item)).toBeInTheDocument();
22
+ });
23
+ });
24
+
25
+ it("starts with details expanded (showDetails is true)", () => {
26
+ render(<SeeMore list={defaultList} />);
27
+ const button = screen.getByRole("button");
28
+ expect(button).toHaveAttribute("aria-expanded", "true");
29
+ });
30
+
31
+ it("shows keyboard_arrow_down icon when expanded", () => {
32
+ render(<SeeMore list={defaultList} />);
33
+ expect(screen.getByText("keyboard_arrow_down")).toBeInTheDocument();
34
+ });
35
+
36
+ it("collapses details when button is clicked", () => {
37
+ render(<SeeMore list={defaultList} />);
38
+ const button = screen.getByRole("button");
39
+
40
+ fireEvent.click(button);
41
+
42
+ expect(button).toHaveAttribute("aria-expanded", "false");
43
+ });
44
+
45
+ it("shows keyboard_arrow_up icon when collapsed", () => {
46
+ render(<SeeMore list={defaultList} />);
47
+ const button = screen.getByRole("button");
48
+
49
+ fireEvent.click(button);
50
+
51
+ expect(screen.getByText("keyboard_arrow_up")).toBeInTheDocument();
52
+ });
53
+
54
+ it("expands details again on second click", () => {
55
+ render(<SeeMore list={defaultList} />);
56
+ const button = screen.getByRole("button");
57
+
58
+ fireEvent.click(button);
59
+ fireEvent.click(button);
60
+
61
+ expect(button).toHaveAttribute("aria-expanded", "true");
62
+ expect(screen.getByText("keyboard_arrow_down")).toBeInTheDocument();
63
+ });
64
+
65
+ it("stops event propagation on click", () => {
66
+ const parentClickHandler = jest.fn();
67
+ render(
68
+ <div onClick={parentClickHandler}>
69
+ <SeeMore list={defaultList} />
70
+ </div>
71
+ );
72
+ const button = screen.getByRole("button");
73
+
74
+ fireEvent.click(button);
75
+
76
+ expect(parentClickHandler).not.toHaveBeenCalled();
77
+ });
78
+
79
+ it("passes list items to Checklist", () => {
80
+ const items = ["Feature A", "Feature B"];
81
+ render(<SeeMore list={items} />);
82
+
83
+ items.forEach(item => {
84
+ expect(screen.getByText(item)).toBeInTheDocument();
85
+ });
86
+ });
87
+
88
+ it("renders with an empty list without crashing", () => {
89
+ render(<SeeMore list={[]} />);
90
+ expect(screen.getByRole("button")).toBeInTheDocument();
91
+ });
92
+
93
+ it("has the correct displayName", () => {
94
+ expect(SeeMore.displayName).toBe("SeeMore");
95
+ });
96
+ });
@@ -0,0 +1,256 @@
1
+ import React from "react";
2
+ import { Select } from "./index";
3
+ import { SelectOption } from "./types";
4
+
5
+ import { fireEvent, render, screen } from "@testing-library/react";
6
+
7
+ // Mock react-select to simplify testing
8
+ jest.mock("react-select", () => {
9
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
10
+ const { forwardRef } = require("react");
11
+ // eslint-disable-next-line react/display-name
12
+ const MockSelect = forwardRef((props: any, ref: any) => {
13
+ const {
14
+ options,
15
+ value,
16
+ placeholder,
17
+ onChange,
18
+ className,
19
+ classNames,
20
+ ...rest
21
+ } = props;
22
+
23
+ // Call classNames functions to exercise them for coverage
24
+ if (classNames) {
25
+ classNames.control?.({ isFocused: false });
26
+ classNames.control?.({ isFocused: true });
27
+ classNames.indicatorSeparator?.();
28
+ classNames.dropdownIndicator?.({ selectProps: { value: null } });
29
+ classNames.dropdownIndicator?.({ selectProps: { value: "test" } });
30
+ classNames.singleValue?.();
31
+ classNames.menu?.();
32
+ classNames.option?.({ isFocused: false, isSelected: false, label: "A" });
33
+ classNames.option?.({ isFocused: true, isSelected: false, label: "A" });
34
+ classNames.option?.({ isFocused: false, isSelected: true, label: "A" });
35
+ classNames.placeholder?.();
36
+ classNames.input?.();
37
+ classNames.valueContainer?.();
38
+ }
39
+
40
+ return (
41
+ <select
42
+ ref={ref}
43
+ data-testid={rest["data-testid"] || "mock-select"}
44
+ data-cy={rest["data-cy"]}
45
+ className={className}
46
+ value={value?.value || ""}
47
+ onChange={e => {
48
+ const selected = options?.find(
49
+ (o: SelectOption) => o.value === e.target.value
50
+ );
51
+ onChange?.(selected, { action: "select-option", option: selected });
52
+ }}
53
+ >
54
+ {placeholder && (
55
+ <option value="" disabled={true}>
56
+ {placeholder}
57
+ </option>
58
+ )}
59
+ {options?.map((opt: SelectOption) => (
60
+ <option key={opt.value} value={opt.value}>
61
+ {opt.label}
62
+ </option>
63
+ ))}
64
+ </select>
65
+ );
66
+ });
67
+
68
+ return {
69
+ __esModule: true,
70
+ default: MockSelect,
71
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
72
+ createFilter: (filterConfig: any) => () => true,
73
+ };
74
+ });
75
+
76
+ const defaultOptions: SelectOption[] = [
77
+ { label: "Option A", value: "a" },
78
+ { label: "Option B", value: "b" },
79
+ { label: "Option C", value: "c" },
80
+ ];
81
+
82
+ describe("Select", () => {
83
+ it("renders with options", () => {
84
+ render(<Select options={defaultOptions} data-testid="my-select" />);
85
+ expect(screen.getByTestId("my-select")).toBeInTheDocument();
86
+ });
87
+
88
+ it("renders placeholder", () => {
89
+ render(<Select options={defaultOptions} placeholder="Pick one" />);
90
+ expect(screen.getByText("Pick one")).toBeInTheDocument();
91
+ });
92
+
93
+ it("appends * to placeholder when required", () => {
94
+ render(
95
+ <Select options={defaultOptions} placeholder="Pick" required={true} />
96
+ );
97
+ expect(screen.getByText("Pick*")).toBeInTheDocument();
98
+ });
99
+
100
+ it("calls onChange when an option is selected", () => {
101
+ const onChange = jest.fn();
102
+ render(
103
+ <Select options={defaultOptions} onChange={onChange} data-testid="sel" />
104
+ );
105
+ fireEvent.change(screen.getByTestId("sel"), { target: { value: "b" } });
106
+ expect(onChange).toHaveBeenCalledWith(
107
+ defaultOptions[1],
108
+ expect.objectContaining({ action: "select-option" })
109
+ );
110
+ });
111
+
112
+ it("renders label when provided", () => {
113
+ render(<Select options={defaultOptions} label="My Label" />);
114
+ expect(screen.getByText("My Label")).toBeInTheDocument();
115
+ });
116
+
117
+ it("renders label with custom className", () => {
118
+ render(
119
+ <Select
120
+ options={defaultOptions}
121
+ label="Label"
122
+ labelClassName="custom-class"
123
+ />
124
+ );
125
+ const label = screen.getByText("Label");
126
+ expect(label).toHaveClass("custom-class");
127
+ });
128
+
129
+ it("does not render label when not provided", () => {
130
+ const { container } = render(<Select options={defaultOptions} />);
131
+ expect(container.querySelector("label")).not.toBeInTheDocument();
132
+ });
133
+
134
+ it("renders error message when error prop is provided", () => {
135
+ render(<Select options={defaultOptions} error="Required field" />);
136
+ expect(screen.getByText("Required field")).toBeInTheDocument();
137
+ });
138
+
139
+ it("renders helperText when no error", () => {
140
+ render(<Select options={defaultOptions} helperText="Choose wisely" />);
141
+ expect(screen.getByText("Choose wisely")).toBeInTheDocument();
142
+ });
143
+
144
+ it("does not render helperText when error is present", () => {
145
+ render(
146
+ <Select
147
+ options={defaultOptions}
148
+ error="Error"
149
+ helperText="Choose wisely"
150
+ />
151
+ );
152
+ expect(screen.getByText("Error")).toBeInTheDocument();
153
+ expect(screen.queryByText("Choose wisely")).not.toBeInTheDocument();
154
+ });
155
+
156
+ it("renders unstyled variant without label/error wrapper", () => {
157
+ const { container } = render(
158
+ <Select
159
+ options={defaultOptions}
160
+ variant="unstyled"
161
+ label="Should not appear"
162
+ error="Should not appear"
163
+ />
164
+ );
165
+ expect(screen.queryByText("Should not appear")).not.toBeInTheDocument();
166
+ // unstyled variant doesn't wrap in a div.w-full
167
+ expect(container.querySelector(".w-full")).not.toBeInTheDocument();
168
+ });
169
+
170
+ it("applies size sm classes via classNames", () => {
171
+ // This test exercises the getSizeClasses branch for "sm"
172
+ render(
173
+ <Select options={defaultOptions} size="sm" data-testid="sm-select" />
174
+ );
175
+ expect(screen.getByTestId("sm-select")).toBeInTheDocument();
176
+ });
177
+
178
+ it("applies default (md) size classes", () => {
179
+ render(
180
+ <Select options={defaultOptions} size="md" data-testid="md-select" />
181
+ );
182
+ expect(screen.getByTestId("md-select")).toBeInTheDocument();
183
+ });
184
+
185
+ it("handles isCustomStyle prop", () => {
186
+ render(
187
+ <Select
188
+ options={defaultOptions}
189
+ isCustomStyle={true}
190
+ data-testid="custom-select"
191
+ />
192
+ );
193
+ expect(screen.getByTestId("custom-select")).toBeInTheDocument();
194
+ });
195
+
196
+ it("handles hasError prop", () => {
197
+ render(
198
+ <Select
199
+ options={defaultOptions}
200
+ hasError={true}
201
+ data-testid="err-select"
202
+ />
203
+ );
204
+ expect(screen.getByTestId("err-select")).toBeInTheDocument();
205
+ });
206
+
207
+ it("handles filterOptions prop", () => {
208
+ render(
209
+ <Select
210
+ options={defaultOptions}
211
+ filterOptions={{ matchFrom: "start", ignoreCase: true }}
212
+ data-testid="filter-select"
213
+ />
214
+ );
215
+ expect(screen.getByTestId("filter-select")).toBeInTheDocument();
216
+ });
217
+
218
+ it("passes data-cy prop", () => {
219
+ render(<Select options={defaultOptions} data-cy="cypress-sel" />);
220
+ expect(
221
+ document.querySelector('[data-cy="cypress-sel"]')
222
+ ).toBeInTheDocument();
223
+ });
224
+
225
+ it("renders with a selected value", () => {
226
+ render(
227
+ <Select
228
+ options={defaultOptions}
229
+ value={defaultOptions[2]}
230
+ data-testid="val-select"
231
+ />
232
+ );
233
+ const select = screen.getByTestId("val-select") as HTMLSelectElement;
234
+ expect(select.value).toBe("c");
235
+ });
236
+
237
+ it("has the correct displayName", () => {
238
+ expect(Select.displayName).toBe("Select");
239
+ });
240
+
241
+ it("exercises option classNames with last option label", () => {
242
+ // Ensures the last-option border logic is covered
243
+ const opts = [
244
+ { label: "First", value: "1" },
245
+ { label: "Last", value: "2" },
246
+ ];
247
+ render(
248
+ <Select
249
+ options={opts}
250
+ isCustomStyle={true}
251
+ data-testid="last-opt-select"
252
+ />
253
+ );
254
+ expect(screen.getByTestId("last-opt-select")).toBeInTheDocument();
255
+ });
256
+ });
@@ -0,0 +1,173 @@
1
+ import React from "react";
2
+ import { SelectPlanButton } from "./index";
3
+
4
+ import { fireEvent, render, screen } from "@testing-library/react";
5
+
6
+ describe("SelectPlanButton", () => {
7
+ const defaultProps = {
8
+ onSelect: jest.fn(),
9
+ speed: "500",
10
+ };
11
+
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ it("renders with default props", () => {
17
+ render(<SelectPlanButton {...defaultProps} />);
18
+ expect(screen.getByText("Select plan")).toBeInTheDocument();
19
+ });
20
+
21
+ it("calls onSelect when clicked", () => {
22
+ render(<SelectPlanButton {...defaultProps} />);
23
+ fireEvent.click(screen.getByRole("button"));
24
+ expect(defaultProps.onSelect).toHaveBeenCalledTimes(1);
25
+ });
26
+
27
+ it("applies selected styles when isSelected is true", () => {
28
+ render(<SelectPlanButton {...defaultProps} isSelected={true} />);
29
+ const button = screen.getByRole("button");
30
+ expect(button.className).toContain("bg-bg-surface");
31
+ expect(button.className).toContain("text-text");
32
+ });
33
+
34
+ it("applies default (unselected) styles when isSelected is false", () => {
35
+ render(<SelectPlanButton {...defaultProps} isSelected={false} />);
36
+ const button = screen.getByRole("button");
37
+ expect(button.className).toContain("bg-bg-fill-brand");
38
+ expect(button.className).toContain("text-text-brand-on-bg-fill");
39
+ });
40
+
41
+ it("renders the expand_circle_right icon", () => {
42
+ render(<SelectPlanButton {...defaultProps} />);
43
+ expect(screen.getByText("expand_circle_right")).toBeInTheDocument();
44
+ });
45
+
46
+ it("applies icon className for unselected state", () => {
47
+ render(
48
+ <SelectPlanButton
49
+ {...defaultProps}
50
+ isSelected={false}
51
+ iconClassName="custom-icon"
52
+ />
53
+ );
54
+ const icon = screen.getByText("expand_circle_right");
55
+ expect(icon.className).toContain("text-icon-inverse");
56
+ expect(icon.className).toContain("custom-icon");
57
+ });
58
+
59
+ it("applies icon className for selected state", () => {
60
+ render(
61
+ <SelectPlanButton
62
+ {...defaultProps}
63
+ isSelected={true}
64
+ iconClassName="custom-icon"
65
+ />
66
+ );
67
+ const icon = screen.getByText("expand_circle_right");
68
+ expect(icon.className).toContain("text-icon");
69
+ expect(icon.className).toContain("custom-icon");
70
+ });
71
+
72
+ it("sets data-track attributes on the button", () => {
73
+ render(<SelectPlanButton {...defaultProps} speed="1000" />);
74
+ const button = screen.getByRole("button");
75
+ expect(button).toHaveAttribute(
76
+ "data-track-element-name",
77
+ "speed_plan_select_button"
78
+ );
79
+ expect(button).toHaveAttribute(
80
+ "data-track-click-text",
81
+ "Select plan speed 1000"
82
+ );
83
+ expect(button).toHaveAttribute(
84
+ "data-track-element-clicked",
85
+ "speed_plan_card"
86
+ );
87
+ });
88
+
89
+ it("calls renderCheckPlans with correct arguments", () => {
90
+ const renderCheckPlans = jest.fn(() => <div data-testid="check-plans" />);
91
+ render(
92
+ <SelectPlanButton
93
+ {...defaultProps}
94
+ speed="300"
95
+ techType="fiber"
96
+ isMax={true}
97
+ cta={{ label: "Go", url: "/go" } as any}
98
+ renderCheckPlans={renderCheckPlans}
99
+ />
100
+ );
101
+
102
+ expect(renderCheckPlans).toHaveBeenCalledWith({
103
+ speedCardConfig: expect.objectContaining({
104
+ speed: "300",
105
+ techType: "fiber",
106
+ isMax: true,
107
+ isModalOpen: false,
108
+ }),
109
+ cta: { label: "Go", url: "/go" },
110
+ });
111
+ expect(screen.getByTestId("check-plans")).toBeInTheDocument();
112
+ });
113
+
114
+ it("sets isModalOpen to true after click and passes setModalOpen", () => {
115
+ let capturedConfig: any;
116
+ const renderCheckPlans = jest.fn(args => {
117
+ capturedConfig = args;
118
+ return null;
119
+ });
120
+
121
+ const { rerender } = render(
122
+ <SelectPlanButton {...defaultProps} renderCheckPlans={renderCheckPlans} />
123
+ );
124
+
125
+ // Initially modal is closed
126
+ expect(renderCheckPlans).toHaveBeenLastCalledWith(
127
+ expect.objectContaining({
128
+ speedCardConfig: expect.objectContaining({ isModalOpen: false }),
129
+ })
130
+ );
131
+
132
+ // Click opens modal
133
+ fireEvent.click(screen.getByRole("button"));
134
+
135
+ // After click, re-render happens with open=true
136
+ expect(renderCheckPlans).toHaveBeenLastCalledWith(
137
+ expect.objectContaining({
138
+ speedCardConfig: expect.objectContaining({ isModalOpen: true }),
139
+ })
140
+ );
141
+
142
+ // Call setModalOpen to close
143
+ capturedConfig.speedCardConfig.setModalOpen();
144
+
145
+ // Need rerender to capture new state
146
+ rerender(
147
+ <SelectPlanButton {...defaultProps} renderCheckPlans={renderCheckPlans} />
148
+ );
149
+
150
+ expect(renderCheckPlans).toHaveBeenLastCalledWith(
151
+ expect.objectContaining({
152
+ speedCardConfig: expect.objectContaining({ isModalOpen: false }),
153
+ })
154
+ );
155
+ });
156
+
157
+ it("renders without renderCheckPlans (optional)", () => {
158
+ render(<SelectPlanButton {...defaultProps} />);
159
+ expect(screen.getByRole("button")).toBeInTheDocument();
160
+ });
161
+
162
+ it("uses default iconSize of 24", () => {
163
+ render(<SelectPlanButton {...defaultProps} />);
164
+ const icon = screen.getByText("expand_circle_right");
165
+ expect(icon.style.fontSize).toBe("24px");
166
+ });
167
+
168
+ it("uses custom iconSize", () => {
169
+ render(<SelectPlanButton {...defaultProps} iconSize={32} />);
170
+ const icon = screen.getByText("expand_circle_right");
171
+ expect(icon.style.fontSize).toBe("32px");
172
+ });
173
+ });
@@ -0,0 +1,74 @@
1
+ import { PageSkeleton, Skeleton } from "./index";
2
+
3
+ import { render, screen } from "@testing-library/react";
4
+
5
+ describe("Skeleton", () => {
6
+ it("renders with default props (1 skeleton item)", () => {
7
+ const { container } = render(<Skeleton />);
8
+ const items = container.querySelectorAll(".h-8");
9
+ expect(items).toHaveLength(1);
10
+ });
11
+
12
+ it("renders the specified count of skeleton items", () => {
13
+ const { container } = render(<Skeleton count={4} />);
14
+ const items = container.querySelectorAll(".h-8");
15
+ expect(items).toHaveLength(4);
16
+ });
17
+
18
+ it("applies custom className to each skeleton item", () => {
19
+ const { container } = render(<Skeleton count={2} className="w-1/2" />);
20
+ const items = container.querySelectorAll(".h-8");
21
+ items.forEach(item => {
22
+ expect(item.className).toContain("w-1/2");
23
+ });
24
+ });
25
+
26
+ it("applies decreasing opacity to each item", () => {
27
+ const { container } = render(<Skeleton count={3} />);
28
+ const items = container.querySelectorAll(".h-8");
29
+ expect(items[0].style.opacity).toBe("1");
30
+ expect(items[1].style.opacity).toBe("0.9");
31
+ expect(items[2].style.opacity).toBe("0.8");
32
+ });
33
+
34
+ it("wraps items in an animate-pulse container", () => {
35
+ const { container } = render(<Skeleton />);
36
+ expect(container.firstChild).toHaveClass("animate-pulse", "space-y-4");
37
+ });
38
+
39
+ it("has the correct displayName", () => {
40
+ expect(Skeleton.displayName).toBe("Skeleton");
41
+ });
42
+ });
43
+
44
+ describe("PageSkeleton", () => {
45
+ it("renders the generic-skeleton container", () => {
46
+ render(<PageSkeleton />);
47
+ expect(screen.getByTestId("generic-skeleton")).toBeInTheDocument();
48
+ });
49
+
50
+ it("renders title section", () => {
51
+ render(<PageSkeleton />);
52
+ expect(screen.getByTestId("title")).toBeInTheDocument();
53
+ });
54
+
55
+ it("renders subtitle section", () => {
56
+ render(<PageSkeleton />);
57
+ expect(screen.getByTestId("subtitle")).toBeInTheDocument();
58
+ });
59
+
60
+ it("renders content section", () => {
61
+ render(<PageSkeleton />);
62
+ expect(screen.getByTestId("content")).toBeInTheDocument();
63
+ });
64
+
65
+ it("renders additional-info section", () => {
66
+ render(<PageSkeleton />);
67
+ expect(screen.getByTestId("additional-info")).toBeInTheDocument();
68
+ });
69
+
70
+ it("renders cta-button section", () => {
71
+ render(<PageSkeleton />);
72
+ expect(screen.getByTestId("cta-button")).toBeInTheDocument();
73
+ });
74
+ });
@@ -0,0 +1,76 @@
1
+ import { Spinner } from "./index";
2
+
3
+ import { render, screen } from "@testing-library/react";
4
+
5
+ describe("Spinner", () => {
6
+ it("renders with role status", () => {
7
+ render(<Spinner />);
8
+ expect(screen.getByRole("status")).toBeInTheDocument();
9
+ });
10
+
11
+ it("renders a screen-reader-only loading text", () => {
12
+ render(<Spinner />);
13
+ expect(screen.getByText("Loading...")).toBeInTheDocument();
14
+ });
15
+
16
+ it("applies medium size class by default", () => {
17
+ const { container } = render(<Spinner />);
18
+ const svg = container.querySelector("svg");
19
+ const classes = svg?.getAttribute("class") || "";
20
+ expect(classes).toContain("w-6");
21
+ expect(classes).toContain("h-6");
22
+ });
23
+
24
+ it("applies small size class", () => {
25
+ const { container } = render(<Spinner size="small" />);
26
+ const svg = container.querySelector("svg");
27
+ const classes = svg?.getAttribute("class") || "";
28
+ expect(classes).toContain("w-4");
29
+ expect(classes).toContain("h-4");
30
+ });
31
+
32
+ it("applies large size class", () => {
33
+ const { container } = render(<Spinner size="large" />);
34
+ const svg = container.querySelector("svg");
35
+ const classes = svg?.getAttribute("class") || "";
36
+ expect(classes).toContain("w-8");
37
+ expect(classes).toContain("h-8");
38
+ });
39
+
40
+ it("applies xlarge size class", () => {
41
+ const { container } = render(<Spinner size="xlarge" />);
42
+ const svg = container.querySelector("svg");
43
+ const classes = svg?.getAttribute("class") || "";
44
+ expect(classes).toContain("w-12");
45
+ expect(classes).toContain("h-12");
46
+ });
47
+
48
+ it("uses white as default color", () => {
49
+ const { container } = render(<Spinner />);
50
+ const svg = container.querySelector("svg");
51
+ expect(svg).toHaveAttribute("fill", "#fff");
52
+ });
53
+
54
+ it("uses custom color when provided", () => {
55
+ const { container } = render(<Spinner color="#ff0000" />);
56
+ const svg = container.querySelector("svg");
57
+ expect(svg).toHaveAttribute("fill", "#ff0000");
58
+ });
59
+
60
+ it("applies custom className to the svg", () => {
61
+ const { container } = render(<Spinner className="custom-spinner" />);
62
+ const svg = container.querySelector("svg");
63
+ const classes = svg?.getAttribute("class") || "";
64
+ expect(classes).toContain("custom-spinner");
65
+ });
66
+
67
+ it("svg has aria-hidden true", () => {
68
+ const { container } = render(<Spinner />);
69
+ const svg = container.querySelector("svg");
70
+ expect(svg).toHaveAttribute("aria-hidden", "true");
71
+ });
72
+
73
+ it("has the correct displayName", () => {
74
+ expect(Spinner.displayName).toBe("Spinner");
75
+ });
76
+ });