@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,240 @@
1
+ import React from "react";
2
+ import { DynamicTabs } from "./index";
3
+
4
+ import { act, fireEvent, render, screen } from "@testing-library/react";
5
+
6
+ jest.mock("framer-motion", () => ({
7
+ AnimatePresence: ({ children }: any) => <>{children}</>,
8
+ motion: {
9
+ div: ({ children, animate }: any) => (
10
+ <div data-testid="motion-div" style={{ opacity: animate?.opacity }}>
11
+ {children}
12
+ </div>
13
+ ),
14
+ },
15
+ }));
16
+
17
+ jest.mock("@shared/components/collapse", () => ({
18
+ Collapse: ({ open, children }: any) => (
19
+ <div data-testid="collapse" data-open={open}>
20
+ {open ? children : null}
21
+ </div>
22
+ ),
23
+ }));
24
+
25
+ jest.mock("@shared/components/material-icon", () => ({
26
+ MaterialIcon: ({ name }: any) => (
27
+ <span data-testid="material-icon">{name}</span>
28
+ ),
29
+ }));
30
+
31
+ jest.mock("@shared/components/text", () => ({
32
+ Text: ({ children, className }: any) => (
33
+ <span className={className}>{children}</span>
34
+ ),
35
+ }));
36
+
37
+ jest.mock("@shared/contentful/blocks/button", () => ({
38
+ Button: ({ children, onClick, buttonClassName }: any) => (
39
+ <button
40
+ data-testid="tab-button"
41
+ onClick={onClick}
42
+ className={buttonClassName}
43
+ >
44
+ {children}
45
+ </button>
46
+ ),
47
+ }));
48
+
49
+ jest.mock("@shared/utils", () => ({
50
+ cx: (...args: any[]) => args.filter(Boolean).join(" "),
51
+ }));
52
+
53
+ describe("DynamicTabs", () => {
54
+ const defaultProps = {
55
+ tabs: {
56
+ items: [
57
+ { tabName: "Tab 1", anchorId: "tab-1", tabContent: { items: [] } },
58
+ { tabName: "Tab 2", anchorId: "tab-2", tabContent: { items: [] } },
59
+ { tabName: "Tab 3", anchorId: "tab-3", tabContent: { items: [] } },
60
+ ],
61
+ },
62
+ tabsContent: [
63
+ <div key="1">Content 1</div>,
64
+ <div key="2">Content 2</div>,
65
+ <div key="3">Content 3</div>,
66
+ ],
67
+ };
68
+
69
+ beforeEach(() => {
70
+ jest.clearAllMocks();
71
+ window.location.hash = "";
72
+ });
73
+
74
+ describe("Rendering", () => {
75
+ it("renders all tab buttons", () => {
76
+ render(<DynamicTabs {...defaultProps} />);
77
+ expect(screen.getAllByText("Tab 1").length).toBeGreaterThan(0);
78
+ expect(screen.getAllByText("Tab 2").length).toBeGreaterThan(0);
79
+ expect(screen.getAllByText("Tab 3").length).toBeGreaterThan(0);
80
+ });
81
+
82
+ it("renders tab content", () => {
83
+ render(<DynamicTabs {...defaultProps} />);
84
+ expect(screen.getAllByText("Content 1").length).toBeGreaterThan(0);
85
+ });
86
+
87
+ it("renders title when provided", () => {
88
+ render(<DynamicTabs {...defaultProps} title="My Tabs" />);
89
+ expect(screen.getByText("My Tabs")).toBeInTheDocument();
90
+ });
91
+
92
+ it("renders subtitle when provided", () => {
93
+ render(<DynamicTabs {...defaultProps} subTitle="A subtitle" />);
94
+ expect(screen.getByText("A subtitle")).toBeInTheDocument();
95
+ });
96
+
97
+ it("does not render title section when no title or subtitle", () => {
98
+ const { container } = render(<DynamicTabs {...defaultProps} />);
99
+ expect(container.querySelector(".mb-8.mt-5")).not.toBeInTheDocument();
100
+ });
101
+ });
102
+
103
+ describe("Tab switching", () => {
104
+ it("switches tab content on click", () => {
105
+ render(<DynamicTabs {...defaultProps} />);
106
+ const tabButtons = screen.getAllByTestId("tab-button");
107
+ // Find a Tab 2 button and click it
108
+ const tab2Buttons = tabButtons.filter(btn => btn.textContent === "Tab 2");
109
+ fireEvent.click(tab2Buttons[0]);
110
+ // After clicking Tab 2, the motion-div for tab 2 should show opacity 1
111
+ const motionDivs = screen.getAllByTestId("motion-div");
112
+ expect(motionDivs.length).toBeGreaterThan(0);
113
+ });
114
+
115
+ it("first tab is active by default", () => {
116
+ render(<DynamicTabs {...defaultProps} />);
117
+ const tabButtons = screen.getAllByTestId("tab-button");
118
+ const firstTabBtn = tabButtons.find(btn => btn.textContent === "Tab 1");
119
+ expect(firstTabBtn?.className).toContain("border-b-2");
120
+ });
121
+ });
122
+
123
+ describe("Navigation type", () => {
124
+ it("renders tab view on desktop when navigationType is true", () => {
125
+ const { container } = render(
126
+ <DynamicTabs {...defaultProps} navigationType={true} />
127
+ );
128
+ // Desktop view should have the overflow-x-auto div in "hidden md:block"
129
+ const desktopBlock = container.querySelector(".hidden.md\\:block");
130
+ expect(desktopBlock).toBeInTheDocument();
131
+ });
132
+
133
+ it("renders sidebar view on desktop when navigationType is false", () => {
134
+ const { container } = render(
135
+ <DynamicTabs {...defaultProps} navigationType={false} />
136
+ );
137
+ const desktopBlock = container.querySelector(".hidden.md\\:block");
138
+ expect(desktopBlock).toBeInTheDocument();
139
+ });
140
+ });
141
+
142
+ describe("Mobile view", () => {
143
+ it("renders tab view on mobile when mobileSidebarTabType is true", () => {
144
+ const { container } = render(
145
+ <DynamicTabs {...defaultProps} mobileSidebarTabType={true} />
146
+ );
147
+ const mobileBlock = container.querySelector(".md\\:hidden");
148
+ expect(mobileBlock).toBeInTheDocument();
149
+ });
150
+
151
+ it("renders accordion view on mobile when mobileSidebarTabType is false", () => {
152
+ render(<DynamicTabs {...defaultProps} mobileSidebarTabType={false} />);
153
+ // Accordion uses Collapse component
154
+ const collapses = screen.getAllByTestId("collapse");
155
+ expect(collapses.length).toBeGreaterThan(0);
156
+ });
157
+
158
+ it("clicks accordion button to switch tabs", () => {
159
+ window.location.hash = "#tab-1";
160
+ const { container } = render(
161
+ <DynamicTabs {...defaultProps} mobileSidebarTabType={false} />
162
+ );
163
+ // Accordion buttons are inside the mobile container (.md\:hidden)
164
+ const mobileContainer = container.querySelector(".md\\:hidden");
165
+ const accordionButtons = mobileContainer!.querySelectorAll(
166
+ "[data-testid='tab-button']"
167
+ );
168
+ // Click the second accordion button (Tab 2)
169
+ fireEvent.click(accordionButtons[1]);
170
+ expect(window.location.hash).toBe("#tab-2");
171
+ });
172
+ });
173
+
174
+ describe("Sidebar view", () => {
175
+ it("clicks sidebar button to switch tabs", () => {
176
+ window.location.hash = "#tab-1";
177
+ const { container } = render(
178
+ <DynamicTabs {...defaultProps} navigationType={false} />
179
+ );
180
+ // Sidebar buttons are inside the desktop container (.hidden.md\:block)
181
+ const desktopContainer = container.querySelector(".hidden.md\\:block");
182
+ const sidebarButtons = desktopContainer!.querySelectorAll(
183
+ "[data-testid='tab-button']"
184
+ );
185
+ // Click the third sidebar button (Tab 3)
186
+ fireEvent.click(sidebarButtons[2]);
187
+ expect(window.location.hash).toBe("#tab-3");
188
+ });
189
+ });
190
+
191
+ describe("Hash navigation", () => {
192
+ it("sets tab based on URL hash", () => {
193
+ window.location.hash = "#tab-2";
194
+ render(<DynamicTabs {...defaultProps} />);
195
+ // After useEffect runs, tab 2 should be active
196
+ const tabButtons = screen.getAllByTestId("tab-button");
197
+ const tab2Btns = tabButtons.filter(btn => btn.textContent === "Tab 2");
198
+ // At least one Tab 2 button should have the active style
199
+ const hasActiveTab2 = tab2Btns.some(btn =>
200
+ btn.className.includes("border-b-2")
201
+ );
202
+ expect(hasActiveTab2).toBe(true);
203
+ });
204
+
205
+ it("updates hash on tab click when fragment exists", () => {
206
+ window.location.hash = "#tab-1";
207
+ render(<DynamicTabs {...defaultProps} />);
208
+ const tabButtons = screen.getAllByTestId("tab-button");
209
+ const tab2Btns = tabButtons.filter(btn => btn.textContent === "Tab 2");
210
+ fireEvent.click(tab2Btns[0]);
211
+ expect(window.location.hash).toBe("#tab-2");
212
+ });
213
+
214
+ it("listens to hashchange events", () => {
215
+ render(<DynamicTabs {...defaultProps} />);
216
+ act(() => {
217
+ window.location.hash = "#tab-3";
218
+ window.dispatchEvent(new HashChangeEvent("hashchange"));
219
+ });
220
+ const tabButtons = screen.getAllByTestId("tab-button");
221
+ const tab3Btns = tabButtons.filter(btn => btn.textContent === "Tab 3");
222
+ const hasActiveTab3 = tab3Btns.some(btn =>
223
+ btn.className.includes("border-b-2")
224
+ );
225
+ expect(hasActiveTab3).toBe(true);
226
+ });
227
+ });
228
+
229
+ describe("Empty state", () => {
230
+ it("renders without tabs", () => {
231
+ const { container } = render(<DynamicTabs />);
232
+ expect(container.firstChild).toBeInTheDocument();
233
+ });
234
+
235
+ it("renders without tabsContent", () => {
236
+ render(<DynamicTabs tabs={defaultProps.tabs} />);
237
+ expect(screen.getAllByTestId("tab-button").length).toBeGreaterThan(0);
238
+ });
239
+ });
240
+ });
@@ -0,0 +1,213 @@
1
+ import { EmailInputBlock } from "./index";
2
+
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+
5
+ describe("EmailInputBlock", () => {
6
+ const defaultProps = {
7
+ onSubmit: jest.fn(),
8
+ };
9
+
10
+ beforeEach(() => {
11
+ jest.clearAllMocks();
12
+ });
13
+
14
+ it("renders with default props", () => {
15
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
16
+ expect(container.querySelector("#email-input")).toBeInTheDocument();
17
+ });
18
+
19
+ it("renders custom anchorId", () => {
20
+ const { container } = render(
21
+ <EmailInputBlock {...defaultProps} anchorId="custom-anchor" />
22
+ );
23
+ expect(container.querySelector("#custom-anchor")).toBeInTheDocument();
24
+ });
25
+
26
+ it("renders title when provided", () => {
27
+ render(<EmailInputBlock {...defaultProps} title="Join us" />);
28
+ expect(screen.getByText("Join us")).toBeInTheDocument();
29
+ expect(screen.getByText("Join us").tagName).toBe("H1");
30
+ });
31
+
32
+ it("does not render title when not provided", () => {
33
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
34
+ expect(container.querySelector("h1")).not.toBeInTheDocument();
35
+ });
36
+
37
+ it("renders subTitle when provided", () => {
38
+ render(<EmailInputBlock {...defaultProps} subTitle="Stay updated" />);
39
+ expect(screen.getByText("Stay updated")).toBeInTheDocument();
40
+ expect(screen.getByText("Stay updated").tagName).toBe("H2");
41
+ });
42
+
43
+ it("does not render subTitle when not provided", () => {
44
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
45
+ expect(container.querySelector("h2")).not.toBeInTheDocument();
46
+ });
47
+
48
+ it("renders emailInputDescription when provided", () => {
49
+ render(
50
+ <EmailInputBlock {...defaultProps} emailInputDescription="Sign up now" />
51
+ );
52
+ expect(screen.getByText("Sign up now")).toBeInTheDocument();
53
+ });
54
+
55
+ it("renders caption when provided", () => {
56
+ render(<EmailInputBlock {...defaultProps} caption="No spam" />);
57
+ expect(screen.getByText("No spam")).toBeInTheDocument();
58
+ });
59
+
60
+ it("does not render caption when not provided", () => {
61
+ render(<EmailInputBlock {...defaultProps} />);
62
+ expect(screen.queryByText("No spam")).not.toBeInTheDocument();
63
+ });
64
+
65
+ it("uses default placeholder text", () => {
66
+ render(<EmailInputBlock {...defaultProps} />);
67
+ expect(
68
+ screen.getByPlaceholderText("Enter your email here")
69
+ ).toBeInTheDocument();
70
+ });
71
+
72
+ it("uses custom placeholder text", () => {
73
+ render(<EmailInputBlock {...defaultProps} inputPlaceholder="Your email" />);
74
+ expect(screen.getByPlaceholderText("Your email")).toBeInTheDocument();
75
+ });
76
+
77
+ it("calls onSubmit with email on valid submission", () => {
78
+ render(<EmailInputBlock {...defaultProps} />);
79
+ const input = screen.getByPlaceholderText("Enter your email here");
80
+ fireEvent.change(input, { target: { value: "test@example.com" } });
81
+ fireEvent.click(screen.getByText("Sign me up"));
82
+ expect(defaultProps.onSubmit).toHaveBeenCalledWith({
83
+ email: "test@example.com",
84
+ });
85
+ });
86
+
87
+ it("clears email after successful submission", () => {
88
+ render(<EmailInputBlock {...defaultProps} />);
89
+ const input = screen.getByPlaceholderText(
90
+ "Enter your email here"
91
+ ) as HTMLInputElement;
92
+ fireEvent.change(input, { target: { value: "test@example.com" } });
93
+ fireEvent.click(screen.getByText("Sign me up"));
94
+ expect(input.value).toBe("");
95
+ });
96
+
97
+ it("shows error for invalid email", () => {
98
+ render(<EmailInputBlock {...defaultProps} />);
99
+ const input = screen.getByPlaceholderText("Enter your email here");
100
+ fireEvent.change(input, { target: { value: "invalid" } });
101
+ fireEvent.click(screen.getByText("Sign me up"));
102
+ expect(
103
+ screen.getByText("Please enter a valid email address.")
104
+ ).toBeInTheDocument();
105
+ expect(defaultProps.onSubmit).not.toHaveBeenCalled();
106
+ });
107
+
108
+ it("clears error when user types after error", () => {
109
+ render(<EmailInputBlock {...defaultProps} />);
110
+ const input = screen.getByPlaceholderText("Enter your email here");
111
+ fireEvent.change(input, { target: { value: "invalid" } });
112
+ fireEvent.click(screen.getByText("Sign me up"));
113
+ expect(
114
+ screen.getByText("Please enter a valid email address.")
115
+ ).toBeInTheDocument();
116
+
117
+ fireEvent.change(input, { target: { value: "a" } });
118
+ expect(
119
+ screen.queryByText("Please enter a valid email address.")
120
+ ).not.toBeInTheDocument();
121
+ });
122
+
123
+ it("shows success feedback after valid submission", () => {
124
+ render(<EmailInputBlock {...defaultProps} successFeedback="Thanks!" />);
125
+ const input = screen.getByPlaceholderText("Enter your email here");
126
+ fireEvent.change(input, { target: { value: "user@test.com" } });
127
+ fireEvent.click(screen.getByText("Sign me up"));
128
+ expect(screen.getByText("Thanks!")).toBeInTheDocument();
129
+ });
130
+
131
+ it("hides success feedback when user types again", () => {
132
+ render(<EmailInputBlock {...defaultProps} successFeedback="Thanks!" />);
133
+ const input = screen.getByPlaceholderText("Enter your email here");
134
+ fireEvent.change(input, { target: { value: "user@test.com" } });
135
+ fireEvent.click(screen.getByText("Sign me up"));
136
+ expect(screen.getByText("Thanks!")).toBeInTheDocument();
137
+
138
+ fireEvent.change(input, { target: { value: "x" } });
139
+ expect(screen.queryByText("Thanks!")).not.toBeInTheDocument();
140
+ });
141
+
142
+ it("does not show success feedback without successFeedback prop", () => {
143
+ render(<EmailInputBlock {...defaultProps} />);
144
+ const input = screen.getByPlaceholderText("Enter your email here");
145
+ fireEvent.change(input, { target: { value: "user@test.com" } });
146
+ fireEvent.click(screen.getByText("Sign me up"));
147
+ // No success text rendered
148
+ expect(screen.queryByText("Thanks!")).not.toBeInTheDocument();
149
+ });
150
+
151
+ it("applies green background by default", () => {
152
+ const { container } = render(<EmailInputBlock {...defaultProps} />);
153
+ const inner = container.querySelector(".bg-bg-fill-success");
154
+ expect(inner).toBeInTheDocument();
155
+ });
156
+
157
+ it("applies navy background class", () => {
158
+ const { container } = render(
159
+ <EmailInputBlock {...defaultProps} backgroundColor="navy" />
160
+ );
161
+ expect(container.querySelector(".bg-bg-fill-inverse")).toBeInTheDocument();
162
+ });
163
+
164
+ it("applies blue background class", () => {
165
+ const { container } = render(
166
+ <EmailInputBlock {...defaultProps} backgroundColor="blue" />
167
+ );
168
+ expect(container.querySelector(".bg-bg-fill-brand")).toBeInTheDocument();
169
+ });
170
+
171
+ it("applies yellow background class", () => {
172
+ const { container } = render(
173
+ <EmailInputBlock {...defaultProps} backgroundColor="yellow" />
174
+ );
175
+ expect(
176
+ container.querySelector(".bg-bg-fill-brand-accent")
177
+ ).toBeInTheDocument();
178
+ });
179
+
180
+ it("applies white background class", () => {
181
+ const { container } = render(
182
+ <EmailInputBlock {...defaultProps} backgroundColor="white" />
183
+ );
184
+ expect(container.querySelector(".bg-white")).toBeInTheDocument();
185
+ });
186
+
187
+ it("uses white text for green/blue/navy backgrounds", () => {
188
+ render(
189
+ <EmailInputBlock
190
+ {...defaultProps}
191
+ backgroundColor="navy"
192
+ title="Navy title"
193
+ />
194
+ );
195
+ expect(screen.getByText("Navy title").className).toContain("text-white");
196
+ });
197
+
198
+ it("uses dark text for white/yellow backgrounds", () => {
199
+ render(
200
+ <EmailInputBlock
201
+ {...defaultProps}
202
+ backgroundColor="yellow"
203
+ title="Yellow title"
204
+ />
205
+ );
206
+ expect(screen.getByText("Yellow title").className).toContain("text-text");
207
+ });
208
+
209
+ it("uses custom ctaText", () => {
210
+ render(<EmailInputBlock {...defaultProps} ctaText="Subscribe" />);
211
+ expect(screen.getByText("Subscribe")).toBeInTheDocument();
212
+ });
213
+ });
@@ -20,19 +20,18 @@ export const EmailInputBlock: React.FC<EmailInputBlockProps> = props => {
20
20
 
21
21
  const bgClasses: Record<string, string> = {
22
22
  navy: "bg-bg-fill-inverse",
23
- green: "bg-bg-fill-success",
23
+ green: "bg-bg-fill-brand",
24
24
  blue: "bg-bg-fill-brand",
25
25
  yellow: "bg-bg-fill-brand-accent",
26
- white: "bg-white",
26
+ white: "bg-bg",
27
27
  };
28
28
 
29
- const currentBgClass =
30
- bgClasses[backgroundColor as keyof typeof bgClasses] || bgClasses.green;
29
+ const normalizedBg = (backgroundColor as string)?.toLowerCase?.() || "green";
30
+ const bgColorClass =
31
+ bgClasses[normalizedBg as keyof typeof bgClasses] || bgClasses.green;
31
32
 
32
33
  // Determine if text should be white based on background
33
- const textWhite = ["green", "blue", "navy"].includes(
34
- backgroundColor as string
35
- );
34
+ const textWhite = ["green", "blue", "navy"].includes(normalizedBg);
36
35
  const textColor = textWhite ? "text-white" : "text-text";
37
36
 
38
37
  const [email, setEmail] = useState("");
@@ -56,41 +55,47 @@ export const EmailInputBlock: React.FC<EmailInputBlockProps> = props => {
56
55
  };
57
56
 
58
57
  return (
59
- <section id={anchorId} className="w-full px-4 py-8 md:px-0">
58
+ <section id={anchorId} className="w-full px-4 !pt-8 md:!pt-20 md:px-0">
60
59
  <div
61
- className={`mx-auto max-w-[1200px] overflow-hidden rounded-[32px] md:rounded-[20px] ${currentBgClass} flex flex-col items-start p-8 md:p-16`}
60
+ className={`mx-auto max-w-[1200px] overflow-hidden !rounded-[20px] md:rounded-[20px] flex flex-col items-start !px-5 !pt-6 !pb-10 md:!p-10 md:!pt-12 !h-[344px] md:!w-auto md:!h-[367px] ${bgColorClass}`}
61
+ data-testid="email-input-blocck"
62
62
  >
63
- <div className="lg:w-[548px]">
64
- {title && (
65
- <h1 className={`text-heading1 font-black ${textColor}`}>{title}</h1>
66
- )}
67
- {subTitle && (
68
- <h2 className={`text-heading1 font-black ${textColor}`}>
69
- {subTitle}
70
- </h2>
71
- )}
63
+ <div className="w-full">
64
+ <div className="lg:w-[548px]">
65
+ {title && (
66
+ <h1 className={`mb-3 text-[2.25rem] leading-[2.5rem] font-black md:text-heading1 md:font-black ${textColor}`}>{title}</h1>
67
+ )}
68
+ {subTitle && (
69
+ <h2 className={`mb-3 text-heading1 font-black ${textColor}`}>
70
+ {subTitle}
71
+ </h2>
72
+ )}
72
73
 
73
- {emailInputDescription && (
74
- <p className={`mb-10 text-subheading1 opacity-95 ${textColor}`}>
75
- {emailInputDescription}
76
- </p>
77
- )}
74
+ {emailInputDescription && (
75
+ <p className={`!mb-4 md:!mb-16 text-xl leading-7 font-medium md:text-subheading1 md:!leading-8 md:!font-bold opacity-95 ${textColor}`}>
76
+ {emailInputDescription}
77
+ </p>
78
+ )}
79
+ </div>
78
80
 
79
- <div className="max-w-2xl flex w-full items-center overflow-hidden rounded-3xl border border-transparent bg-white p-1.5 shadow-xl transition-all md:p-2">
80
- <input
81
- type="email"
82
- placeholder={inputPlaceholder}
83
- value={email}
84
- onChange={e => {
85
- setEmail(e.target.value);
86
- if (emailError) setEmailError("");
87
- if (formSubmitted) setFormSubmitted(false);
88
- }}
89
- className="w-full flex-grow bg-transparent px-6 py-3 text-lg font-medium text-text outline-none placeholder:text-input-icon-placeholder md:py-4"
90
- />
81
+ <div className="relative max-w-[548px] flex w-full flex-col items-center sm:flex-row sm:overflow-hidden sm:rounded-3xl sm:border-2 sm:!border-gray-600 sm:bg-white sm:p-1.5 sm:shadow-xl sm:transition-all md:!h-[75px] md:p-2">
82
+ <div className="w-full !rounded-lg border border-transparent bg-white p-1.5 shadow-md sm:border-0 sm:p-0 sm:rounded-none sm:shadow-none">
83
+ <input
84
+ type="email"
85
+ placeholder={inputPlaceholder}
86
+ value={email}
87
+ onChange={e => {
88
+ setEmail(e.target.value);
89
+ if (emailError) setEmailError("");
90
+ if (formSubmitted) setFormSubmitted(false);
91
+ }}
92
+ className="w-full flex-grow bg-transparent !pl-4 pr-6 py-3 text-[1rem] leading-none md:text-base font-normal text-text outline-none placeholder:input-text-placeholder md:py-4"
93
+ />
94
+ </div>
91
95
  <Button
92
96
  buttonLabel={ctaText}
93
97
  buttonVariant="primary_inverse"
98
+ buttonClassName="capitalize-first whitespace-nowrap rounded-2xl px-8 !text-lg mt-3 w-full sm:mt-0 sm:w-auto sm:absolute sm:right-[8px] sm:top-[calc(50%-28px)]"
94
99
  onClick={handleOnSubmit}
95
100
  />
96
101
  </div>