@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,277 @@
1
+ import "@testing-library/jest-dom";
2
+
3
+ import React from "react";
4
+ import CookieBanner from "./index";
5
+
6
+ import { act, fireEvent, render, screen } from "@testing-library/react";
7
+
8
+ // Mock cookie utilities
9
+ const mockGetCookie = jest.fn();
10
+ const mockSetCookie = jest.fn();
11
+
12
+ jest.mock("../../../utils/cookie", () => ({
13
+ getCookie: (...args: any[]) => mockGetCookie(...args),
14
+ setCookie: (...args: any[]) => mockSetCookie(...args),
15
+ }));
16
+
17
+ jest.mock("@shared/components/material-icon", () => ({
18
+ MaterialIcon: ({ name }: any) => (
19
+ <span data-testid={`icon-${name}`}>{name}</span>
20
+ ),
21
+ }));
22
+
23
+ jest.mock("@shared/contentful/blocks/button", () => ({
24
+ Button: (props: any) => (
25
+ <button onClick={props.onClick} data-testid="cookie-button">
26
+ {props.children}
27
+ </button>
28
+ ),
29
+ }));
30
+
31
+ describe("CookieBanner", () => {
32
+ const defaultContent = {
33
+ entryName: "Cookie Notice",
34
+ anchorId: "cookie-banner",
35
+ title: "Cookies",
36
+ richText: <p>We use cookies to improve your experience.</p>,
37
+ };
38
+
39
+ beforeEach(() => {
40
+ jest.useFakeTimers();
41
+ mockGetCookie.mockReset();
42
+ mockSetCookie.mockReset();
43
+ // Default: cookie not set (banner should show)
44
+ mockGetCookie.mockReturnValue(null);
45
+ // Reset body classes
46
+ document.body.classList.remove("cookie-banner-visible");
47
+ // Reset CSS custom property
48
+ document.documentElement.style.removeProperty("--cookie-banner-height");
49
+ });
50
+
51
+ afterEach(() => {
52
+ jest.useRealTimers();
53
+ });
54
+
55
+ describe("cookie management", () => {
56
+ it("shows the banner when cookieBannerClosed cookie is not set", () => {
57
+ mockGetCookie.mockReturnValue(null);
58
+ render(<CookieBanner content={defaultContent} />);
59
+ expect(
60
+ screen.getByLabelText("Cookie usage notification")
61
+ ).toBeInTheDocument();
62
+ });
63
+
64
+ it("hides the banner when cookieBannerClosed cookie is true", () => {
65
+ mockGetCookie.mockReturnValue("true");
66
+ const { container } = render(<CookieBanner content={defaultContent} />);
67
+ expect(container.firstChild).toBeNull();
68
+ });
69
+
70
+ it("calls getCookie with cookieBannerClosed key", () => {
71
+ render(<CookieBanner content={defaultContent} />);
72
+ expect(mockGetCookie).toHaveBeenCalledWith("cookieBannerClosed");
73
+ });
74
+
75
+ it("sets cookieBannerClosed cookie on close", () => {
76
+ mockGetCookie.mockReturnValue(null);
77
+ render(<CookieBanner content={defaultContent} />);
78
+ fireEvent.click(screen.getByTestId("cookie-button"));
79
+ expect(mockSetCookie).toHaveBeenCalledWith(
80
+ "cookieBannerClosed",
81
+ true,
82
+ expect.objectContaining({ expires: expect.any(Date) })
83
+ );
84
+ });
85
+
86
+ it("sets cookie with correct expiration (30 days)", () => {
87
+ mockGetCookie.mockReturnValue(null);
88
+ render(<CookieBanner content={defaultContent} />);
89
+
90
+ const now = Date.now();
91
+ fireEvent.click(screen.getByTestId("cookie-button"));
92
+
93
+ const callArgs = mockSetCookie.mock.calls[0];
94
+ const expirationDate = callArgs[2].expires;
95
+ // 43200 minutes * 60 * 1000 = 30 days in milliseconds
96
+ const expectedMs = 43200 * 60 * 1000;
97
+ const diff = expirationDate.getTime() - now;
98
+ expect(diff).toBeGreaterThanOrEqual(expectedMs - 1000);
99
+ expect(diff).toBeLessThanOrEqual(expectedMs + 1000);
100
+ });
101
+ });
102
+
103
+ describe("rendering", () => {
104
+ it("renders with fixed positioning", () => {
105
+ render(<CookieBanner content={defaultContent} />);
106
+ const banner = screen.getByLabelText("Cookie usage notification");
107
+ expect(banner.className).toContain("fixed");
108
+ });
109
+
110
+ it("renders the rich text content", () => {
111
+ render(<CookieBanner content={defaultContent} />);
112
+ expect(
113
+ screen.getByText("We use cookies to improve your experience.")
114
+ ).toBeInTheDocument();
115
+ });
116
+
117
+ it("does not render rich text when richText is falsy", () => {
118
+ const content = { ...defaultContent, richText: null as any };
119
+ render(<CookieBanner content={content} />);
120
+ const banner = screen.getByLabelText("Cookie usage notification");
121
+ // Banner is shown but no rich text inside
122
+ expect(banner.querySelector(".mx-auto")).toBeNull();
123
+ });
124
+
125
+ it("renders close button with close icon", () => {
126
+ render(<CookieBanner content={defaultContent} />);
127
+ expect(screen.getByTestId("icon-close")).toBeInTheDocument();
128
+ });
129
+
130
+ it("has correct z-index class", () => {
131
+ render(<CookieBanner content={defaultContent} />);
132
+ const banner = screen.getByLabelText("Cookie usage notification");
133
+ expect(banner.className).toContain("z-[1000]");
134
+ });
135
+
136
+ it("renders with correct max width", () => {
137
+ render(<CookieBanner content={defaultContent} />);
138
+ const banner = screen.getByLabelText("Cookie usage notification");
139
+ expect(banner.className).toContain("max-w-[350px]");
140
+ });
141
+
142
+ it("renders with correct positioning styles", () => {
143
+ render(<CookieBanner content={defaultContent} />);
144
+ const banner = screen.getByLabelText("Cookie usage notification");
145
+ expect(banner.style.bottom).toBe("0px");
146
+ expect(banner.style.right).toBe("20px");
147
+ });
148
+
149
+ it("has id cookie-banner", () => {
150
+ render(<CookieBanner content={defaultContent} />);
151
+ expect(document.getElementById("cookie-banner")).toBeInTheDocument();
152
+ });
153
+ });
154
+
155
+ describe("close behavior", () => {
156
+ it("hides banner on close button click", () => {
157
+ mockGetCookie.mockReturnValue(null);
158
+ render(<CookieBanner content={defaultContent} />);
159
+ expect(
160
+ screen.getByLabelText("Cookie usage notification")
161
+ ).toBeInTheDocument();
162
+ fireEvent.click(screen.getByTestId("cookie-button"));
163
+ expect(
164
+ screen.queryByLabelText("Cookie usage notification")
165
+ ).not.toBeInTheDocument();
166
+ });
167
+ });
168
+
169
+ describe("body class management", () => {
170
+ it("adds cookie-banner-visible class to body when visible", () => {
171
+ mockGetCookie.mockReturnValue(null);
172
+ render(<CookieBanner content={defaultContent} />);
173
+ expect(document.body.classList.contains("cookie-banner-visible")).toBe(
174
+ true
175
+ );
176
+ });
177
+
178
+ it("removes cookie-banner-visible class when banner is hidden", () => {
179
+ mockGetCookie.mockReturnValue(null);
180
+ render(<CookieBanner content={defaultContent} />);
181
+ fireEvent.click(screen.getByTestId("cookie-button"));
182
+ expect(document.body.classList.contains("cookie-banner-visible")).toBe(
183
+ false
184
+ );
185
+ });
186
+
187
+ it("removes cookie-banner-visible class on unmount", () => {
188
+ mockGetCookie.mockReturnValue(null);
189
+ const { unmount } = render(<CookieBanner content={defaultContent} />);
190
+ expect(document.body.classList.contains("cookie-banner-visible")).toBe(
191
+ true
192
+ );
193
+ unmount();
194
+ expect(document.body.classList.contains("cookie-banner-visible")).toBe(
195
+ false
196
+ );
197
+ });
198
+ });
199
+
200
+ describe("sticky footer detection", () => {
201
+ it("uses larger margin when anchored-banner element exists", () => {
202
+ const anchoredBanner = document.createElement("div");
203
+ anchoredBanner.id = "anchored-banner";
204
+ document.body.appendChild(anchoredBanner);
205
+
206
+ mockGetCookie.mockReturnValue(null);
207
+ render(<CookieBanner content={defaultContent} />);
208
+ const banner = screen.getByLabelText("Cookie usage notification");
209
+ // marginBottom = 14 * 4 = 56px when anchored-banner exists
210
+ expect(banner.style.marginBottom).toBe("56px");
211
+
212
+ document.body.removeChild(anchoredBanner);
213
+ });
214
+
215
+ it("uses smaller margin when anchored-banner does not exist", () => {
216
+ mockGetCookie.mockReturnValue(null);
217
+ render(<CookieBanner content={defaultContent} />);
218
+ const banner = screen.getByLabelText("Cookie usage notification");
219
+ // marginBottom = 3 * 4 = 12px when no anchored-banner
220
+ expect(banner.style.marginBottom).toBe("12px");
221
+ });
222
+ });
223
+
224
+ describe("CSS custom property for height", () => {
225
+ it("calculates and sets --cookie-banner-height property", () => {
226
+ mockGetCookie.mockReturnValue(null);
227
+ render(<CookieBanner content={defaultContent} />);
228
+
229
+ act(() => {
230
+ jest.advanceTimersByTime(350);
231
+ });
232
+
233
+ // The property should be set (value depends on getBoundingClientRect mock)
234
+ // In jsdom, getBoundingClientRect returns zeros, so spaceFromBottom = innerHeight - 0
235
+ const value = document.documentElement.style.getPropertyValue(
236
+ "--cookie-banner-height"
237
+ );
238
+ // Should be set (even though value may be 0px in jsdom)
239
+ expect(value).toBeDefined();
240
+ });
241
+
242
+ it("removes --cookie-banner-height on unmount", () => {
243
+ mockGetCookie.mockReturnValue(null);
244
+ const { unmount } = render(<CookieBanner content={defaultContent} />);
245
+
246
+ act(() => {
247
+ jest.advanceTimersByTime(350);
248
+ });
249
+
250
+ unmount();
251
+ const value = document.documentElement.style.getPropertyValue(
252
+ "--cookie-banner-height"
253
+ );
254
+ expect(value).toBe("");
255
+ });
256
+
257
+ it("recalculates height on window resize", () => {
258
+ mockGetCookie.mockReturnValue(null);
259
+ render(<CookieBanner content={defaultContent} />);
260
+
261
+ act(() => {
262
+ jest.advanceTimersByTime(350);
263
+ });
264
+
265
+ act(() => {
266
+ window.dispatchEvent(new Event("resize"));
267
+ jest.advanceTimersByTime(200);
268
+ });
269
+
270
+ // Should not throw and property remains set
271
+ const value = document.documentElement.style.getPropertyValue(
272
+ "--cookie-banner-height"
273
+ );
274
+ expect(value).toBeDefined();
275
+ });
276
+ });
277
+ });
@@ -0,0 +1,244 @@
1
+ import "@testing-library/jest-dom";
2
+
3
+ import React from "react";
4
+ import { CtaCallout } from "./index";
5
+ import { CtaCalloutProps } from "./types";
6
+
7
+ import { render, screen } from "@testing-library/react";
8
+
9
+ jest.mock("@shared/components/text", () => ({
10
+ Text: ({ children, as, ...props }: any) => {
11
+ const Tag = as || "span";
12
+ return <Tag {...props}>{children}</Tag>;
13
+ },
14
+ }));
15
+
16
+ jest.mock("@shared/contentful/blocks/button", () => ({
17
+ Button: (props: any) => (
18
+ <button
19
+ data-testid="cta-button"
20
+ data-size={JSON.stringify(props.size)}
21
+ data-href={props.href}
22
+ >
23
+ {props.buttonLabel || props.children}
24
+ </button>
25
+ ),
26
+ }));
27
+
28
+ describe("CtaCallout", () => {
29
+ const defaultProps: CtaCalloutProps = {
30
+ title: "Get Started Today",
31
+ subTitle: "Best Internet Plans",
32
+ description: <p>Choose the plan that works for you.</p>,
33
+ button: { buttonLabel: "Shop Now", href: "/shop" },
34
+ background: "white",
35
+ color: "dark",
36
+ contentAlignment: "center",
37
+ descriptionAlignment: "center",
38
+ };
39
+
40
+ it("renders the component", () => {
41
+ const { container } = render(<CtaCallout {...defaultProps} />);
42
+ expect(container.firstChild).toBeInTheDocument();
43
+ });
44
+
45
+ it("renders title as h2 by default", () => {
46
+ render(<CtaCallout {...defaultProps} />);
47
+ const title = screen.getByText("Get Started Today");
48
+ expect(title.tagName).toBe("H2");
49
+ });
50
+
51
+ it("renders title as h1 when enableHeading is true", () => {
52
+ render(<CtaCallout {...defaultProps} enableHeading={true} />);
53
+ const title = screen.getByText("Get Started Today");
54
+ expect(title.tagName).toBe("H1");
55
+ });
56
+
57
+ it("renders subtitle as h3", () => {
58
+ render(<CtaCallout {...defaultProps} />);
59
+ const subtitle = screen.getByText("Best Internet Plans");
60
+ expect(subtitle.tagName).toBe("H3");
61
+ });
62
+
63
+ it("renders description", () => {
64
+ render(<CtaCallout {...defaultProps} />);
65
+ expect(
66
+ screen.getByText("Choose the plan that works for you.")
67
+ ).toBeInTheDocument();
68
+ });
69
+
70
+ it("renders the button", () => {
71
+ render(<CtaCallout {...defaultProps} />);
72
+ expect(screen.getByTestId("cta-button")).toBeInTheDocument();
73
+ });
74
+
75
+ it("does not render title when not provided", () => {
76
+ render(<CtaCallout {...defaultProps} title={undefined} />);
77
+ expect(screen.queryByText("Get Started Today")).not.toBeInTheDocument();
78
+ });
79
+
80
+ it("does not render subtitle when not provided", () => {
81
+ render(<CtaCallout {...defaultProps} subTitle={undefined} />);
82
+ expect(screen.queryByText("Best Internet Plans")).not.toBeInTheDocument();
83
+ });
84
+
85
+ it("does not render description when not provided", () => {
86
+ render(<CtaCallout {...defaultProps} description={undefined} />);
87
+ expect(
88
+ screen.queryByText("Choose the plan that works for you.")
89
+ ).not.toBeInTheDocument();
90
+ });
91
+
92
+ describe("background colors", () => {
93
+ it("applies white background class", () => {
94
+ const { container } = render(
95
+ <CtaCallout {...defaultProps} background="white" />
96
+ );
97
+ expect(container.firstChild).toHaveClass("bg-bg");
98
+ });
99
+
100
+ it("applies navy background class", () => {
101
+ const { container } = render(
102
+ <CtaCallout {...defaultProps} background="navy" />
103
+ );
104
+ expect(container.firstChild).toHaveClass("bg-bg-fill-inverse");
105
+ });
106
+
107
+ it("applies blue background class", () => {
108
+ const { container } = render(
109
+ <CtaCallout {...defaultProps} background="blue" />
110
+ );
111
+ expect(container.firstChild).toHaveClass("bg-bg-fill-brand");
112
+ });
113
+
114
+ it("applies green background class", () => {
115
+ const { container } = render(
116
+ <CtaCallout {...defaultProps} background="green" />
117
+ );
118
+ expect(container.firstChild).toHaveClass("bg-bg-fill-success");
119
+ });
120
+
121
+ it("applies purple background class", () => {
122
+ const { container } = render(
123
+ <CtaCallout {...defaultProps} background="purple" />
124
+ );
125
+ expect(container.firstChild).toHaveClass("bg-bg-fill-brand-tertiary");
126
+ });
127
+
128
+ it("applies yellow background class", () => {
129
+ const { container } = render(
130
+ <CtaCallout {...defaultProps} background="yellow" />
131
+ );
132
+ expect(container.firstChild).toHaveClass("bg-bg-fill-brand-accent");
133
+ });
134
+ });
135
+
136
+ describe("text colors", () => {
137
+ it("applies dark text color", () => {
138
+ const { container } = render(
139
+ <CtaCallout {...defaultProps} color="dark" />
140
+ );
141
+ const inner = container.querySelector(".flex.flex-col");
142
+ expect(inner?.className).toContain("text-text");
143
+ });
144
+
145
+ it("applies light text color", () => {
146
+ const { container } = render(
147
+ <CtaCallout {...defaultProps} color="light" />
148
+ );
149
+ const inner = container.querySelector(".flex.flex-col");
150
+ expect(inner?.className).toContain("text-text-inverse");
151
+ });
152
+ });
153
+
154
+ describe("alignment", () => {
155
+ it("applies content alignment to title", () => {
156
+ render(<CtaCallout {...defaultProps} contentAlignment="left" />);
157
+ const title = screen.getByText("Get Started Today");
158
+ expect(title.className).toContain("text-left");
159
+ });
160
+
161
+ it("applies content alignment to subtitle", () => {
162
+ render(<CtaCallout {...defaultProps} contentAlignment="right" />);
163
+ const subtitle = screen.getByText("Best Internet Plans");
164
+ expect(subtitle.className).toContain("text-right");
165
+ });
166
+
167
+ it("applies description alignment", () => {
168
+ render(<CtaCallout {...defaultProps} descriptionAlignment="left" />);
169
+ const descWrapper = screen
170
+ .getByText("Choose the plan that works for you.")
171
+ .closest("div");
172
+ expect(descWrapper?.className).toContain("text-left");
173
+ });
174
+ });
175
+
176
+ describe("maxWidth", () => {
177
+ it("applies max-w-120 when maxWidth is true (default)", () => {
178
+ const { container } = render(<CtaCallout {...defaultProps} />);
179
+ const inner = container.querySelector(".flex.flex-col");
180
+ expect(inner?.className).toContain("max-w-120");
181
+ expect(inner?.className).toContain("mx-auto");
182
+ });
183
+
184
+ it("does not apply max-w-120 when maxWidth is false", () => {
185
+ const { container } = render(
186
+ <CtaCallout {...defaultProps} maxWidth={false} />
187
+ );
188
+ const inner = container.querySelector(".flex.flex-col");
189
+ expect(inner?.className).not.toContain("max-w-120");
190
+ });
191
+ });
192
+
193
+ describe("button props", () => {
194
+ it("passes button props to Button component", () => {
195
+ render(<CtaCallout {...defaultProps} />);
196
+ const button = screen.getByTestId("cta-button");
197
+ expect(button).toHaveAttribute("data-href", "/shop");
198
+ });
199
+
200
+ it("passes size prop with base large", () => {
201
+ render(<CtaCallout {...defaultProps} />);
202
+ const button = screen.getByTestId("cta-button");
203
+ expect(button.dataset.size).toContain("large");
204
+ });
205
+
206
+ it("passes onModalButtonClick to Button", () => {
207
+ const onModalButtonClick = jest.fn();
208
+ render(
209
+ <CtaCallout {...defaultProps} onModalButtonClick={onModalButtonClick} />
210
+ );
211
+ const button = screen.getByTestId("cta-button");
212
+ expect(button).toBeInTheDocument();
213
+ });
214
+
215
+ it("passes renderCheckPlans to Button", () => {
216
+ const renderCheckPlans = jest.fn();
217
+ render(
218
+ <CtaCallout {...defaultProps} renderCheckPlans={renderCheckPlans} />
219
+ );
220
+ const button = screen.getByTestId("cta-button");
221
+ expect(button).toBeInTheDocument();
222
+ });
223
+ });
224
+
225
+ describe("section rendering", () => {
226
+ it("has component-container class", () => {
227
+ const { container } = render(<CtaCallout {...defaultProps} />);
228
+ expect(container.firstChild).toHaveClass("component-container");
229
+ });
230
+
231
+ it("has correct padding classes", () => {
232
+ const { container } = render(<CtaCallout {...defaultProps} />);
233
+ expect(container.firstChild).toHaveClass("px-5");
234
+ expect(container.firstChild).toHaveClass("py-16");
235
+ });
236
+
237
+ it("button container has flex and justify-center", () => {
238
+ render(<CtaCallout {...defaultProps} />);
239
+ const buttonWrapper = screen.getByTestId("cta-button").parentElement;
240
+ expect(buttonWrapper?.className).toContain("flex");
241
+ expect(buttonWrapper?.className).toContain("justify-center");
242
+ });
243
+ });
244
+ });