Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ terraform 1.14.1
pre-commit 4.5.0
vale 3.13.0
nodejs 22.13.1
gitleaks 8.30.1

# ==============================================================================
# The section below is reserved for Docker image versions.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ We will increase appointment attendance and reduce the number of 'did not attend

### GitHub

- As per NHS guidelines, make your GitHub email private by going to [GitHub Settings](https://github.com/settings/emails). There is a checkbox named "Keep my email addresses private". Note down your private email from this setting.
- As per NHS guidelines, make your GitHub email private by going to [GitHub profile settings](https://github.com/settings/emails). There is a checkbox named "Keep my email addresses private". Note down your private email from this setting.
- Follow these [instructions](https://nhsd-confluence.digital.nhs.uk/display/Vacc/Developer+setup%3A+Github).
- Remember to use your private email, noted above, in GitHub config 'user.email'.
- When on the step to create personal access tokens, remember to also tick 'workflow'. This will allow developers to update workflows
Expand Down Expand Up @@ -89,7 +89,7 @@ From NHS repository template:

- **Colima** - or any equivalent Docker container runtime, e.g. [Rancher Desktop](https://rancherdesktop.io/), etc.

- **Act** - tool to run GitHub actions locally. Refer to the [usage guide](https://nektosact.com/usage/index.html).
- **Act** - tool to run GitHub actions locally. Usage guide is available in this [article](https://nektosact.com/usage/index.html)

```shell
brew install act
Expand Down Expand Up @@ -260,7 +260,7 @@ make githooks-run

### Deploy your local changes to AWS dev environment

Refer to the detailed description of our [infrastructure](infrastructure/README.md).
A detailed description of our infrastructure is outlined in this [guide](infrastructure/README.md).

We use Terraform workspaces to distinguish each developer.
So make sure you use your own unique combination of initials, to set the workspace.
Expand Down Expand Up @@ -409,7 +409,7 @@ Our release strategy is based on Semantic Versioning and utilizes tagged commits
- A new tagged release in the "Releases" section of the GitHub repository.
- The corresponding build artifact within the `/tags` folder of the GitHub AWS S3 bucket.

**Refer to the [branching and tagging strategy](https://nhsd-confluence.digital.nhs.uk/spaces/Vacc/pages/989220238/Branching+and+release+strategy#Branchingandreleasestrategy-Fixingdeployedbrokenreleases) to fix broken deployed releases.**
**The branching and tagging strategy to fix broken deployed releases can be found in this [guide](https://nhsd-confluence.digital.nhs.uk/spaces/Vacc/pages/989220238/Branching+and+release+strategy#Branchingandreleasestrategy-Fixingdeployedbrokenreleases).**

## Design

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 3 additions & 29 deletions src/app/_components/eligibility/RSVEligibilityFallback.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { RSVEligibilityFallback } from "@src/app/_components/eligibility/RSVEligibilityFallback";
import { PharmacyBookingInfo } from "@src/app/_components/nbs/PharmacyBookingInfo";
import { VaccineType } from "@src/models/vaccine";
import { render, screen, within } from "@testing-library/react";

jest.mock("@src/app/_components/nbs/PharmacyBookingInfo", () => ({
PharmacyBookingInfo: jest
.fn()
.mockImplementation(() => <p data-testid={"pharmacy-booking-info"}>Pharmacy booking test</p>),
}));

describe("RSVEligibilityFallback", () => {
const vaccineType = VaccineType.RSV;
const howToGetVaccineFallback = <div>How Section styled component</div>;
Expand All @@ -17,40 +10,21 @@ describe("RSVEligibilityFallback", () => {
render(<RSVEligibilityFallback howToGetVaccineFallback={howToGetVaccineFallback} vaccineType={vaccineType} />);

const fallbackHeading: HTMLElement = screen.getByText("The RSV vaccine is recommended if you:");
const fallbackBulletPoint1: HTMLElement = screen.getByText("are aged between 75 and 79");
const fallbackBulletPoint2: HTMLElement = screen.getByText("turned 80 after 1 September 2024");
const fallbackBulletPoint1: HTMLElement = screen.getByText("are aged 75 or over");
const fallbackBulletPoint2: HTMLElement = screen.getByText("live in a care home for older adults");

expect(fallbackHeading).toBeVisible();
expect(fallbackBulletPoint1).toBeVisible();
expect(fallbackBulletPoint2).toBeVisible();
});

it("should include Pharmacy booking info for specified vaccine", () => {
render(<RSVEligibilityFallback howToGetVaccineFallback={howToGetVaccineFallback} vaccineType={vaccineType} />);

const pharmacyBookingInfo: HTMLElement = screen.getByTestId("pharmacy-booking-info");
expect(pharmacyBookingInfo).toBeVisible();

expect(PharmacyBookingInfo).toHaveBeenCalledWith(
{
vaccineType: vaccineType,
},
undefined,
);
});

it("should display 'If this applies' paragraph with provided how-to-get content", async () => {
it("should display provided how-to-get content", async () => {
render(<RSVEligibilityFallback howToGetVaccineFallback={howToGetVaccineFallback} vaccineType={vaccineType} />);

const fallback = screen.getByTestId("elid-fallback");

const fallbackHeading: HTMLElement = within(fallback).getByRole("heading", {
name: "If this applies to you",
level: 3,
});
const howToGetContent: HTMLElement = within(fallback).getByText("How Section styled component");

expect(fallbackHeading).toBeVisible();
expect(howToGetContent).toBeVisible();
});
});
8 changes: 2 additions & 6 deletions src/app/_components/eligibility/RSVEligibilityFallback.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { PharmacyBookingInfo } from "@src/app/_components/nbs/PharmacyBookingInfo";
import NonUrgentCareCard from "@src/app/_components/nhs-frontend/NonUrgentCareCard";
import { HEADINGS } from "@src/app/constants";
import { VaccineType } from "@src/models/vaccine";
import React, { JSX } from "react";

Expand All @@ -15,15 +13,13 @@ const RSVEligibilityFallback = (props: {
content={
<>
<ul>
<li>{"are aged between 75 and 79"}</li>
<li>{"turned 80 after 1 September 2024"}</li>
<li>{"are aged 75 or over"}</li>
<li>{"live in a care home for older adults"}</li>
</ul>
</>
}
/>
<h3>{HEADINGS.IF_THIS_APPLIES}</h3>
{props.howToGetVaccineFallback}
<PharmacyBookingInfo vaccineType={props.vaccineType} />
</div>
);
};
Expand Down
10 changes: 3 additions & 7 deletions src/app/_components/nbs/PharmacyBookingInfo.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,16 @@ describe("PharmacyBookingInfo", () => {
it("should contain include correct text for older adults", () => {
render(<PharmacyBookingInfo vaccineType={VaccineType.RSV} />);

const pharmacyBookingInfo: HTMLElement = screen.getByText(
/This pharmacy service is only for adults aged 75 to 79./,
);
const pharmacyBookingInfo: HTMLElement = screen.getByText(/In some areas you can /);

expect(pharmacyBookingInfo).toBeInTheDocument();
});

it("should contain include correct text for younger adults", () => {
render(<PharmacyBookingInfo vaccineType={VaccineType.RSV_PREGNANCY} />);

const pharmacyBookingInfo: HTMLElement | null = screen.queryByText(
/This pharmacy service is only for adults aged 75 to 79./,
);
const pharmacyBookingInfo: HTMLElement | null = screen.queryByText(/In some areas you can also /);

expect(pharmacyBookingInfo).not.toBeInTheDocument();
expect(pharmacyBookingInfo).toBeInTheDocument();
});
});
2 changes: 1 addition & 1 deletion src/app/_components/nbs/PharmacyBookingInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const PharmacyBookingInfo = ({ vaccineType }: PharmacyBookingProps): JSX.Element
renderAs={"anchor"}
reduceBottomPadding={false}
/>
{vaccineInfo.forOlderAdults ? ". This pharmacy service is only for adults aged 75 to 79." : "."}
{"."}
</p>
);
};
Expand Down
1 change: 0 additions & 1 deletion src/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ export const HEADINGS = {
HOW_TO_GET_VACCINE: "How to get the vaccine",
EXTRA_DOSES_SCHEDULE: "Extra doses of the vaccine",
VACCINE_SIDE_EFFECTS: "Side effects of the vaccine",
IF_THIS_APPLIES: "If this applies to you",
};
7 changes: 6 additions & 1 deletion src/app/vaccines-for-all-ages/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ describe("VaccinesForAllAges", () => {
description: "65 to 67 and 70 to 79 years",
path: "/vaccines/shingles-vaccine",
},
{ section: AgeSectionTestId.ADULTS, cardTitle: "RSV", description: "75 years and over", path: "/vaccines/rsv" },
{
section: AgeSectionTestId.ADULTS,
cardTitle: "RSV",
description: "75 years and over, or living in a care home for older adults",
path: "/vaccines/rsv",
},
{
section: AgeSectionTestId.ADULTS,
cardTitle: "COVID-19",
Expand Down
5 changes: 3 additions & 2 deletions src/models/ageBasedHub.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,16 @@ describe("AgeBasedHubInfo", () => {
expect(info.heading).toBe("Adults aged 65 to 74 should get these routine vaccines");
});

it("has 3 vaccines", () => {
expect(info.vaccines).toHaveLength(4);
it("has expected #vaccines", () => {
expect(info.vaccines).toHaveLength(5);
});

it("has correct vaccine names", () => {
expect(info.vaccines.map((v) => v.vaccineName)).toEqual([
VaccineType.PNEUMOCOCCAL,
VaccineType.FLU_FOR_ADULTS,
VaccineType.SHINGLES,
VaccineType.RSV,
VaccineType.COVID_19,
]);
});
Expand Down
1 change: 1 addition & 0 deletions src/models/ageBasedHub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const AgeBasedHubInfo: Record<AgeGroup, AgeBasedHubDetails | undefined> = {
{ vaccineName: VaccineType.PNEUMOCOCCAL, cardLinkDescription: "65 years and over" },
{ vaccineName: VaccineType.FLU_FOR_ADULTS, cardLinkDescription: "65 years and over" },
{ vaccineName: VaccineType.SHINGLES, cardLinkDescription: "65 to 67 and 70 to 79 years" },
{ vaccineName: VaccineType.RSV, cardLinkDescription: "If living in a care home for older adults" },
{ vaccineName: VaccineType.COVID_19, cardLinkDescription: "If living in a care home for older adults" },
],
showPregnancyHubContent: false,
Expand Down
2 changes: 1 addition & 1 deletion src/models/vaccine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ const adultVaccines: VaccineCardDetails[] = [
{ vaccineName: VaccineType.PNEUMOCOCCAL, cardLinkDescription: "65 years and over" },
{ vaccineName: VaccineType.FLU_FOR_ADULTS, cardLinkDescription: "65 years and over" },
{ vaccineName: VaccineType.SHINGLES, cardLinkDescription: "65 to 67 and 70 to 79 years" },
{ vaccineName: VaccineType.RSV, cardLinkDescription: "75 years and over" },
{ vaccineName: VaccineType.RSV, cardLinkDescription: "75 years and over, or living in a care home for older adults" },
{
vaccineName: VaccineType.COVID_19,
cardLinkDescription: "75 years and over, or living in a care home for older adults",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ jest.mock("@project/src/app/_components/markdown/MarkdownWithStyling", () => ({
MarkdownWithStyling: jest.fn(),
}));

jest.mock("@src/app/_components/nbs/PharmacyBookingInfo", () => ({
PharmacyBookingInfo: () => <div>Pharmacy Booking Info</div>,
}));

jest.mock("sanitize-data", () => ({ sanitize: jest.fn() }));
jest.mock("cheerio", () => ({
load: jest.fn(() => {
Expand Down Expand Up @@ -69,7 +73,7 @@ describe("ContentStylingService", () => {

const mockHowToGetMarkdownSubsection: VaccinePageSubsection = {
type: "simpleElement",
text: "<p>para</p><h3>If you're aged 75 to 79</h3><p>para1</p><p>para2</p><h3>If you're pregnant</h3><p>para3</p><p>para4</p>",
text: "<p>para</p><h3>If you're aged 75 or over</h3><p>para1</p><p>para2</p><h3>If you live in a care home for older adults</h3><p>para3</p><p>para4</p><h3>If you're pregnant</h3><p>para5</p><p>para6</p>",
name: "markdown",
headline: "How To Get Headline",
};
Expand Down Expand Up @@ -347,10 +351,11 @@ describe("ContentStylingService", () => {
expect(styledVaccineContent.recommendation?.heading).toEqual(mockRecommendation.heading);
expect(styledVaccineContent.additionalInformation?.heading).toEqual(mockAdditionalInformationSection.headline);

const expectedRsvHowToGetSection = "<div><p>para1</p><p>para2</p></div>";
const expectedRsvPregnancyHowToGetSection = `<div><div><p>para3</p><p>para4</p></div></div>`;
const expectedRsvHowToGetSection =
"<div><div><h3>If you're aged 75 or over</h3><p>para1</p><p>para2</p></div><div>Pharmacy Booking Info</div><div><h3>If you live in a care home for older adults</h3><p>para3</p><p>para4</p></div></div>";
const expectedRsvPregnancyHowToGetSection = `<div><div><p>para5</p><p>para6</p></div></div>`;
const expectedGenericVaccineHowToGetSection =
"<div class=\"zeroMarginBottom\"><h3>How To Get Headline</h3><p>para</p><h3>If you're aged 75 to 79</h3><p>para1</p><p>para2</p><h3>If you're pregnant</h3><p>para3</p><p>para4</p></div>";
"<div class=\"zeroMarginBottom\"><h3>How To Get Headline</h3><p>para</p><h3>If you're aged 75 or over</h3><p>para1</p><p>para2</p><h3>If you live in a care home for older adults</h3><p>para3</p><p>para4</p><h3>If you're pregnant</h3><p>para5</p><p>para6</p></div>";
const { container } = render(styledVaccineContent.howToGetVaccine.component);
if (vaccine === VaccineType.RSV) {
expect(container).toContainHTML(expectedRsvHowToGetSection);
Expand Down
21 changes: 7 additions & 14 deletions src/services/content-api/parsers/custom/both.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { render } from "@testing-library/react";
import React from "react";

const mockNBSBookingActionHTML = "NBS Booking Link Test";
jest.mock("@src/app/_components/nbs/PharmacyBookingInfo", () => ({
PharmacyBookingInfo: () => <p data-testid="pharmacy-booking-info">test</p>,
}));
jest.mock("@src/app/_components/nbs/NBSBookingAction", () => ({
NBSBookingAction: () => mockNBSBookingActionHTML,
}));
Expand All @@ -26,14 +29,7 @@ const mockRsvInPregnancySubsection: VaccinePageSubsection = {

const mockRsvForOlderAdultsSubsection: VaccinePageSubsection = {
type: "simpleElement",
text: "<h3>If you're aged 75 to 79</h3><p>Paragraph 1</p><p>Paragraph 2</p>",
headline: "",
name: "",
};

const mockRsvForOlderAdultsNewerSubsection: VaccinePageSubsection = {
type: "simpleElement",
text: "<h3>If you're aged 75 to 79 (or turned 80 after 1 September 2024)</h3><p>Paragraph 1</p><p>Paragraph 2</p>",
text: "<h3>If you're aged 75 or over</h3><p>Paragraph 1</p><p>Paragraph 2</p><h3>If you live in a care home for older adults</h3><p>Paragraph 3</p><p>Paragraph 4</p>",
headline: "",
name: "",
};
Expand Down Expand Up @@ -63,12 +59,9 @@ describe("styleHowToGetSubsection for rsv in older adults", () => {

it("renders HTML if subsection contains rsv for older adults", () => {
const { container } = render(<>{styleHowToGetSubsectionForRsv(mockRsvForOlderAdultsSubsection, 0, false)}</>);
expect(container.innerHTML).toBe("<div><p>Paragraph 1</p><p>Paragraph 2</p></div>");
});

it("renders HTML if newer subsection contains rsv for older adults", () => {
const { container } = render(<>{styleHowToGetSubsectionForRsv(mockRsvForOlderAdultsNewerSubsection, 0, false)}</>);
expect(container.innerHTML).toBe("<div><p>Paragraph 1</p><p>Paragraph 2</p></div>");
expect(container.innerHTML).toBe(
'<div><div><h3>If you\'re aged 75 or over</h3><p>Paragraph 1</p><p>Paragraph 2</p></div><p data-testid="pharmacy-booking-info">test</p><div><h3>If you live in a care home for older adults</h3><p>Paragraph 3</p><p>Paragraph 4</p></div></div>',
);
});
});

Expand Down
37 changes: 23 additions & 14 deletions src/services/content-api/parsers/custom/rsv.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PharmacyBookingInfo } from "@src/app/_components/nbs/PharmacyBookingInfo";
import { VaccineType } from "@src/models/vaccine";
import { ContentParsingError } from "@src/services/content-api/parsers/custom/exceptions";
import type { StyledPageSection, VaccinePageSection, VaccinePageSubsection } from "@src/services/content-api/types";
import { logger } from "@src/utils/logger";
Expand All @@ -7,9 +9,9 @@ import React from "react";

const log: Logger = logger.child({ module: "services-content-api-parsers-custom-rsv" });

const olderAdultsRegExp: RegExp =
/<h3>If you're aged \d+ to \d+(?: \(or turned \d+ after .*\))?<\/h3>((?:\s*<p>.*?<\/p>)+)/i;
const paragraphsRegExp: RegExp = /<p>.*?<\/p>/g;
const olderAdultsRegExp: RegExp = /<h3>If you're aged \d+ or over<\/h3>((?:\s*<p>.*?<\/p>)+)/i;
const olderAdultsInCareHomeRegExp: RegExp =
/<h3>If you live in a care home for older adults<\/h3>((?:\s*<p>.*?<\/p>)+)/i;

export const styleHowToGetSubsection = (subsection: VaccinePageSubsection, index: number, fragile: boolean) => {
if (subsection.type !== "simpleElement") {
Expand All @@ -31,26 +33,33 @@ export const styleHowToGetSubsection = (subsection: VaccinePageSubsection, index
}
}

const paragraphsMatches = olderAdultsMatches[1].match(paragraphsRegExp);
if (!paragraphsMatches) {
const olderAdultsInCareHomeMatches = olderAdultsInCareHomeRegExp.exec(subsection.text);
if (!olderAdultsInCareHomeMatches) {
log.warn(
{ context: { text: olderAdultsMatches[1] } },
"HowToGetSubsection paragraph not found - has the content changed?",
{ context: { text: subsection.text } },
"HowToGetSubsection care home header not found - has the content changed?",
);
if (fragile) {
throw new ContentParsingError("HowToGetSubsection paragraph not found - has the content changed?");
throw new ContentParsingError("HowToGetSubsection care home header not found - has the content changed?");
} else {
return <></>;
}
}

return (
<div
key={index}
dangerouslySetInnerHTML={{
__html: sanitiseHtml(paragraphsMatches.join("")),
}}
/>
<div key={index}>
<div
dangerouslySetInnerHTML={{
__html: sanitiseHtml(olderAdultsMatches[0]),
}}
/>
<PharmacyBookingInfo vaccineType={VaccineType.RSV} />
<div
dangerouslySetInnerHTML={{
__html: sanitiseHtml(olderAdultsInCareHomeMatches[0]),
}}
/>
</div>
);
};

Expand Down
2 changes: 1 addition & 1 deletion wiremock/__files/rsv-vaccine.json
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@
{
"position": 0,
"identifier": "1",
"text": "<p>There are different ways to get the RSV vaccine.</p><h3>If you're pregnant</h3><p>You should be offered the RSV vaccine around the time of your 28-week antenatal appointment.</p><p>Getting vaccinated as soon as possible from 28 weeks will provide the best protection for your baby. But the vaccine can be given later if needed, including up until you go into labour.</p><p>Speak to your maternity service or GP surgery if you're 28 weeks pregnant or more and have not been offered the vaccine.</p><h3>If you're aged 75 to 79 (or turned 80 after 1 September 2024)</h3><p>If you're aged 75 to 79 (or turned 80 after 1 September 2024) contact your GP surgery to book your RSV vaccination.</p><p>Your GP surgery may contact you about getting the RSV vaccine. This may be by letter, text, phone call or email.</p><p>You do not need to wait to be contacted before booking your vaccination.</p>",
"text": "<p>There are different ways to get the RSV vaccine.</p><h3>If you're pregnant</h3><p>You should be offered the RSV vaccine around the time of your 28-week antenatal appointment.</p><p>Getting vaccinated as soon as possible from 28 weeks will provide the best protection for your baby. But the vaccine can be given later if needed, including up until you go into labour.</p><p>Speak to your maternity service or GP surgery if you're 28 weeks pregnant or more and have not been offered the vaccine.</p><h3>If you're aged 75 or over</h3><p>Contact your GP surgery to book your RSV vaccination.</p><p>Your GP surgery may contact you about getting the RSV vaccine. This may be by letter, text, phone call or email.</p><p>You do not need to wait to be contacted before booking your vaccination.</p><h3>If you live in a care home for older adults</h3><p>Speak to a member of staff at your care home or your GP surgery about how to get the RSV vaccine.</p><p>Your GP surgery may contact you about getting the RSV vaccine. This may be by letter, text, phone call or email.</p>",
"@type": "WebPageElement",
"name": "markdown",
"headline": ""
Expand Down
Loading
Loading