@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,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
|
+
});
|