@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,218 @@
1
+ import React from "react";
2
+ import { BlogCard } from "./index";
3
+
4
+ import { render, screen } from "@testing-library/react";
5
+
6
+ // Mock next/link
7
+ jest.mock("next/link", () => {
8
+ const MockLink = ({ children, href, className, ...rest }: any) => (
9
+ <a href={href} className={className} {...rest}>
10
+ {children}
11
+ </a>
12
+ );
13
+ MockLink.displayName = "MockNextLink";
14
+ return MockLink;
15
+ });
16
+
17
+ jest.mock("@shared/components/material-icon", () => ({
18
+ MaterialIcon: ({ name }: any) => (
19
+ <span data-testid="material-icon" data-name={name}>
20
+ {name}
21
+ </span>
22
+ ),
23
+ }));
24
+
25
+ jest.mock("@shared/components/text", () => ({
26
+ Text: ({ children, className, ...rest }: any) => (
27
+ <span className={className} {...rest}>
28
+ {children}
29
+ </span>
30
+ ),
31
+ }));
32
+
33
+ describe("BlogCard", () => {
34
+ const defaultProps = {
35
+ href: "/blog/test-post",
36
+ title: "Test Blog Post",
37
+ description: "A description of the post",
38
+ date: "Jan 15, 2026",
39
+ category: "Technology",
40
+ };
41
+
42
+ describe("Rendering", () => {
43
+ it("renders the article element", () => {
44
+ const { container } = render(<BlogCard {...defaultProps} />);
45
+ expect(container.querySelector("article")).toBeInTheDocument();
46
+ });
47
+
48
+ it("renders title as a link", () => {
49
+ render(<BlogCard {...defaultProps} />);
50
+ expect(screen.getByText("Test Blog Post")).toBeInTheDocument();
51
+ expect(screen.getByText("Test Blog Post").closest("a")).toHaveAttribute(
52
+ "href",
53
+ "/blog/test-post"
54
+ );
55
+ });
56
+
57
+ it("renders description", () => {
58
+ render(<BlogCard {...defaultProps} />);
59
+ expect(screen.getByText("A description of the post")).toBeInTheDocument();
60
+ });
61
+
62
+ it("renders category", () => {
63
+ render(<BlogCard {...defaultProps} />);
64
+ expect(screen.getByText("Technology")).toBeInTheDocument();
65
+ });
66
+
67
+ it("renders date", () => {
68
+ render(<BlogCard {...defaultProps} />);
69
+ expect(screen.getByText("Jan 15, 2026")).toBeInTheDocument();
70
+ });
71
+
72
+ it("renders dot separator between category and date", () => {
73
+ render(<BlogCard {...defaultProps} />);
74
+ expect(screen.getByText("•")).toBeInTheDocument();
75
+ });
76
+
77
+ it("renders read more link", () => {
78
+ render(<BlogCard {...defaultProps} />);
79
+ expect(screen.getByText("Read more")).toBeInTheDocument();
80
+ });
81
+
82
+ it("renders custom readMoreText", () => {
83
+ render(<BlogCard {...defaultProps} readMoreText="Continue reading" />);
84
+ expect(screen.getByText("Continue reading")).toBeInTheDocument();
85
+ });
86
+
87
+ it("does not render date separator when date is not provided", () => {
88
+ render(<BlogCard {...defaultProps} date={undefined} />);
89
+ expect(screen.queryByText("•")).not.toBeInTheDocument();
90
+ });
91
+ });
92
+
93
+ describe("Image", () => {
94
+ it("renders img element when image is provided", () => {
95
+ const image = {
96
+ src: "/test.jpg",
97
+ alt: "Test image",
98
+ width: 400,
99
+ height: 300,
100
+ };
101
+ const { container } = render(
102
+ <BlogCard {...defaultProps} image={image} />
103
+ );
104
+ const img = container.querySelector("img");
105
+ expect(img).toBeInTheDocument();
106
+ expect(img).toHaveAttribute("src", "/test.jpg");
107
+ expect(img).toHaveAttribute("alt", "Test image");
108
+ });
109
+
110
+ it("renders custom imageComponent when provided", () => {
111
+ const image = {
112
+ src: "/test.jpg",
113
+ alt: "Custom image",
114
+ width: 400,
115
+ height: 300,
116
+ };
117
+ const CustomImage = (props: any) => (
118
+ <img data-testid="custom-image" {...props} />
119
+ );
120
+ render(
121
+ <BlogCard
122
+ {...defaultProps}
123
+ image={image}
124
+ imageComponent={CustomImage}
125
+ />
126
+ );
127
+ expect(screen.getByTestId("custom-image")).toBeInTheDocument();
128
+ });
129
+
130
+ it("renders placeholder when no image is provided", () => {
131
+ const { container } = render(<BlogCard {...defaultProps} />);
132
+ const placeholder = container.querySelector("[aria-hidden='true']");
133
+ expect(placeholder).toBeInTheDocument();
134
+ });
135
+ });
136
+
137
+ describe("Layout", () => {
138
+ it("applies grid layout by default", () => {
139
+ const { container } = render(<BlogCard {...defaultProps} />);
140
+ const article = container.querySelector("article");
141
+ expect(article?.className).toContain("flex h-full flex-col");
142
+ });
143
+
144
+ it("applies non-grid layout when asGrid is false", () => {
145
+ const { container } = render(
146
+ <BlogCard {...defaultProps} asGrid={false} />
147
+ );
148
+ const article = container.querySelector("article");
149
+ expect(article?.className).toContain("callout-card");
150
+ });
151
+
152
+ it("applies lgWidth and mdWidth in non-grid mode", () => {
153
+ const { container } = render(
154
+ <BlogCard
155
+ {...defaultProps}
156
+ asGrid={false}
157
+ lgWidth="lg:w-1/3"
158
+ mdWidth="md:w-1/2"
159
+ />
160
+ );
161
+ const article = container.querySelector("article");
162
+ expect(article?.className).toContain("lg:w-1/3");
163
+ expect(article?.className).toContain("md:w-1/2");
164
+ });
165
+
166
+ it("applies data-section-type attribute", () => {
167
+ const { container } = render(<BlogCard {...defaultProps} />);
168
+ const article = container.querySelector("article");
169
+ expect(article).toHaveAttribute("data-section-type", "blog-card");
170
+ });
171
+
172
+ it("applies data-section-index attribute", () => {
173
+ const { container } = render(<BlogCard {...defaultProps} index={2} />);
174
+ const article = container.querySelector("article");
175
+ expect(article).toHaveAttribute("data-section-index", "2");
176
+ });
177
+ });
178
+
179
+ describe("Optional props", () => {
180
+ it("renders without title", () => {
181
+ const { container } = render(
182
+ <BlogCard {...defaultProps} title={undefined} />
183
+ );
184
+ expect(container.querySelector("article")).toBeInTheDocument();
185
+ });
186
+
187
+ it("renders without description", () => {
188
+ const { container } = render(
189
+ <BlogCard {...defaultProps} description={undefined} />
190
+ );
191
+ expect(container.querySelector("article")).toBeInTheDocument();
192
+ });
193
+
194
+ it("sets aria-label on read more link", () => {
195
+ render(<BlogCard {...defaultProps} />);
196
+ const links = screen.getAllByRole("link");
197
+ const readMoreLink = links.find(link =>
198
+ link.getAttribute("aria-label")?.includes("Read more")
199
+ );
200
+ expect(readMoreLink).toHaveAttribute(
201
+ "aria-label",
202
+ "Read more about Test Blog Post"
203
+ );
204
+ });
205
+
206
+ it("uses fallback aria-label when title is not provided", () => {
207
+ render(<BlogCard {...defaultProps} title={undefined} />);
208
+ const links = screen.getAllByRole("link");
209
+ const readMoreLink = links.find(link =>
210
+ link.getAttribute("aria-label")?.includes("Read more")
211
+ );
212
+ expect(readMoreLink).toHaveAttribute(
213
+ "aria-label",
214
+ "Read more about this article"
215
+ );
216
+ });
217
+ });
218
+ });
@@ -0,0 +1,201 @@
1
+ import React from "react";
2
+ import { FloatingImageCard } from "./index";
3
+
4
+ import { render, screen } from "@testing-library/react";
5
+
6
+ jest.mock("@shared/components/next-image", () => ({
7
+ NextImage: ({ src, alt, width, height, className, onClick }: any) => (
8
+ <img
9
+ data-testid="next-image"
10
+ src={src}
11
+ alt={alt}
12
+ width={width}
13
+ height={height}
14
+ className={className}
15
+ onClick={onClick}
16
+ />
17
+ ),
18
+ }));
19
+
20
+ jest.mock("@shared/components/text", () => ({
21
+ Text: ({ as: Tag = "span", children, className }: any) => (
22
+ <Tag className={className}>{children}</Tag>
23
+ ),
24
+ }));
25
+
26
+ jest.mock("../../button", () => ({
27
+ Button: ({ children, linkClassName, ...rest }: any) => (
28
+ <button data-testid="cta-button" className={linkClassName}>
29
+ {rest.buttonLabel || children}
30
+ </button>
31
+ ),
32
+ }));
33
+
34
+ jest.mock("@shared/utils", () => ({
35
+ cx: (...args: any[]) => args.filter(Boolean).join(" "),
36
+ }));
37
+
38
+ describe("FloatingImageCard", () => {
39
+ const defaultCard = {
40
+ title: "Card Title",
41
+ header: "Card Header",
42
+ body: "Card body content",
43
+ image: { href: "/image.jpg", title: "Test Image", width: 400, height: 285 },
44
+ };
45
+
46
+ describe("Rendering", () => {
47
+ it("renders the section element", () => {
48
+ const { container } = render(<FloatingImageCard card={defaultCard} />);
49
+ expect(container.querySelector("section")).toBeInTheDocument();
50
+ });
51
+
52
+ it("renders header text", () => {
53
+ render(<FloatingImageCard card={defaultCard} />);
54
+ expect(screen.getByText("Card Header")).toBeInTheDocument();
55
+ });
56
+
57
+ it("renders body text", () => {
58
+ render(<FloatingImageCard card={defaultCard} />);
59
+ expect(screen.getByText("Card body content")).toBeInTheDocument();
60
+ });
61
+
62
+ it("renders image when provided", () => {
63
+ render(<FloatingImageCard card={defaultCard} />);
64
+ const img = screen.getByTestId("next-image");
65
+ expect(img).toHaveAttribute("src", "/image.jpg");
66
+ expect(img).toHaveAttribute("alt", "Test Image");
67
+ });
68
+
69
+ it("does not render image when not provided", () => {
70
+ render(<FloatingImageCard card={{ ...defaultCard, image: undefined }} />);
71
+ expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
72
+ });
73
+
74
+ it("does not render header when not provided", () => {
75
+ render(
76
+ <FloatingImageCard card={{ ...defaultCard, header: undefined }} />
77
+ );
78
+ expect(screen.queryByText("Card Header")).not.toBeInTheDocument();
79
+ });
80
+
81
+ it("does not render body when not provided", () => {
82
+ render(<FloatingImageCard card={{ ...defaultCard, body: undefined }} />);
83
+ expect(screen.queryByText("Card body content")).not.toBeInTheDocument();
84
+ });
85
+ });
86
+
87
+ describe("CTA", () => {
88
+ it("renders CTA button when cta is provided", () => {
89
+ const card = {
90
+ ...defaultCard,
91
+ cta: { buttonLabel: "Learn More", href: "/learn" },
92
+ };
93
+ render(<FloatingImageCard card={card} />);
94
+ expect(screen.getByTestId("cta-button")).toBeInTheDocument();
95
+ });
96
+
97
+ it("does not render CTA when not provided", () => {
98
+ render(<FloatingImageCard card={defaultCard} />);
99
+ expect(screen.queryByTestId("cta-button")).not.toBeInTheDocument();
100
+ });
101
+ });
102
+
103
+ describe("CTA alignment", () => {
104
+ it("applies left alignment by default", () => {
105
+ const card = {
106
+ ...defaultCard,
107
+ cta: { buttonLabel: "Click", href: "/click" },
108
+ };
109
+ render(<FloatingImageCard card={card} />);
110
+ const ctaWrapper = screen.getByTestId("cta-button").parentElement;
111
+ expect(ctaWrapper?.className).toContain("justify-start");
112
+ });
113
+
114
+ it("applies center alignment", () => {
115
+ const card = {
116
+ ...defaultCard,
117
+ cta: { buttonLabel: "Click", href: "/click" },
118
+ ctaAlignment: "center" as const,
119
+ };
120
+ render(<FloatingImageCard card={card} />);
121
+ const ctaWrapper = screen.getByTestId("cta-button").parentElement;
122
+ expect(ctaWrapper?.className).toContain("justify-center");
123
+ });
124
+
125
+ it("applies right alignment", () => {
126
+ const card = {
127
+ ...defaultCard,
128
+ cta: { buttonLabel: "Click", href: "/click" },
129
+ ctaAlignment: "right" as const,
130
+ };
131
+ render(<FloatingImageCard card={card} />);
132
+ const ctaWrapper = screen.getByTestId("cta-button").parentElement;
133
+ expect(ctaWrapper?.className).toContain("justify-end");
134
+ });
135
+ });
136
+
137
+ describe("PromoBar mode", () => {
138
+ it("renders only image when isPromoBarImg is true", () => {
139
+ const card = {
140
+ ...defaultCard,
141
+ isPromoBarImg: true,
142
+ };
143
+ const { container } = render(<FloatingImageCard card={card} />);
144
+ expect(container.querySelector("section")).not.toBeInTheDocument();
145
+ expect(screen.getByTestId("next-image")).toBeInTheDocument();
146
+ });
147
+
148
+ it("returns null when isPromoBarImg is true and no image", () => {
149
+ const card = {
150
+ ...defaultCard,
151
+ image: undefined,
152
+ isPromoBarImg: true,
153
+ };
154
+ const { container } = render(<FloatingImageCard card={card} />);
155
+ expect(container.firstChild).toBeNull();
156
+ });
157
+ });
158
+
159
+ describe("Image link", () => {
160
+ it("makes image clickable when imageLink is provided", () => {
161
+ const windowOpen = jest.spyOn(window, "open").mockImplementation();
162
+ const card = {
163
+ ...defaultCard,
164
+ imageLink: "https://example.com",
165
+ };
166
+ render(<FloatingImageCard card={card} />);
167
+ const img = screen.getByTestId("next-image");
168
+ img.click();
169
+ expect(windowOpen).toHaveBeenCalledWith("https://example.com", "_blank");
170
+ windowOpen.mockRestore();
171
+ });
172
+ });
173
+
174
+ describe("Layout props", () => {
175
+ it("applies lgWidth class", () => {
176
+ const { container } = render(
177
+ <FloatingImageCard card={defaultCard} lgWidth="lg:w-1/3" />
178
+ );
179
+ expect(container.querySelector("section")?.className).toContain(
180
+ "lg:w-1/3"
181
+ );
182
+ });
183
+
184
+ it("applies mdWidth class", () => {
185
+ const { container } = render(
186
+ <FloatingImageCard card={defaultCard} mdWidth="md:w-1/2" />
187
+ );
188
+ expect(container.querySelector("section")?.className).toContain(
189
+ "md:w-1/2"
190
+ );
191
+ });
192
+
193
+ it("applies shadow class when shadow is true", () => {
194
+ const card = { ...defaultCard, shadow: true };
195
+ const { container } = render(<FloatingImageCard card={card} />);
196
+ expect(container.querySelector("section")?.className).toContain(
197
+ "shadow-card"
198
+ );
199
+ });
200
+ });
201
+ });
@@ -0,0 +1,216 @@
1
+ import React from "react";
2
+ import { FullImageCard } from "./index";
3
+
4
+ import { render, screen } from "@testing-library/react";
5
+
6
+ jest.mock("@shared/components/next-image", () => ({
7
+ NextImage: ({ src, alt, width, height, className }: any) => (
8
+ <img
9
+ data-testid="next-image"
10
+ src={src}
11
+ alt={alt}
12
+ width={width}
13
+ height={height}
14
+ className={className}
15
+ />
16
+ ),
17
+ }));
18
+
19
+ jest.mock("@shared/components/text", () => ({
20
+ Text: ({ as: Tag = "span", children, className }: any) => (
21
+ <Tag className={className}>{children}</Tag>
22
+ ),
23
+ }));
24
+
25
+ jest.mock("@shared/components/link", () => ({
26
+ Link: ({ children, href, className, tabIndex, target, ...rest }: any) => (
27
+ <a
28
+ href={href}
29
+ className={className}
30
+ tabIndex={tabIndex}
31
+ target={target}
32
+ {...rest}
33
+ >
34
+ {children}
35
+ </a>
36
+ ),
37
+ }));
38
+
39
+ jest.mock("../../button", () => ({
40
+ Button: ({ children, linkClassName, buttonLabel }: any) => (
41
+ <button data-testid="cta-button" className={linkClassName}>
42
+ {buttonLabel || children}
43
+ </button>
44
+ ),
45
+ }));
46
+
47
+ jest.mock("@shared/utils", () => ({
48
+ cx: (...args: any[]) => args.filter(Boolean).join(" "),
49
+ }));
50
+
51
+ describe("FullImageCard", () => {
52
+ const defaultCard = {
53
+ title: "Card Title",
54
+ description: "Card description",
55
+ image: { href: "/image.jpg", title: "Card Image", width: 400, height: 280 },
56
+ };
57
+
58
+ describe("Rendering", () => {
59
+ it("renders the section element", () => {
60
+ const { container } = render(<FullImageCard card={defaultCard} />);
61
+ expect(container.querySelector("section")).toBeInTheDocument();
62
+ });
63
+
64
+ it("renders title", () => {
65
+ render(<FullImageCard card={defaultCard} />);
66
+ expect(screen.getByText("Card Title")).toBeInTheDocument();
67
+ });
68
+
69
+ it("renders description", () => {
70
+ render(<FullImageCard card={defaultCard} />);
71
+ expect(screen.getByText("Card description")).toBeInTheDocument();
72
+ });
73
+
74
+ it("renders image when provided", () => {
75
+ render(<FullImageCard card={defaultCard} />);
76
+ const img = screen.getByTestId("next-image");
77
+ expect(img).toHaveAttribute("src", "/image.jpg");
78
+ });
79
+
80
+ it("does not render image when not provided", () => {
81
+ render(<FullImageCard card={{ title: "No Image" }} />);
82
+ expect(screen.queryByTestId("next-image")).not.toBeInTheDocument();
83
+ });
84
+
85
+ it("does not render title when not provided", () => {
86
+ render(<FullImageCard card={{ description: "Desc only" }} />);
87
+ expect(screen.queryByRole("heading")).not.toBeInTheDocument();
88
+ });
89
+
90
+ it("does not render description when not provided", () => {
91
+ render(<FullImageCard card={{ title: "Title only" }} />);
92
+ expect(screen.queryByText("Card description")).not.toBeInTheDocument();
93
+ });
94
+ });
95
+
96
+ describe("Caption", () => {
97
+ it("renders caption when provided", () => {
98
+ const card = { ...defaultCard, caption: "A note" };
99
+ render(<FullImageCard card={card} />);
100
+ expect(screen.getByText("A note")).toBeInTheDocument();
101
+ });
102
+
103
+ it("does not render caption when not provided", () => {
104
+ render(<FullImageCard card={defaultCard} />);
105
+ expect(screen.queryByText("A note")).not.toBeInTheDocument();
106
+ });
107
+ });
108
+
109
+ describe("CTA", () => {
110
+ it("renders CTA button when cta is provided", () => {
111
+ const card = {
112
+ ...defaultCard,
113
+ cta: { buttonLabel: "Learn More", href: "/learn" },
114
+ };
115
+ render(<FullImageCard card={card} />);
116
+ expect(screen.getByTestId("cta-button")).toBeInTheDocument();
117
+ });
118
+
119
+ it("does not render CTA when not provided", () => {
120
+ render(<FullImageCard card={defaultCard} />);
121
+ expect(screen.queryByTestId("cta-button")).not.toBeInTheDocument();
122
+ });
123
+
124
+ it("wraps title in link when cta href is present", () => {
125
+ const card = {
126
+ ...defaultCard,
127
+ cta: { buttonLabel: "Click", href: "/click" },
128
+ };
129
+ render(<FullImageCard card={card} />);
130
+ const titleLink = screen.getByText("Card Title").closest("a");
131
+ expect(titleLink).toHaveAttribute("href", "/click");
132
+ });
133
+
134
+ it("wraps image in link when cta href is present", () => {
135
+ const card = {
136
+ ...defaultCard,
137
+ cta: { buttonLabel: "Click", href: "/click" },
138
+ };
139
+ render(<FullImageCard card={card} />);
140
+ const imgLink = screen.getByTestId("next-image").closest("a");
141
+ expect(imgLink).toHaveAttribute("href", "/click");
142
+ });
143
+ });
144
+
145
+ describe("Link prop", () => {
146
+ it("uses link href for image and title when no cta", () => {
147
+ const card = {
148
+ ...defaultCard,
149
+ link: { href: "/link-target", target: "_blank" as const },
150
+ };
151
+ render(<FullImageCard card={card} />);
152
+ const imgLink = screen.getByTestId("next-image").closest("a");
153
+ expect(imgLink).toHaveAttribute("href", "/link-target");
154
+ });
155
+ });
156
+
157
+ describe("Layout props", () => {
158
+ it("applies lgWidth class", () => {
159
+ const { container } = render(
160
+ <FullImageCard card={defaultCard} lgWidth="lg:w-1/3" />
161
+ );
162
+ expect(container.querySelector("section")?.className).toContain(
163
+ "lg:w-1/3"
164
+ );
165
+ });
166
+
167
+ it("applies mdWidth class", () => {
168
+ const { container } = render(
169
+ <FullImageCard card={defaultCard} mdWidth="md:w-1/2" />
170
+ );
171
+ expect(container.querySelector("section")?.className).toContain(
172
+ "md:w-1/2"
173
+ );
174
+ });
175
+
176
+ it("applies shadow by default", () => {
177
+ const { container } = render(<FullImageCard card={defaultCard} />);
178
+ expect(container.querySelector("section")?.className).toContain(
179
+ "shadow-card"
180
+ );
181
+ });
182
+
183
+ it("does not apply shadow when shadow is false", () => {
184
+ const card = { ...defaultCard, shadow: false };
185
+ const { container } = render(<FullImageCard card={card} />);
186
+ expect(container.querySelector("section")?.className).not.toContain(
187
+ "shadow-card"
188
+ );
189
+ });
190
+
191
+ it("applies custom className", () => {
192
+ const card = { ...defaultCard, className: "my-class" };
193
+ const { container } = render(<FullImageCard card={card} />);
194
+ expect(container.querySelector("section")?.className).toContain(
195
+ "my-class"
196
+ );
197
+ });
198
+
199
+ it("applies anchorId as section id", () => {
200
+ const card = { ...defaultCard, anchorId: "my-card" };
201
+ const { container } = render(<FullImageCard card={card} />);
202
+ expect(container.querySelector("section")).toHaveAttribute(
203
+ "id",
204
+ "my-card"
205
+ );
206
+ });
207
+
208
+ it("uses default anchorId FullImageCard", () => {
209
+ const { container } = render(<FullImageCard card={defaultCard} />);
210
+ expect(container.querySelector("section")).toHaveAttribute(
211
+ "id",
212
+ "FullImageCard"
213
+ );
214
+ });
215
+ });
216
+ });
@@ -0,0 +1,39 @@
1
+ import "@testing-library/jest-dom";
2
+
3
+ import React from "react";
4
+ import { Cards } from "./index";
5
+
6
+ import { render, screen } from "@testing-library/react";
7
+
8
+ jest.mock("@shared/components/text", () => ({
9
+ Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
10
+ }));
11
+
12
+ describe("Cards", () => {
13
+ it("renders the Cards component", () => {
14
+ render(<Cards fields={{}} />);
15
+ expect(screen.getByText("Cards")).toBeInTheDocument();
16
+ });
17
+
18
+ it("renders a div wrapper", () => {
19
+ const { container } = render(<Cards fields={{}} />);
20
+ expect(container.querySelector("div")).toBeInTheDocument();
21
+ });
22
+
23
+ it("renders with empty fields object", () => {
24
+ const { container } = render(<Cards fields={{}} />);
25
+ expect(container.firstChild).toBeTruthy();
26
+ });
27
+
28
+ it("renders Text component with Cards text", () => {
29
+ render(<Cards fields={{}} />);
30
+ const text = screen.getByText("Cards");
31
+ expect(text).toBeInTheDocument();
32
+ expect(text.tagName).toBe("SPAN");
33
+ });
34
+
35
+ it("exports default Cards component", async () => {
36
+ const mod = await import("./index");
37
+ expect(mod.default).toBeDefined();
38
+ });
39
+ });