@windstream/react-shared-components 0.2.73 → 0.2.75

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.
@@ -0,0 +1,163 @@
1
+ import "@testing-library/jest-dom";
2
+
3
+ import React from "react";
4
+ import { OrderedList } from "./index";
5
+
6
+ import { render, screen, within } from "@testing-library/react";
7
+
8
+ jest.mock("@shared/components/link", () => ({
9
+ Link: ({ children, href, title, className }: any) => (
10
+ <a href={href} title={title} className={className} data-testid="ol-link">
11
+ {children}
12
+ </a>
13
+ ),
14
+ }));
15
+
16
+ jest.mock("@shared/components/text", () => ({
17
+ Text: ({ children, as: Tag = "span", className }: any) => (
18
+ <Tag className={className}>{children}</Tag>
19
+ ),
20
+ }));
21
+
22
+ jest.mock("@shared/contentful/blocks/dynamic-tabs", () => ({
23
+ DynamicTabs: ({
24
+ tabs,
25
+ dynamic,
26
+ isOrderedList,
27
+ navigationType,
28
+ mobileSidebarTabType,
29
+ }: any) => (
30
+ <div
31
+ data-testid="dynamic-tabs"
32
+ data-dynamic={String(dynamic)}
33
+ data-ordered={String(isOrderedList)}
34
+ data-nav={String(navigationType)}
35
+ data-sidebar={String(mobileSidebarTabType)}
36
+ >
37
+ {tabs.items.map((tab: any) => (
38
+ <div
39
+ key={tab.anchorId}
40
+ data-testid="dt-tab"
41
+ data-tabname={tab.tabName}
42
+ data-anchor={tab.anchorId}
43
+ >
44
+ {tab.tabContent}
45
+ </div>
46
+ ))}
47
+ </div>
48
+ ),
49
+ }));
50
+
51
+ describe("OrderedList", () => {
52
+ const tabs = [
53
+ {
54
+ items: {
55
+ tabName: "States",
56
+ tabContent: [
57
+ { itemName: "new york", itemSlug: "/new-york" },
58
+ { itemName: "los angeles" },
59
+ ],
60
+ },
61
+ },
62
+ {
63
+ items: {
64
+ tabName: "Cities",
65
+ tabContent: [{ itemName: "san FRANCISCO", itemSlug: "/sf" }],
66
+ },
67
+ },
68
+ ];
69
+
70
+ const renderComponent = (overrides: Partial<any> = {}) =>
71
+ render(
72
+ <OrderedList
73
+ title="Browse locations"
74
+ description="Find availability near you"
75
+ tabs={tabs}
76
+ {...overrides}
77
+ />
78
+ );
79
+
80
+ it("renders the title and description", () => {
81
+ renderComponent();
82
+ expect(screen.getByText("Browse locations")).toBeInTheDocument();
83
+ expect(screen.getByText("Find availability near you")).toBeInTheDocument();
84
+ });
85
+
86
+ it("renders the section with the provided anchor id and testid", () => {
87
+ const { container } = renderComponent({ anchorId: "locations" });
88
+ const section = container.querySelector(
89
+ '[data-testid="non-fiber-city-list"]'
90
+ );
91
+ expect(section).toBeInTheDocument();
92
+ expect(section).toHaveAttribute("id", "locations");
93
+ });
94
+
95
+ it("defaults the background to white", () => {
96
+ const { container } = renderComponent();
97
+ expect(container.querySelector(".bg-secondary-white")).toBeInTheDocument();
98
+ });
99
+
100
+ it("applies the cream background when requested", () => {
101
+ const { container } = renderComponent({ backgroundColor: "cream" });
102
+ expect(container.querySelector(".bg-secondary-cream")).toBeInTheDocument();
103
+ expect(
104
+ container.querySelector(".bg-secondary-white")
105
+ ).not.toBeInTheDocument();
106
+ });
107
+
108
+ it("forwards the expected flags to DynamicTabs", () => {
109
+ renderComponent();
110
+ const dynamicTabs = screen.getByTestId("dynamic-tabs");
111
+ expect(dynamicTabs).toHaveAttribute("data-dynamic", "true");
112
+ expect(dynamicTabs).toHaveAttribute("data-ordered", "true");
113
+ expect(dynamicTabs).toHaveAttribute("data-nav", "false");
114
+ expect(dynamicTabs).toHaveAttribute("data-sidebar", "false");
115
+ });
116
+
117
+ it("maps each tab with its name and a generated anchor id", () => {
118
+ renderComponent();
119
+ const dtTabs = screen.getAllByTestId("dt-tab");
120
+ expect(dtTabs).toHaveLength(2);
121
+ expect(dtTabs[0]).toHaveAttribute("data-tabname", "States");
122
+ expect(dtTabs[0]).toHaveAttribute("data-anchor", "tab-0");
123
+ expect(dtTabs[1]).toHaveAttribute("data-tabname", "Cities");
124
+ expect(dtTabs[1]).toHaveAttribute("data-anchor", "tab-1");
125
+ });
126
+
127
+ it("title-cases item names regardless of input casing", () => {
128
+ renderComponent();
129
+ // "new york" -> "New York", "los angeles" -> "Los Angeles",
130
+ // "san FRANCISCO" -> "San Francisco"
131
+ expect(screen.getByText("New York")).toBeInTheDocument();
132
+ expect(screen.getByText("Los Angeles")).toBeInTheDocument();
133
+ expect(screen.getByText("San Francisco")).toBeInTheDocument();
134
+ });
135
+
136
+ it("renders a linked item with an uppercased title and href", () => {
137
+ renderComponent();
138
+ const link = screen.getByText("New York").closest("a");
139
+ expect(link).not.toBeNull();
140
+ expect(link).toHaveAttribute("href", "/new-york");
141
+ expect(link).toHaveAttribute("title", "NEW YORK");
142
+ });
143
+
144
+ it("renders an item without a slug as plain text (no link)", () => {
145
+ renderComponent();
146
+ const losAngeles = screen.getByText("Los Angeles");
147
+ expect(losAngeles.closest("a")).toBeNull();
148
+ });
149
+
150
+ it("keeps each tab's items within that tab", () => {
151
+ renderComponent();
152
+ const dtTabs = screen.getAllByTestId("dt-tab");
153
+ expect(within(dtTabs[0]).getByText("New York")).toBeInTheDocument();
154
+ expect(within(dtTabs[0]).getByText("Los Angeles")).toBeInTheDocument();
155
+ expect(within(dtTabs[1]).getByText("San Francisco")).toBeInTheDocument();
156
+ expect(within(dtTabs[1]).queryByText("New York")).toBeNull();
157
+ });
158
+
159
+ it("does not render DynamicTabs when there are no tabs", () => {
160
+ renderComponent({ tabs: [] });
161
+ expect(screen.queryByTestId("dynamic-tabs")).toBeNull();
162
+ });
163
+ });
@@ -184,6 +184,7 @@ export const PrimaryHero: React.FC<PrimaryHeroProps> = props => {
184
184
  const checklistEl = (
185
185
  <Checklist
186
186
  iconSize={24}
187
+ iconPosition="top"
187
188
  listItemClassName={`body2 text-left ${textColorClass}`}
188
189
  items={checklist.map(item =>
189
190
  item.iconUrl
@@ -1120,6 +1120,61 @@ describe("renderContentfulRichTextTable", () => {
1120
1120
  expect(container).toHaveTextContent("Other Title");
1121
1121
  });
1122
1122
 
1123
+ it("renders componentVideoLink inline entry (REST shape, no links prop)", () => {
1124
+ // Regression test: a doc containing both a table and an inline
1125
+ // componentVideoLink entry previously rendered nothing for the video,
1126
+ // since this function's INLINES.EMBEDDED_ENTRY override only resolved
1127
+ // entries via the GraphQL `links` lookup.
1128
+ const doc = makeDoc({
1129
+ nodeType: BLOCKS.PARAGRAPH,
1130
+ data: {},
1131
+ content: [
1132
+ makeInlineEntry("componentVideoLink", {
1133
+ link: "https://youtube.com/watch?v=123",
1134
+ }),
1135
+ ],
1136
+ });
1137
+ const { container } = render(<>{renderContentfulRichTextTable(doc)}</>);
1138
+ const el = container.querySelector('[data-testid="video-link"]')!;
1139
+ expect(el).toBeInTheDocument();
1140
+ const videoLink = JSON.parse(el.getAttribute("data-video-link")!);
1141
+ expect(videoLink).toMatchObject({
1142
+ link: "https://youtube.com/watch?v=123",
1143
+ });
1144
+ });
1145
+
1146
+ it("renders ComponentVideoLink (GraphQL __typename) inline entry resolved from links in table path", () => {
1147
+ const entryId = "video-inline-1";
1148
+ const doc = makeDoc({
1149
+ nodeType: BLOCKS.PARAGRAPH,
1150
+ data: {},
1151
+ content: [
1152
+ {
1153
+ nodeType: INLINES.EMBEDDED_ENTRY,
1154
+ data: { target: { sys: { id: entryId } } },
1155
+ content: [],
1156
+ },
1157
+ ],
1158
+ });
1159
+ const links = {
1160
+ entries: {
1161
+ inline: [
1162
+ {
1163
+ sys: { id: entryId },
1164
+ __typename: "ComponentVideoLink",
1165
+ link: "https://youtube.com/watch?v=456",
1166
+ },
1167
+ ],
1168
+ },
1169
+ };
1170
+ const { container } = render(
1171
+ <>{renderContentfulRichTextTable(doc, links)}</>
1172
+ );
1173
+ expect(
1174
+ container.querySelector('[data-testid="video-link"]')
1175
+ ).toBeInTheDocument();
1176
+ });
1177
+
1123
1178
  it("returns null for inline entry not found in links", () => {
1124
1179
  const doc = makeDoc({
1125
1180
  nodeType: BLOCKS.PARAGRAPH,
@@ -471,18 +471,21 @@ export function renderContentfulRichTextTable(
471
471
  );
472
472
  },
473
473
  [INLINES.EMBEDDED_ENTRY]: node => {
474
- const entryId = node.data.target.sys.id;
475
-
476
- const entry = links?.entries?.inline?.find(
477
- (e: any) => e.sys.id === entryId
478
- );
474
+ // resolveEmbeddedEntry/getEntryTypeId/getEntryFields handle both REST
475
+ // (already-resolved target) and GraphQL (`links` lookup) shapes - a
476
+ // plain `links?.entries?.inline` lookup here always returns undefined
477
+ // for REST responses (no `links` prop passed), silently dropping any
478
+ // inline embedded entry (e.g. componentVideoLink) in a doc with a table.
479
+ const entry = resolveEmbeddedEntry(node, links);
479
480
  if (!entry) return null;
481
+ const ctid = getEntryTypeId(entry);
482
+ const fields = getEntryFields(entry);
480
483
 
481
- if (entry.__typename === "ComponentCheckList") {
484
+ if (ctid === "componentCheckList" || ctid === "ComponentCheckList") {
482
485
  // FIX: Don't use a Hook here.
483
486
  // Map the items manually or use a non-hook helper function.
484
487
  const items =
485
- entry.list?.items?.map((item: any) =>
488
+ (fields.list?.items ?? entry.list?.items)?.map((item: any) =>
486
489
  renderContentfulRichText(
487
490
  toDocument(item?.checkListTitle ?? ""),
488
491
  true,
@@ -498,7 +501,11 @@ export function renderContentfulRichTextTable(
498
501
  );
499
502
  }
500
503
 
501
- return <span>{entry.title || ""}</span>;
504
+ if (ctid === "componentVideoLink" || ctid === "ComponentVideoLink") {
505
+ return <VideoLink videoLink={toVideoLinkProps(fields)} />;
506
+ }
507
+
508
+ return <span>{fields?.title ?? entry.title ?? ""}</span>;
502
509
  },
503
510
  },
504
511
  };