Skip to content

Commit f6a8822

Browse files
VIA-760 Replace NBS booking with unauthenticated link for Children's Flu
In the design system library a green button is usually used to submit a form, not follow a hyperlink. In previous stories a custom green button was built to follow a URL, with handlers to ensure the link opens in the correct webview/new tab depending on whether the user is in the NHS app or not. Until now this has only been used for authenticated SSO journeys to NBS, so was labelled in the code as 'NBSButton', with the SSO link generation happening in the background. Renamed NBS Booking action components to make it clearer which ones add SSO handover parameters into the URL and which do not. Add new capability to enable using the button without SSO. In the future if we need 'green button as a link' functionality elsewhere in the UI 'NBSBookingButton' could be extracted and renamed to something more generic for reuse. For now, leaving it where it is.
1 parent 0cf47db commit f6a8822

18 files changed

+246
-62
lines changed

src/app/_components/eligibility/EligibilityActions.test.tsx

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ jest.mock("react-markdown", () => {
1212
};
1313
});
1414
jest.mock("@src/app/_components/nbs/NBSBookingAction", () => ({
15-
NBSBookingActionForBaseUrl: () => <a href="https://nbs-test-link">NBS Booking Link Test</a>,
15+
NBSBookingActionWithAuthSSOForBaseUrl: () => <a href="https://nbs-test-link">NBS Booking Link Test</a>,
16+
NBSBookingActionWithoutAuthForUrl: () => (
17+
<a href="https://nbs-without-auth-test-link">NBS Booking Without Auth Link Test</a>
18+
),
1619
}));
1720

1821
describe("EligibilityActions", () => {
@@ -54,8 +57,14 @@ describe("EligibilityActions", () => {
5457
actions: [
5558
actionBuilder().withType(ActionDisplayType.infotext).andContent("Test Content 1").build(),
5659
actionBuilder().withType(ActionDisplayType.card).andContent("Test Content 2").build(),
57-
actionBuilder().withType(ActionDisplayType.buttonWithCard).andContent("Test Content 3").build(),
58-
actionBuilder().withType(ActionDisplayType.buttonWithInfo).andContent("Test Content 4").build(),
60+
actionBuilder()
61+
.withType(ActionDisplayType.nbsAuthLinkButtonWithCard)
62+
.andContent("Test Content 3")
63+
.build(),
64+
actionBuilder()
65+
.withType(ActionDisplayType.nbsAuthLinkButtonWithInfo)
66+
.andContent("Test Content 4")
67+
.build(),
5968
actionBuilder().withType(ActionDisplayType.actionLinkWithInfo).andContent("Test Content 5").build(),
6069
],
6170
vaccineType: VaccineType.RSV,
@@ -127,12 +136,15 @@ describe("EligibilityActions", () => {
127136
});
128137
});
129138

130-
describe("buttonWithCard", () => {
139+
describe("nbsAuthLinkButtonWithCard", () => {
131140
it("should display auth action card content successfully", () => {
132141
render(
133142
EligibilityActions({
134143
actions: [
135-
actionBuilder().withType(ActionDisplayType.buttonWithCard).andContent("Test Auth Action Content").build(),
144+
actionBuilder()
145+
.withType(ActionDisplayType.nbsAuthLinkButtonWithCard)
146+
.andContent("Test Auth Action Content")
147+
.build(),
136148
],
137149
vaccineType: VaccineType.RSV,
138150
}),
@@ -144,7 +156,9 @@ describe("EligibilityActions", () => {
144156
it("should display button content successfully", () => {
145157
render(
146158
EligibilityActions({
147-
actions: [actionBuilder().withType(ActionDisplayType.buttonWithCard).andContent("Test Content").build()],
159+
actions: [
160+
actionBuilder().withType(ActionDisplayType.nbsAuthLinkButtonWithCard).andContent("Test Content").build(),
161+
],
148162
vaccineType: VaccineType.RSV,
149163
}),
150164
);
@@ -158,8 +172,14 @@ describe("EligibilityActions", () => {
158172
render(
159173
EligibilityActions({
160174
actions: [
161-
actionBuilder().withType(ActionDisplayType.buttonWithCard).andContent("Test Content 1").build(),
162-
actionBuilder().withType(ActionDisplayType.buttonWithCard).andContent("Test Content 2").build(),
175+
actionBuilder()
176+
.withType(ActionDisplayType.nbsAuthLinkButtonWithCard)
177+
.andContent("Test Content 1")
178+
.build(),
179+
actionBuilder()
180+
.withType(ActionDisplayType.nbsAuthLinkButtonWithCard)
181+
.andContent("Test Content 2")
182+
.build(),
163183
],
164184
vaccineType: VaccineType.RSV,
165185
}),
@@ -171,7 +191,7 @@ describe("EligibilityActions", () => {
171191
it("should display button without card if no description present", () => {
172192
render(
173193
EligibilityActions({
174-
actions: [actionBuilder().withType(ActionDisplayType.buttonWithCard).andContent("").build()],
194+
actions: [actionBuilder().withType(ActionDisplayType.nbsAuthLinkButtonWithCard).andContent("").build()],
175195
vaccineType: VaccineType.RSV,
176196
}),
177197
);
@@ -184,12 +204,15 @@ describe("EligibilityActions", () => {
184204
});
185205
});
186206

187-
describe("buttonWithInfo", () => {
207+
describe("nbsAuthLinkButtonWithInfo", () => {
188208
it("should display info content successfully", () => {
189209
render(
190210
EligibilityActions({
191211
actions: [
192-
actionBuilder().withType(ActionDisplayType.buttonWithInfo).andContent("Test Auth Action Content").build(),
212+
actionBuilder()
213+
.withType(ActionDisplayType.nbsAuthLinkButtonWithInfo)
214+
.andContent("Test Auth Action Content")
215+
.build(),
193216
],
194217
vaccineType: VaccineType.RSV,
195218
}),
@@ -201,7 +224,9 @@ describe("EligibilityActions", () => {
201224
it("should display button content successfully", () => {
202225
render(
203226
EligibilityActions({
204-
actions: [actionBuilder().withType(ActionDisplayType.buttonWithInfo).andContent("Test Content").build()],
227+
actions: [
228+
actionBuilder().withType(ActionDisplayType.nbsAuthLinkButtonWithInfo).andContent("Test Content").build(),
229+
],
205230
vaccineType: VaccineType.RSV,
206231
}),
207232
);
@@ -215,8 +240,14 @@ describe("EligibilityActions", () => {
215240
render(
216241
EligibilityActions({
217242
actions: [
218-
actionBuilder().withType(ActionDisplayType.buttonWithInfo).andContent("Test Content 1").build(),
219-
actionBuilder().withType(ActionDisplayType.buttonWithInfo).andContent("Test Content 2").build(),
243+
actionBuilder()
244+
.withType(ActionDisplayType.nbsAuthLinkButtonWithInfo)
245+
.andContent("Test Content 1")
246+
.build(),
247+
actionBuilder()
248+
.withType(ActionDisplayType.nbsAuthLinkButtonWithInfo)
249+
.andContent("Test Content 2")
250+
.build(),
220251
],
221252
vaccineType: VaccineType.RSV,
222253
}),
@@ -228,7 +259,7 @@ describe("EligibilityActions", () => {
228259
it("should display button without info if no description present", () => {
229260
render(
230261
EligibilityActions({
231-
actions: [actionBuilder().withType(ActionDisplayType.buttonWithInfo).andContent("").build()],
262+
actions: [actionBuilder().withType(ActionDisplayType.nbsAuthLinkButtonWithInfo).andContent("").build()],
232263
vaccineType: VaccineType.RSV,
233264
}),
234265
);
@@ -241,6 +272,77 @@ describe("EligibilityActions", () => {
241272
});
242273
});
243274

275+
describe("buttonWithoutAuthLinkWithInfo", () => {
276+
it("should display info content successfully", () => {
277+
render(
278+
EligibilityActions({
279+
actions: [
280+
actionBuilder()
281+
.withType(ActionDisplayType.buttonWithoutAuthLinkWithInfo)
282+
.andContent("Test Button Action Content")
283+
.build(),
284+
],
285+
vaccineType: VaccineType.FLU_FOR_CHILDREN,
286+
}),
287+
);
288+
289+
expectContentStringToBeVisible("Test Button Action Content");
290+
});
291+
292+
it("should display button content successfully", () => {
293+
render(
294+
EligibilityActions({
295+
actions: [
296+
actionBuilder()
297+
.withType(ActionDisplayType.buttonWithoutAuthLinkWithInfo)
298+
.andContent("Test Content")
299+
.build(),
300+
],
301+
vaccineType: VaccineType.FLU_FOR_CHILDREN,
302+
}),
303+
);
304+
305+
expectContentStringToBeVisible("Test Content");
306+
const bookingButton: HTMLElement = screen.getByText("NBS Booking Without Auth Link Test");
307+
expect(bookingButton).toBeInTheDocument();
308+
});
309+
310+
it("should display multiple button content components successfully", () => {
311+
render(
312+
EligibilityActions({
313+
actions: [
314+
actionBuilder()
315+
.withType(ActionDisplayType.buttonWithoutAuthLinkWithInfo)
316+
.andContent("Test Content 1")
317+
.build(),
318+
actionBuilder()
319+
.withType(ActionDisplayType.buttonWithoutAuthLinkWithInfo)
320+
.andContent("Test Content 2")
321+
.build(),
322+
],
323+
vaccineType: VaccineType.FLU_FOR_CHILDREN,
324+
}),
325+
);
326+
327+
expectEachContentStringToBeVisible(["Test Content 1", "Test Content 2"]);
328+
});
329+
330+
it("should display button without info if no description present", () => {
331+
render(
332+
EligibilityActions({
333+
actions: [actionBuilder().withType(ActionDisplayType.buttonWithoutAuthLinkWithInfo).andContent("").build()],
334+
vaccineType: VaccineType.FLU_FOR_CHILDREN,
335+
}),
336+
);
337+
338+
const buttonComponents = screen.getByTestId("action-button-without-auth-components");
339+
340+
const card = within(buttonComponents).queryByTestId("action-auth-button-card");
341+
342+
expect(card).not.toBeInTheDocument();
343+
});
344+
});
345+
244346
describe("actionLinkWithInfo", () => {
245347
it("should display info content successfully", () => {
246348
render(

src/app/_components/eligibility/EligibilityActions.tsx

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { MarkdownWithStyling } from "@src/app/_components/markdown/MarkdownWithStyling";
2-
import { NBSBookingActionForBaseUrl } from "@src/app/_components/nbs/NBSBookingAction";
2+
import {
3+
NBSBookingActionWithAuthSSOForBaseUrl,
4+
NBSBookingActionWithoutAuthForUrl,
5+
} from "@src/app/_components/nbs/NBSBookingAction";
36
import { BasicCard } from "@src/app/_components/nhs-frontend/BasicCard";
47
import { VaccineType } from "@src/models/vaccine";
58
import { Action, ActionDisplayType, ButtonUrl, Content, Label } from "@src/services/eligibility-api/types";
@@ -24,10 +27,10 @@ const EligibilityActions = ({ actions, vaccineType }: EligibilityActionProps): (
2427
return <Card key={index} content={action.content} delineator={isNotLastAction} />;
2528
}
2629

27-
case ActionDisplayType.buttonWithCard: {
30+
case ActionDisplayType.nbsAuthLinkButtonWithCard: {
2831
const card = action.content && <BasicCard content={action.content} delineator={false} />;
2932
const button = action.button && (
30-
<Button
33+
<NBSAuthSSOBookingButton
3134
vaccineType={vaccineType}
3235
url={action.button.url}
3336
label={action.button.label}
@@ -44,10 +47,10 @@ const EligibilityActions = ({ actions, vaccineType }: EligibilityActionProps): (
4447
);
4548
}
4649

47-
case ActionDisplayType.buttonWithInfo: {
50+
case ActionDisplayType.nbsAuthLinkButtonWithInfo: {
4851
const info = action.content && <InfoText content={action.content} delineator={false} />;
4952
const button = action.button && (
50-
<Button
53+
<NBSAuthSSOBookingButton
5154
vaccineType={vaccineType}
5255
url={action.button.url}
5356
label={action.button.label}
@@ -64,6 +67,25 @@ const EligibilityActions = ({ actions, vaccineType }: EligibilityActionProps): (
6467
);
6568
}
6669

70+
case ActionDisplayType.buttonWithoutAuthLinkWithInfo: {
71+
const info = action.content && <InfoText content={action.content} delineator={false} />;
72+
const button = action.button && (
73+
<NBSBookingActionWithoutAuthForUrl
74+
url={action.button.url.href}
75+
displayText={action.button.label}
76+
renderAs={"button"}
77+
reduceBottomPadding={true}
78+
/>
79+
);
80+
return (
81+
<div key={index} data-testid="action-button-without-auth-components">
82+
{info}
83+
{button}
84+
{isNotLastAction && <hr />}
85+
</div>
86+
);
87+
}
88+
6789
case ActionDisplayType.actionLinkWithInfo: {
6890
const info = action.content && <InfoText content={action.content} delineator={false} />;
6991
const link = action.button && (
@@ -123,9 +145,9 @@ type ButtonProps = {
123145
delineator: boolean;
124146
};
125147

126-
const Button = ({ vaccineType, url, label, renderAs }: ButtonProps): JSX.Element => {
148+
const NBSAuthSSOBookingButton = ({ vaccineType, url, label, renderAs }: ButtonProps): JSX.Element => {
127149
return (
128-
<NBSBookingActionForBaseUrl
150+
<NBSBookingActionWithAuthSSOForBaseUrl
129151
vaccineType={vaccineType}
130152
url={url.href}
131153
displayText={label}

src/app/_components/nbs/NBSBookingAction.test.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { useBrowserContext } from "@src/app/_components/context/BrowserContext";
2-
import { NBSBookingActionForBaseUrl, NBSBookingActionForVaccine } from "@src/app/_components/nbs/NBSBookingAction";
2+
import {
3+
NBSBookingActionWithAuthSSOForBaseUrl,
4+
NBSBookingActionWithAuthSSOForVaccine,
5+
NBSBookingActionWithoutAuthForUrl,
6+
} from "@src/app/_components/nbs/NBSBookingAction";
37
import { VaccineType } from "@src/models/vaccine";
48
import { randomURL } from "@test-data/meta-builder";
59
import { render, screen } from "@testing-library/react";
@@ -43,7 +47,7 @@ describe("NBSBookingAction", () => {
4347
describe("Given vaccine type", () => {
4448
const renderAndClick = (renderAs: "anchor" | "button" | "actionLink", whichElement: number) => {
4549
render(
46-
<NBSBookingActionForVaccine
50+
<NBSBookingActionWithAuthSSOForVaccine
4751
vaccineType={VaccineType.RSV}
4852
displayText="test"
4953
renderAs={renderAs}
@@ -77,7 +81,7 @@ describe("NBSBookingAction", () => {
7781

7882
const renderAndClick = (renderAs: "anchor" | "button" | "actionLink", whichElement: number) => {
7983
render(
80-
<NBSBookingActionForBaseUrl
84+
<NBSBookingActionWithAuthSSOForBaseUrl
8185
vaccineType={VaccineType.COVID_19}
8286
url={url.href}
8387
displayText="test"
@@ -109,4 +113,38 @@ describe("NBSBookingAction", () => {
109113
});
110114
});
111115
});
116+
117+
describe("Given full URL without SSO auth", () => {
118+
const url = randomURL();
119+
120+
const renderAndClick = (renderAs: "anchor" | "button" | "actionLink", whichElement: number) => {
121+
render(
122+
<NBSBookingActionWithoutAuthForUrl
123+
url={url.href}
124+
displayText="test"
125+
renderAs={renderAs}
126+
reduceBottomPadding={false}
127+
/>,
128+
);
129+
const role = renderAs === "button" ? "button" : "link";
130+
const element = screen.getAllByRole(role, { name: "test" })[whichElement];
131+
element.click();
132+
};
133+
134+
it.each(testScenarios)("$name", ({ context, expectedTarget, shouldCall }) => {
135+
(useBrowserContext as jest.Mock).mockReturnValue(context);
136+
137+
renderVariants.forEach(({ type, index }) => {
138+
jest.clearAllMocks();
139+
140+
renderAndClick(type, index);
141+
142+
if (shouldCall) {
143+
expect(window.open).toHaveBeenCalledWith(url.href, expectedTarget);
144+
} else {
145+
expect(window.open).not.toHaveBeenCalled();
146+
}
147+
});
148+
});
149+
});
112150
});

0 commit comments

Comments
 (0)