From 75e814290a5e6a87bb8c1712f9753dd3c71e4f39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 14:05:11 +0100
Subject: [PATCH 1/9] refactored the method to split bigg method to separated
---
ui/src/routes/HomeTestTermsOfUsePage.tsx | 212 ++++++++++++-----------
1 file changed, 108 insertions(+), 104 deletions(-)
diff --git a/ui/src/routes/HomeTestTermsOfUsePage.tsx b/ui/src/routes/HomeTestTermsOfUsePage.tsx
index b366562e5..8f4f08421 100644
--- a/ui/src/routes/HomeTestTermsOfUsePage.tsx
+++ b/ui/src/routes/HomeTestTermsOfUsePage.tsx
@@ -1,89 +1,123 @@
"use client";
+import "@/styles/lists.css";
+
import { useNavigate } from "react-router-dom";
+
+import type { PrivacyPolicySection, PrivacyPolicySubsection } from "@/content";
import { useContent } from "@/hooks";
import PageLayout from "@/layouts/PageLayout";
-import { renderTextWithLinks, cleanListItems, getListClass } from "@/utils/renderTextWithLinks";
-import "@/styles/lists.css";
+import { cleanListItems, getListClass, renderTextWithLinks } from "@/utils/renderTextWithLinks";
-export default function HomeTestTermsOfUsePage() {
- const navigate = useNavigate();
- const { "home-test-terms-of-use": content } = useContent();
+const renderTableCellLines = (cell: string, rowIdx: number, cellIdx: number) =>
+ cell.split("\n").map((line, lineIdx) => (
+
+ {renderTextWithLinks(line, `tbl-${rowIdx}-${cellIdx}-${lineIdx}-`)}
+
+ ));
+
+const renderTableCell = (cell: string, cellIdx: number, rowIdx: number) => (
+
+ {renderTableCellLines(cell, rowIdx, cellIdx)}
+ |
+);
- /**
- * Renders a paragraph, auto-bolding the leading paragraph reference number (e.g. "1.1. ")
- */
- const renderParagraphs = (paragraphs: string[]) => {
- return paragraphs.map((paragraph, index) => {
- const numberMatch = paragraph.match(/^(\d+\.\d+\.?\s+)/);
- if (numberMatch) {
- const number = numberMatch[1];
- const rest = paragraph.slice(number.length);
- return (
-
- {number}
- {renderTextWithLinks(rest, `p${index}-`)}
-
- );
- }
+const renderTableRow = (row: string[], rowIdx: number) => (
+
+ {row.map((cell, cellIdx) => renderTableCell(cell, cellIdx, rowIdx))}
+
+);
+
+/**
+ * Renders a paragraph, auto-bolding the leading paragraph reference number (e.g. "1.1. ")
+ */
+const renderParagraphs = (paragraphs: string[]) =>
+ paragraphs.map((paragraph, index) => {
+ const numberMatch = paragraph.match(/^(\d+\.\d+\.?\s+)/);
+ if (numberMatch) {
+ const number = numberMatch[1];
+ const rest = paragraph.slice(number.length);
return (
- {renderTextWithLinks(paragraph, `p${index}-`)}
+ {number}
+ {renderTextWithLinks(rest, `p${index}-`)}
);
- });
- };
-
- const renderList = (
- items: string[],
- ordered?: boolean,
- indented?: boolean,
- listStyle?: "bullet" | "dash",
- ) => {
- const cleanedItems = cleanListItems(items);
- const ListTag = ordered ? "ol" : "ul";
- const listClass = getListClass(ordered, listStyle);
- const list = (
-
- {cleanedItems.map((item, index) => (
- {renderTextWithLinks(item, `li${index}-`)}
- ))}
-
- );
- return indented ? {list}
: list;
- };
-
- const renderTable = (table: { caption?: string; headers: string[]; rows: string[][] }) => {
+ }
return (
-
- {table.caption && {table.caption}}
-
-
- {table.headers.map((header, i) => (
- |
- {header}
- |
- ))}
-
-
-
- {table.rows.map((row, rowIdx) => (
-
- {row.map((cell, cellIdx) => (
- |
- {cell.split("\n").map((line, lineIdx) => (
-
- {renderTextWithLinks(line, `tbl-${rowIdx}-${cellIdx}-${lineIdx}-`)}
-
- ))}
- |
- ))}
-
- ))}
-
-
+
+ {renderTextWithLinks(paragraph, `p${index}-`)}
+
);
- };
+ });
+
+const renderList = (
+ items: string[],
+ ordered?: boolean,
+ indented?: boolean,
+ listStyle?: "bullet" | "dash",
+) => {
+ const cleanedItems = cleanListItems(items);
+ const ListTag = ordered ? "ol" : "ul";
+ const listClass = getListClass(ordered, listStyle);
+ const list = (
+
+ {cleanedItems.map((item, index) => (
+ {renderTextWithLinks(item, `li${index}-`)}
+ ))}
+
+ );
+ return indented ? {list}
: list;
+};
+
+const renderTable = (table: { caption?: string; headers: string[]; rows: string[][] }) => (
+
+ {table.caption && {table.caption}}
+
+
+ {table.headers.map((header, i) => (
+ |
+ {header}
+ |
+ ))}
+
+
+ {table.rows.map(renderTableRow)}
+
+);
+
+const renderSubsection = (subsection: PrivacyPolicySubsection, subIndex: number) => (
+
+ {subsection.heading &&
{subsection.heading}
}
+
+ {subsection.paragraphs && renderParagraphs(subsection.paragraphs)}
+
+ {subsection.list &&
+ renderList(subsection.list, subsection.ordered, subsection.indented, subsection.listStyle)}
+
+ {subsection.table && renderTable(subsection.table)}
+
+);
+
+const renderSection = (section: PrivacyPolicySection) => (
+
+
+ {section.heading}
+
+
+ {renderParagraphs(section.paragraphs)}
+
+ {section.subsections?.map(renderSubsection)}
+
+);
+
+export default function HomeTestTermsOfUsePage() {
+ const navigate = useNavigate();
+ const { "home-test-terms-of-use": content } = useContent();
return (
navigate(-1)}>
@@ -91,37 +125,7 @@ export default function HomeTestTermsOfUsePage() {
{renderParagraphs(content.introduction)}
- {content.sections.map((section) => (
-
-
- {section.heading}
-
-
- {renderParagraphs(section.paragraphs)}
-
- {section.subsections?.map((subsection, subIndex) => (
-
- {subsection.heading &&
{subsection.heading}
}
-
- {subsection.paragraphs && renderParagraphs(subsection.paragraphs)}
-
- {subsection.list &&
- renderList(
- subsection.list,
- subsection.ordered,
- subsection.indented,
- subsection.listStyle,
- )}
-
- {subsection.table && renderTable(subsection.table)}
-
- ))}
-
- ))}
+ {content.sections.map(renderSection)}
);
}
From 13cdd9b44d4916b2d2c3069894baedd3813b8c70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 14:24:15 +0100
Subject: [PATCH 2/9] fix window to globalthis
---
ui/src/lib/services/session-storage-service.ts | 8 ++++----
ui/src/routes/HomeTestTermsOfUsePage.tsx | 4 +++-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/ui/src/lib/services/session-storage-service.ts b/ui/src/lib/services/session-storage-service.ts
index 9e9f477db..ef6af5c8d 100644
--- a/ui/src/lib/services/session-storage-service.ts
+++ b/ui/src/lib/services/session-storage-service.ts
@@ -1,6 +1,6 @@
class SessionStorageService {
private isAvailable(): boolean {
- return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
+ return globalThis.sessionStorage !== undefined;
}
rehydrate(key: string, fallback: T): T {
@@ -8,7 +8,7 @@ class SessionStorageService {
return fallback;
}
- const rawValue = window.sessionStorage.getItem(key);
+ const rawValue = globalThis.sessionStorage.getItem(key);
if (!rawValue) {
return fallback;
}
@@ -26,7 +26,7 @@ class SessionStorageService {
return;
}
- window.sessionStorage.setItem(key, JSON.stringify(value));
+ globalThis.sessionStorage.setItem(key, JSON.stringify(value));
}
remove(key: string): void {
@@ -34,7 +34,7 @@ class SessionStorageService {
return;
}
- window.sessionStorage.removeItem(key);
+ globalThis.sessionStorage.removeItem(key);
}
}
diff --git a/ui/src/routes/HomeTestTermsOfUsePage.tsx b/ui/src/routes/HomeTestTermsOfUsePage.tsx
index 8f4f08421..dcd7f5586 100644
--- a/ui/src/routes/HomeTestTermsOfUsePage.tsx
+++ b/ui/src/routes/HomeTestTermsOfUsePage.tsx
@@ -82,7 +82,9 @@ const renderTable = (table: { caption?: string; headers: string[]; rows: string[
))}
- {table.rows.map(renderTableRow)}
+
+ {table.rows.map((row, rowIdx) => renderTableRow(row, rowIdx))}
+
);
From e992729f1c75e5b6eb5f25293a383b769a709983 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 14:26:38 +0100
Subject: [PATCH 3/9] fixed the order of function parameters
---
ui/src/routes/HomeTestTermsOfUsePage.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ui/src/routes/HomeTestTermsOfUsePage.tsx b/ui/src/routes/HomeTestTermsOfUsePage.tsx
index dcd7f5586..c5ad9a154 100644
--- a/ui/src/routes/HomeTestTermsOfUsePage.tsx
+++ b/ui/src/routes/HomeTestTermsOfUsePage.tsx
@@ -16,7 +16,7 @@ const renderTableCellLines = (cell: string, rowIdx: number, cellIdx: number) =>
));
-const renderTableCell = (cell: string, cellIdx: number, rowIdx: number) => (
+const renderTableCell = (cell: string, rowIdx: number, cellIdx: number) => (
{renderTableCellLines(cell, rowIdx, cellIdx)}
|
@@ -24,7 +24,7 @@ const renderTableCell = (cell: string, cellIdx: number, rowIdx: number) => (
const renderTableRow = (row: string[], rowIdx: number) => (
- {row.map((cell, cellIdx) => renderTableCell(cell, cellIdx, rowIdx))}
+ {row.map((cell, cellIdx) => renderTableCell(cell, rowIdx, cellIdx))}
);
From a34a7503c9f215542b2acbbe23c3ff3844b354c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 14:31:48 +0100
Subject: [PATCH 4/9] resolve ambiguous spacing issue
---
.../CheckYourAnswersPage.tsx | 8 +++----
.../GetSelfTestKitPage.tsx | 23 +++++++++++--------
2 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage.tsx
index cc05b1bba..bb007cc93 100644
--- a/ui/src/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage.tsx
+++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage.tsx
@@ -252,7 +252,7 @@ export default function CheckYourAnswersPage() {
error={consentError || undefined}
>
- {content.consent.label.replace("{supplier}", supplierName)}{" "}
+ {`${content.consent.label.replace("{supplier}", supplierName)} `}
{
@@ -261,8 +261,8 @@ export default function CheckYourAnswersPage() {
}}
>
{content.consent.termsOfUseText}
- {" "}
- {content.consent.labelAnd}{" "}
+
+ {` ${content.consent.labelAnd} `}
{
@@ -272,7 +272,7 @@ export default function CheckYourAnswersPage() {
>
{content.consent.privacyPolicyText}
- .
+ {"."}
diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage.tsx
index f56f2b87f..ed253f4c9 100644
--- a/ui/src/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage.tsx
+++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/GetSelfTestKitPage.tsx
@@ -34,8 +34,9 @@ export default function GetSelfTestKitPage() {
- {content.urgentCard.aeAdvice}{" "}
- {commonContent.links.nearestAE.text}.
+ {`${content.urgentCard.aeAdvice} `}
+ {commonContent.links.nearestAE.text}
+ {"."}
@@ -70,25 +71,27 @@ export default function GetSelfTestKitPage() {
{content.aboutService.heading}
- {content.aboutService.text}{" "}
- {content.aboutService.termsLink} and{" "}
- {content.aboutService.privacyLink}.
+ {`${content.aboutService.text} `}
+ {content.aboutService.termsLink}
+ {` and `}
+ {content.aboutService.privacyLink}
+ {"."}
{content.otherOptions.heading}
- {content.otherOptions.clinicText}{" "}
+ {`${content.otherOptions.clinicText} `}
{content.otherOptions.clinicLinkText}
- {" "}
- {content.otherOptions.clinicTextEnd}
+
+ {` ${content.otherOptions.clinicTextEnd}`}
- {content.otherOptions.sexualHealthText}{" "}
+ {`${content.otherOptions.sexualHealthText} `}
{content.otherOptions.sexualHealthLink.text}
- .
+ {"."}
From 2c891e6a1748a3a541b12d1d0b9b86801971d1a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 14:37:07 +0100
Subject: [PATCH 5/9] added test for session storage service
---
.../services/session-storage-service.test.ts | 71 +++++++++++++++++++
1 file changed, 71 insertions(+)
create mode 100644 ui/src/__tests__/lib/services/session-storage-service.test.ts
diff --git a/ui/src/__tests__/lib/services/session-storage-service.test.ts b/ui/src/__tests__/lib/services/session-storage-service.test.ts
new file mode 100644
index 000000000..7542f20c0
--- /dev/null
+++ b/ui/src/__tests__/lib/services/session-storage-service.test.ts
@@ -0,0 +1,71 @@
+import sessionStorageService from "@/lib/services/session-storage-service";
+
+describe("SessionStorageService", () => {
+ beforeEach(() => {
+ window.sessionStorage.clear();
+ });
+
+ describe("rehydrate", () => {
+ it("returns fallback when sessionStorage is unavailable", () => {
+ const original = Object.getOwnPropertyDescriptor(globalThis, "sessionStorage");
+ Object.defineProperty(globalThis, "sessionStorage", { value: undefined, configurable: true });
+
+ expect(sessionStorageService.rehydrate("key", "fallback")).toBe("fallback");
+
+ Object.defineProperty(globalThis, "sessionStorage", original!);
+ });
+
+ it("returns fallback when key does not exist", () => {
+ expect(sessionStorageService.rehydrate("missing-key", 42)).toBe(42);
+ });
+
+ it("returns parsed value when key exists with valid JSON", () => {
+ window.sessionStorage.setItem("my-key", JSON.stringify({ foo: "bar" }));
+
+ expect(sessionStorageService.rehydrate("my-key", null)).toEqual({ foo: "bar" });
+ });
+
+ it("returns fallback and removes key when stored value is invalid JSON", () => {
+ window.sessionStorage.setItem("bad-key", "not-valid-json{");
+
+ expect(sessionStorageService.rehydrate("bad-key", "default")).toBe("default");
+ expect(window.sessionStorage.getItem("bad-key")).toBeNull();
+ });
+ });
+
+ describe("dehydrate", () => {
+ it("does nothing when sessionStorage is unavailable", () => {
+ const original = Object.getOwnPropertyDescriptor(globalThis, "sessionStorage");
+ Object.defineProperty(globalThis, "sessionStorage", { value: undefined, configurable: true });
+
+ expect(() => sessionStorageService.dehydrate("key", { x: 1 })).not.toThrow();
+
+ Object.defineProperty(globalThis, "sessionStorage", original!);
+ });
+
+ it("stores value as JSON string", () => {
+ sessionStorageService.dehydrate("my-key", { a: 1, b: true });
+
+ expect(window.sessionStorage.getItem("my-key")).toBe(JSON.stringify({ a: 1, b: true }));
+ });
+ });
+
+ describe("remove", () => {
+ it("does nothing when sessionStorage is unavailable", () => {
+ const original = Object.getOwnPropertyDescriptor(globalThis, "sessionStorage");
+ Object.defineProperty(globalThis, "sessionStorage", { value: undefined, configurable: true });
+
+ expect(() => sessionStorageService.remove("key")).not.toThrow();
+
+ Object.defineProperty(globalThis, "sessionStorage", original!);
+ });
+
+ it("removes the key from sessionStorage", () => {
+ window.sessionStorage.setItem("to-remove", "value");
+
+ sessionStorageService.remove("to-remove");
+
+ expect(window.sessionStorage.getItem("to-remove")).toBeNull();
+ });
+ });
+});
From 9070a99540db897c6f3b05fdfb8bd35eeb272369 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 14:39:54 +0100
Subject: [PATCH 6/9] removed empty files and fix lint issues
---
.../CannotUseServiceUnder18Page.test.tsx | 9 ++++-----
ui/src/app/globals.css | 0
ui/src/app/layout.tsx | 1 -
3 files changed, 4 insertions(+), 6 deletions(-)
delete mode 100644 ui/src/app/globals.css
diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/CannotUseServiceUnder18Page.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/CannotUseServiceUnder18Page.test.tsx
index 049e0a822..fce5955b2 100644
--- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/CannotUseServiceUnder18Page.test.tsx
+++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/CannotUseServiceUnder18Page.test.tsx
@@ -1,6 +1,10 @@
import "@testing-library/jest-dom";
+import { fireEvent, render, screen } from "@testing-library/react";
+import React, { ReactNode } from "react";
+import { MemoryRouter } from "react-router-dom";
import { CannotUseServiceUnder18Content, CommonContent } from "@/content";
+import { JourneyStepNames, RoutePath } from "@/lib/models/route-paths";
import CannotUseServiceUnder18Page, {
HARD_CODED_CLINIC_DATA,
NHS_LINKS,
@@ -8,14 +12,9 @@ import CannotUseServiceUnder18Page, {
import {
CreateOrderProvider,
JourneyNavigationContext,
- JourneyNavigationProvider,
OrderAnswers,
useCreateOrderContext,
} from "@/state";
-import { JourneyStepNames, RoutePath } from "@/lib/models/route-paths";
-import React, { ReactNode } from "react";
-import { fireEvent, render, screen } from "@testing-library/react";
-import { MemoryRouter } from "react-router-dom";
const mockedContent: CannotUseServiceUnder18Content = {
title: "some-mocked-title",
diff --git a/ui/src/app/globals.css b/ui/src/app/globals.css
deleted file mode 100644
index e69de29bb..000000000
diff --git a/ui/src/app/layout.tsx b/ui/src/app/layout.tsx
index 6244d3cf9..0737baba6 100644
--- a/ui/src/app/layout.tsx
+++ b/ui/src/app/layout.tsx
@@ -1,4 +1,3 @@
-import "./globals.css";
import "nhsuk-frontend/dist/nhsuk/nhsuk-frontend.css";
import type { Metadata } from "next";
From a87ead75e2a0e422282f17127538538a1915abdb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 14:46:52 +0100
Subject: [PATCH 7/9] fix test for edge case
---
.../services/session-storage-service.test.ts | 27 ++++++++++---------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/ui/src/__tests__/lib/services/session-storage-service.test.ts b/ui/src/__tests__/lib/services/session-storage-service.test.ts
index 7542f20c0..94e583e01 100644
--- a/ui/src/__tests__/lib/services/session-storage-service.test.ts
+++ b/ui/src/__tests__/lib/services/session-storage-service.test.ts
@@ -9,10 +9,11 @@ describe("SessionStorageService", () => {
it("returns fallback when sessionStorage is unavailable", () => {
const original = Object.getOwnPropertyDescriptor(globalThis, "sessionStorage");
Object.defineProperty(globalThis, "sessionStorage", { value: undefined, configurable: true });
-
- expect(sessionStorageService.rehydrate("key", "fallback")).toBe("fallback");
-
- Object.defineProperty(globalThis, "sessionStorage", original!);
+ try {
+ expect(sessionStorageService.rehydrate("key", "fallback")).toBe("fallback");
+ } finally {
+ Object.defineProperty(globalThis, "sessionStorage", original!);
+ }
});
it("returns fallback when key does not exist", () => {
@@ -37,10 +38,11 @@ describe("SessionStorageService", () => {
it("does nothing when sessionStorage is unavailable", () => {
const original = Object.getOwnPropertyDescriptor(globalThis, "sessionStorage");
Object.defineProperty(globalThis, "sessionStorage", { value: undefined, configurable: true });
-
- expect(() => sessionStorageService.dehydrate("key", { x: 1 })).not.toThrow();
-
- Object.defineProperty(globalThis, "sessionStorage", original!);
+ try {
+ expect(() => sessionStorageService.dehydrate("key", { x: 1 })).not.toThrow();
+ } finally {
+ Object.defineProperty(globalThis, "sessionStorage", original!);
+ }
});
it("stores value as JSON string", () => {
@@ -54,10 +56,11 @@ describe("SessionStorageService", () => {
it("does nothing when sessionStorage is unavailable", () => {
const original = Object.getOwnPropertyDescriptor(globalThis, "sessionStorage");
Object.defineProperty(globalThis, "sessionStorage", { value: undefined, configurable: true });
-
- expect(() => sessionStorageService.remove("key")).not.toThrow();
-
- Object.defineProperty(globalThis, "sessionStorage", original!);
+ try {
+ expect(() => sessionStorageService.remove("key")).not.toThrow();
+ } finally {
+ Object.defineProperty(globalThis, "sessionStorage", original!);
+ }
});
it("removes the key from sessionStorage", () => {
From bfdb76ed08f3cc6d7961705c2bb0eb63ec0510ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 15:34:31 +0100
Subject: [PATCH 8/9] fixes for suggestions from sonarqube
---
ui/jest.setup.ts | 4 ++--
ui/src/routes/HomeTestPrivacyPolicyPage.tsx | 12 +++++++-----
ui/src/routes/HomeTestTermsOfUsePage.tsx | 2 +-
.../EnterAddressManuallyPage.tsx | 11 ++++++-----
.../SelectDeliveryAddressPage.tsx | 17 +++++++++--------
ui/src/state/NavigationContext.tsx | 4 ++--
ui/src/state/PostcodeLookupContext.tsx | 11 ++++++-----
7 files changed, 33 insertions(+), 28 deletions(-)
diff --git a/ui/jest.setup.ts b/ui/jest.setup.ts
index d53820468..fac89734e 100644
--- a/ui/jest.setup.ts
+++ b/ui/jest.setup.ts
@@ -29,13 +29,13 @@ const createStorageMock = (): Storage => {
const sessionStorageMock = createStorageMock();
-Object.defineProperty(window, "sessionStorage", {
+Object.defineProperty(globalThis, "sessionStorage", {
value: sessionStorageMock,
configurable: true,
});
beforeEach(() => {
- window.sessionStorage.clear();
+ globalThis.sessionStorage.clear();
});
globalThis.TextEncoder = TextEncoder;
diff --git a/ui/src/routes/HomeTestPrivacyPolicyPage.tsx b/ui/src/routes/HomeTestPrivacyPolicyPage.tsx
index 71381ab40..8c2ac8711 100644
--- a/ui/src/routes/HomeTestPrivacyPolicyPage.tsx
+++ b/ui/src/routes/HomeTestPrivacyPolicyPage.tsx
@@ -1,17 +1,19 @@
"use client";
-import PageLayout from "@/layouts/PageLayout";
-import { useContent } from "@/hooks";
-import { useNavigate } from "react-router-dom";
-import { renderTextWithLinks, cleanListItems, getListClass } from "@/utils/renderTextWithLinks";
import "@/styles/lists.css";
+import { useNavigate } from "react-router-dom";
+
+import { useContent } from "@/hooks";
+import PageLayout from "@/layouts/PageLayout";
+import { cleanListItems, getListClass, renderTextWithLinks } from "@/utils/renderTextWithLinks";
+
export default function HomeTestPrivacyPolicyPage() {
const navigate = useNavigate();
const { "home-test-privacy-policy": content } = useContent();
const renderHeading = (text: string) => {
- const numberMatch = text.match(/^(\d+\.\s+)/);
+ const numberMatch = /^(\d+\.\s+)/.exec(text);
if (numberMatch) {
return (
<>
diff --git a/ui/src/routes/HomeTestTermsOfUsePage.tsx b/ui/src/routes/HomeTestTermsOfUsePage.tsx
index c5ad9a154..bddb5ca33 100644
--- a/ui/src/routes/HomeTestTermsOfUsePage.tsx
+++ b/ui/src/routes/HomeTestTermsOfUsePage.tsx
@@ -33,7 +33,7 @@ const renderTableRow = (row: string[], rowIdx: number) => (
*/
const renderParagraphs = (paragraphs: string[]) =>
paragraphs.map((paragraph, index) => {
- const numberMatch = paragraph.match(/^(\d+\.\d+\.?\s+)/);
+ const numberMatch = /^(\d+\.\d+\.?\s+)/.exec(paragraph);
if (numberMatch) {
const number = numberMatch[1];
const rest = paragraph.slice(number.length);
diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/EnterAddressManuallyPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/EnterAddressManuallyPage.tsx
index a91d0c556..7062effbb 100644
--- a/ui/src/routes/get-self-test-kit-for-HIV-journey/EnterAddressManuallyPage.tsx
+++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/EnterAddressManuallyPage.tsx
@@ -1,14 +1,15 @@
"use client";
import { Button, ErrorSummary, TextInput } from "nhsuk-react-components";
-import { useAuth, useCreateOrderContext, useJourneyNavigationContext } from "@/state";
+import { useState } from "react";
+
+import type { ValidationMessages } from "@/content";
+import { useContent } from "@/hooks";
import FormPageLayout from "@/layouts/FormPageLayout";
import { JourneyStepNames } from "@/lib/models/route-paths";
-import type { ValidationMessages } from "@/content";
import laLookupService from "@/lib/services/la-lookup-service";
-import { useContent } from "@/hooks";
-import { useState } from "react";
import { isUnder18 } from "@/lib/utils/is-under-18";
+import { useAuth, useCreateOrderContext, useJourneyNavigationContext } from "@/state";
const POSTCODE_REGEX = /^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/i;
const MAX_POSTCODE_LENGTH = 8;
@@ -211,7 +212,7 @@ export default function EnterAddressManuallyPage() {
try {
const postcode = postcodeValidation.value;
const laResponse = await laLookupService.getByPostcode(postcode);
- if (!laResponse || !laResponse.suppliers || laResponse.suppliers.length === 0) {
+ if (!laResponse?.suppliers?.length) {
updateOrderAnswers({ postcodeSearch: postcode });
goToStep(JourneyStepNames.KitNotAvailableInArea);
return;
diff --git a/ui/src/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage.tsx b/ui/src/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage.tsx
index d166dc571..3792574cf 100644
--- a/ui/src/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage.tsx
+++ b/ui/src/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage.tsx
@@ -1,5 +1,13 @@
"use client";
+import { Button, ErrorSummary, Radios } from "nhsuk-react-components";
+import { useState } from "react";
+
+import { useAsyncErrorHandler, useContent } from "@/hooks";
+import FormPageLayout from "@/layouts/FormPageLayout";
+import { JourneyStepNames } from "@/lib/models/route-paths";
+import laLookupService from "@/lib/services/la-lookup-service";
+import { isUnder18 } from "@/lib/utils/is-under-18";
import {
AddressResult,
useAuth,
@@ -7,13 +15,6 @@ import {
useJourneyNavigationContext,
usePostcodeLookup,
} from "@/state";
-import FormPageLayout from "@/layouts/FormPageLayout";
-import { useContent, useAsyncErrorHandler } from "@/hooks";
-import { Radios, Button, ErrorSummary } from "nhsuk-react-components";
-import { JourneyStepNames } from "@/lib/models/route-paths";
-import laLookupService from "@/lib/services/la-lookup-service";
-import { useState } from "react";
-import { isUnder18 } from "@/lib/utils/is-under-18";
export default function SelectDeliveryAddressPage() {
const { goToStep, goBack, stepHistory, returnToStep, setReturnToStep } =
@@ -47,7 +48,7 @@ export default function SelectDeliveryAddressPage() {
const postcode = selected.postcode;
const laResponse = await laLookupService.getByPostcode(postcode);
- if (!laResponse || !laResponse.suppliers || laResponse.suppliers.length === 0) {
+ if (!laResponse?.suppliers?.length) {
updateOrderAnswers({ postcodeSearch: postcode });
goToStep(JourneyStepNames.KitNotAvailableInArea);
return;
diff --git a/ui/src/state/NavigationContext.tsx b/ui/src/state/NavigationContext.tsx
index 41a64aff4..46d7d4970 100644
--- a/ui/src/state/NavigationContext.tsx
+++ b/ui/src/state/NavigationContext.tsx
@@ -73,7 +73,7 @@ export function JourneyNavigationProvider({ children }: Readonly<{ children: Rea
const stepHistory =
persistedState.stepHistory.length > 0 ? persistedState.stepHistory : [currentStep];
- const lastStep = stepHistory[stepHistory.length - 1];
+ const lastStep = stepHistory.at(-1);
return {
stepHistory: lastStep === currentStep ? stepHistory : [...stepHistory, currentStep],
@@ -82,7 +82,7 @@ export function JourneyNavigationProvider({ children }: Readonly<{ children: Rea
});
const stepHistory = useMemo(() => {
- const lastStep = navigation.stepHistory[navigation.stepHistory.length - 1];
+ const lastStep = navigation.stepHistory.at(-1);
return lastStep === currentStep
? navigation.stepHistory
diff --git a/ui/src/state/PostcodeLookupContext.tsx b/ui/src/state/PostcodeLookupContext.tsx
index 82d8cc1e2..dd098e846 100644
--- a/ui/src/state/PostcodeLookupContext.tsx
+++ b/ui/src/state/PostcodeLookupContext.tsx
@@ -6,9 +6,12 @@ import React, {
useEffect,
useState,
} from "react";
+
import sessionService from "@/lib/services/session-service";
import { backendUrl } from "@/settings";
+export type LookupResultsStatus = "idle" | "found" | "not_found" | "error";
+
export interface AddressResult {
id: string;
line1: string;
@@ -25,7 +28,7 @@ export interface PostcodeLookupContextType {
addresses: AddressResult[];
selectedAddress: AddressResult | null;
isLoading: boolean;
- lookupResultsStatus: "idle" | "found" | "not_found" | "error";
+ lookupResultsStatus: LookupResultsStatus;
error: string | null;
lookupPostcode: (postcode: string) => Promise;
setSelectedAddress: (address: AddressResult | null) => void;
@@ -44,7 +47,7 @@ interface PersistedPostcodeLookupState {
postcode: string;
addresses: AddressResult[];
selectedAddress: AddressResult | null;
- lookupResultsStatus: "idle" | "found" | "not_found" | "error";
+ lookupResultsStatus: LookupResultsStatus;
error: string | null;
}
@@ -61,9 +64,7 @@ export const PostcodeLookupProvider: React.FC = ({
const [addresses, setAddresses] = useState([]);
const [selectedAddress, setSelectedAddress] = useState(null);
const [isLoading, setIsLoading] = useState(false);
- const [lookupResultsStatus, setLookupResultsStatus] = useState<
- "idle" | "found" | "not_found" | "error"
- >("idle");
+ const [lookupResultsStatus, setLookupResultsStatus] = useState("idle");
const [error, setError] = useState(null);
const [hasHydrated, setHasHydrated] = useState(false);
From 373a8cc7860a6125a2f499235e877260e8684c7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Rejterada?=
Date: Thu, 26 Mar 2026 15:44:47 +0100
Subject: [PATCH 9/9] use globalThis in tests
---
.../lib/services/session-service.test.ts | 20 +++++++++----------
.../services/session-storage-service.test.ts | 14 ++++++-------
ui/src/__tests__/state/AuthContext.test.tsx | 10 +++++-----
ui/src/__tests__/state/OrderContext.test.tsx | 16 ++++++++-------
.../state/PostcodeLookupContext.test.tsx | 18 ++++++++++-------
5 files changed, 42 insertions(+), 36 deletions(-)
diff --git a/ui/src/__tests__/lib/services/session-service.test.ts b/ui/src/__tests__/lib/services/session-service.test.ts
index ffbd3af09..d38b29f73 100644
--- a/ui/src/__tests__/lib/services/session-service.test.ts
+++ b/ui/src/__tests__/lib/services/session-service.test.ts
@@ -2,7 +2,7 @@ import sessionService, { SESSION_STORAGE_KEYS } from "@/lib/services/session-ser
describe("SessionService", () => {
beforeEach(() => {
- window.sessionStorage.clear();
+ globalThis.sessionStorage.clear();
});
describe("auth user", () => {
@@ -18,21 +18,21 @@ describe("SessionService", () => {
sessionService.dehydrateAuthUser(user);
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.authUser)).toBe(
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.authUser)).toBe(
JSON.stringify(user),
);
expect(sessionService.rehydrateAuthUser()).toEqual(user);
});
it("clears auth user when null is provided", () => {
- window.sessionStorage.setItem(
+ globalThis.sessionStorage.setItem(
SESSION_STORAGE_KEYS.authUser,
JSON.stringify({ sub: "existing" }),
);
sessionService.dehydrateAuthUser(null);
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.authUser)).toBeNull();
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.authUser)).toBeNull();
});
});
@@ -54,13 +54,13 @@ describe("SessionService", () => {
sessionService.dehydrateJourneyNavigation(navigation);
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.journeyNavigation)).toBe(
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.journeyNavigation)).toBe(
JSON.stringify(navigation),
);
sessionService.clearJourneyNavigation();
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.journeyNavigation)).toBeNull();
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.journeyNavigation)).toBeNull();
});
});
@@ -79,13 +79,13 @@ describe("SessionService", () => {
sessionService.dehydrateCreateOrderAnswers(answers);
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).toBe(
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).toBe(
JSON.stringify(answers),
);
sessionService.clearCreateOrderAnswers();
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).toBeNull();
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).toBeNull();
});
});
@@ -118,13 +118,13 @@ describe("SessionService", () => {
sessionService.dehydratePostcodeLookup(postcodeLookup);
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup)).toBe(
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup)).toBe(
JSON.stringify(postcodeLookup),
);
sessionService.clearPostcodeLookup();
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup)).toBeNull();
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup)).toBeNull();
});
});
});
diff --git a/ui/src/__tests__/lib/services/session-storage-service.test.ts b/ui/src/__tests__/lib/services/session-storage-service.test.ts
index 94e583e01..60bd9f8b3 100644
--- a/ui/src/__tests__/lib/services/session-storage-service.test.ts
+++ b/ui/src/__tests__/lib/services/session-storage-service.test.ts
@@ -2,7 +2,7 @@ import sessionStorageService from "@/lib/services/session-storage-service";
describe("SessionStorageService", () => {
beforeEach(() => {
- window.sessionStorage.clear();
+ globalThis.sessionStorage.clear();
});
describe("rehydrate", () => {
@@ -21,16 +21,16 @@ describe("SessionStorageService", () => {
});
it("returns parsed value when key exists with valid JSON", () => {
- window.sessionStorage.setItem("my-key", JSON.stringify({ foo: "bar" }));
+ globalThis.sessionStorage.setItem("my-key", JSON.stringify({ foo: "bar" }));
expect(sessionStorageService.rehydrate("my-key", null)).toEqual({ foo: "bar" });
});
it("returns fallback and removes key when stored value is invalid JSON", () => {
- window.sessionStorage.setItem("bad-key", "not-valid-json{");
+ globalThis.sessionStorage.setItem("bad-key", "not-valid-json{");
expect(sessionStorageService.rehydrate("bad-key", "default")).toBe("default");
- expect(window.sessionStorage.getItem("bad-key")).toBeNull();
+ expect(globalThis.sessionStorage.getItem("bad-key")).toBeNull();
});
});
@@ -48,7 +48,7 @@ describe("SessionStorageService", () => {
it("stores value as JSON string", () => {
sessionStorageService.dehydrate("my-key", { a: 1, b: true });
- expect(window.sessionStorage.getItem("my-key")).toBe(JSON.stringify({ a: 1, b: true }));
+ expect(globalThis.sessionStorage.getItem("my-key")).toBe(JSON.stringify({ a: 1, b: true }));
});
});
@@ -64,11 +64,11 @@ describe("SessionStorageService", () => {
});
it("removes the key from sessionStorage", () => {
- window.sessionStorage.setItem("to-remove", "value");
+ globalThis.sessionStorage.setItem("to-remove", "value");
sessionStorageService.remove("to-remove");
- expect(window.sessionStorage.getItem("to-remove")).toBeNull();
+ expect(globalThis.sessionStorage.getItem("to-remove")).toBeNull();
});
});
});
diff --git a/ui/src/__tests__/state/AuthContext.test.tsx b/ui/src/__tests__/state/AuthContext.test.tsx
index 071de1443..2fc22b09e 100644
--- a/ui/src/__tests__/state/AuthContext.test.tsx
+++ b/ui/src/__tests__/state/AuthContext.test.tsx
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
+import { act, render, renderHook, screen } from "@testing-library/react";
import { AuthProvider, AuthUser, useAuth } from "@/state";
-import { act, render, renderHook, screen } from "@testing-library/react";
const AUTH_STORAGE_KEY = "hometest:auth:user";
@@ -18,7 +18,7 @@ describe("AuthContext", () => {
};
beforeEach(() => {
- window.sessionStorage.clear();
+ globalThis.sessionStorage.clear();
});
describe("AuthProvider", () => {
@@ -42,7 +42,7 @@ describe("AuthContext", () => {
});
it("rehydrates user state from session storage", () => {
- window.sessionStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(mockUser));
+ globalThis.sessionStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(mockUser));
const { result } = renderHook(() => useAuth(), {
wrapper: AuthProvider,
@@ -94,7 +94,7 @@ describe("AuthContext", () => {
expect(result.current.user).toEqual(mockUser);
expect(result.current.user?.nhsNumber).toBe("9876543210");
expect(result.current.user?.birthdate).toBe("1985-05-15");
- expect(window.sessionStorage.getItem(AUTH_STORAGE_KEY)).toBe(JSON.stringify(mockUser));
+ expect(globalThis.sessionStorage.getItem(AUTH_STORAGE_KEY)).toBe(JSON.stringify(mockUser));
});
it("clears user state when setUser is called with null", () => {
@@ -115,7 +115,7 @@ describe("AuthContext", () => {
});
expect(result.current.user).toBeNull();
- expect(window.sessionStorage.getItem(AUTH_STORAGE_KEY)).toBeNull();
+ expect(globalThis.sessionStorage.getItem(AUTH_STORAGE_KEY)).toBeNull();
});
it("updates user correctly when setUser is called multiple times", () => {
diff --git a/ui/src/__tests__/state/OrderContext.test.tsx b/ui/src/__tests__/state/OrderContext.test.tsx
index b40b89034..47cb5492a 100644
--- a/ui/src/__tests__/state/OrderContext.test.tsx
+++ b/ui/src/__tests__/state/OrderContext.test.tsx
@@ -1,12 +1,12 @@
import "@testing-library/jest-dom";
+import { act, renderHook } from "@testing-library/react";
-import { CreateOrderProvider, useCreateOrderContext } from "@/state/OrderContext";
import { SESSION_STORAGE_KEYS } from "@/lib/services/session-service";
-import { act, renderHook } from "@testing-library/react";
+import { CreateOrderProvider, useCreateOrderContext } from "@/state/OrderContext";
describe("OrderContext", () => {
beforeEach(() => {
- window.sessionStorage.clear();
+ globalThis.sessionStorage.clear();
});
describe("CreateOrderProvider", () => {
@@ -24,7 +24,7 @@ describe("OrderContext", () => {
mobileNumber: "07700900123",
};
- window.sessionStorage.setItem(
+ globalThis.sessionStorage.setItem(
SESSION_STORAGE_KEYS.createOrderAnswers,
JSON.stringify(persistedAnswers),
);
@@ -52,7 +52,7 @@ describe("OrderContext", () => {
postcodeSearch: "SW1A 1AA",
mobileNumber: "07700900123",
});
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).toBe(
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).toBe(
JSON.stringify({
postcodeSearch: "SW1A 1AA",
mobileNumber: "07700900123",
@@ -69,14 +69,16 @@ describe("OrderContext", () => {
result.current.updateOrderAnswers({ postcodeSearch: "SW1A 1AA" });
});
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).not.toBeNull();
+ expect(
+ globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers),
+ ).not.toBeNull();
act(() => {
result.current.reset();
});
expect(result.current.orderAnswers).toEqual({});
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).toBeNull();
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.createOrderAnswers)).toBeNull();
});
});
diff --git a/ui/src/__tests__/state/PostcodeLookupContext.test.tsx b/ui/src/__tests__/state/PostcodeLookupContext.test.tsx
index f8bb94723..4b17aca2c 100644
--- a/ui/src/__tests__/state/PostcodeLookupContext.test.tsx
+++ b/ui/src/__tests__/state/PostcodeLookupContext.test.tsx
@@ -1,8 +1,8 @@
import "@testing-library/jest-dom";
+import { act, renderHook, waitFor } from "@testing-library/react";
-import { PostcodeLookupProvider, usePostcodeLookup } from "@/state/PostcodeLookupContext";
import { SESSION_STORAGE_KEYS } from "@/lib/services/session-service";
-import { act, renderHook, waitFor } from "@testing-library/react";
+import { PostcodeLookupProvider, usePostcodeLookup } from "@/state/PostcodeLookupContext";
jest.mock("@/settings", () => ({ backendUrl: "http://mock-backend" }));
@@ -11,7 +11,7 @@ globalThis.fetch = mockFetch as typeof fetch;
describe("PostcodeLookupContext", () => {
beforeEach(() => {
- window.sessionStorage.clear();
+ globalThis.sessionStorage.clear();
jest.clearAllMocks();
});
@@ -51,7 +51,7 @@ describe("PostcodeLookupContext", () => {
error: null,
};
- window.sessionStorage.setItem(
+ globalThis.sessionStorage.setItem(
SESSION_STORAGE_KEYS.postcodeLookup,
JSON.stringify(persistedState),
);
@@ -98,7 +98,9 @@ describe("PostcodeLookupContext", () => {
expect(result.current.error).toBeNull();
await waitFor(() => {
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup)).not.toBeNull();
+ expect(
+ globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup),
+ ).not.toBeNull();
});
});
@@ -164,7 +166,9 @@ describe("PostcodeLookupContext", () => {
});
await waitFor(() => {
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup)).not.toBeNull();
+ expect(
+ globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup),
+ ).not.toBeNull();
});
act(() => {
@@ -176,7 +180,7 @@ describe("PostcodeLookupContext", () => {
expect(result.current.selectedAddress).toBeNull();
expect(result.current.lookupResultsStatus).toBe("idle");
expect(result.current.error).toBeNull();
- expect(window.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup)).toBeNull();
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.postcodeLookup)).toBeNull();
});
});