diff --git a/ui/src/__tests__/components/FormBackLink.test.tsx b/ui/src/__tests__/components/FormBackLink.test.tsx
index 4e3d6917..2a78c174 100644
--- a/ui/src/__tests__/components/FormBackLink.test.tsx
+++ b/ui/src/__tests__/components/FormBackLink.test.tsx
@@ -1,5 +1,4 @@
import "@testing-library/jest-dom";
-
import { fireEvent, render, screen } from "@testing-library/react";
import { FormBackLink } from "@/components/FormBackLink";
@@ -12,6 +11,9 @@ const mockNavigationContext = {
goBack: jest.fn(),
canGoBack: jest.fn(() => true),
clearHistory: jest.fn(),
+ resetNavigation: jest.fn(),
+ returnToStep: null,
+ setReturnToStep: jest.fn(),
};
jest.mock("@/state", () => ({
@@ -109,7 +111,7 @@ describe("FormBackLink", () => {
// BackLink should still be rendered but with empty text
// Check for the back link container class
- expect(document.querySelector('.nhsuk-back-link')).toBeInTheDocument();
+ expect(document.querySelector(".nhsuk-back-link")).toBeInTheDocument();
});
});
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 fce5955b..1c542d63 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
@@ -46,6 +46,7 @@ jest.mock("@/hooks", () => ({
const goBackMock = jest.fn();
const goToStepMock = jest.fn();
+const resetNavigationMock = jest.fn();
const TestProvider = ({
children,
@@ -77,6 +78,7 @@ const TestProvider = ({
goToStep: goToStepMock,
canGoBack: () => history.length > 1,
clearHistory: jest.fn(),
+ resetNavigation: resetNavigationMock,
setReturnToStep: jest.fn(),
}}
>
diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage.test.tsx
index 1f3c2cfd..fe9e378e 100644
--- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage.test.tsx
+++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage.test.tsx
@@ -1,4 +1,11 @@
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { useEffect } from "react";
+import { MemoryRouter } from "react-router-dom";
+
+import { RoutePath } from "@/lib/models/route-paths";
+import orderService from "@/lib/services/order-service";
import { TestErrorBoundary } from "@/lib/test-utils/TestErrorBoundary";
+import CheckYourAnswersPage from "@/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage";
import {
AuthProvider,
AuthUser,
@@ -6,16 +13,25 @@ import {
useAuth,
useCreateOrderContext,
} from "@/state";
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
-import CheckYourAnswersPage from "@/routes/get-self-test-kit-for-HIV-journey/CheckYourAnswersPage";
-import { MemoryRouter } from "react-router-dom";
-import orderService from "@/lib/services/order-service";
-import { useEffect } from "react";
+const mockNavigate = jest.fn();
+const mockClearAddresses = jest.fn();
+let mockNavigationType = "PUSH";
+
+jest.mock("react-router-dom", () => {
+ const actual = jest.requireActual("react-router-dom");
+
+ return {
+ ...actual,
+ useNavigate: () => mockNavigate,
+ useNavigationType: () => mockNavigationType,
+ };
+});
const mockGoToStep = jest.fn();
const mockSetReturnToStep = jest.fn();
const mockGoBack = jest.fn();
+const mockResetNavigation = jest.fn();
jest.mock("@/state", () => {
const actual = jest.requireActual("@/state");
@@ -29,8 +45,20 @@ jest.mock("@/state", () => {
goBack: mockGoBack,
canGoBack: () => true,
clearHistory: jest.fn(),
+ resetNavigation: mockResetNavigation,
setReturnToStep: mockSetReturnToStep,
}),
+ usePostcodeLookup: () => ({
+ postcode: "",
+ addresses: [],
+ selectedAddress: null,
+ isLoading: false,
+ lookupResultsStatus: "idle",
+ error: null,
+ lookupPostcode: jest.fn(),
+ setSelectedAddress: jest.fn(),
+ clearAddresses: mockClearAddresses,
+ }),
};
});
@@ -79,10 +107,10 @@ function StateSeeder({
function AuthSeeder({
children,
user = defaultAuthUser,
-}: {
+}: Readonly<{
children: React.ReactNode;
user?: AuthUser;
-}) {
+}>) {
const { setUser } = useAuth();
useEffect(() => {
@@ -139,6 +167,7 @@ describe("CheckYourAnswersPage", () => {
beforeEach(() => {
jest.clearAllMocks();
+ mockNavigationType = "PUSH";
});
describe("Component Rendering", () => {
@@ -308,6 +337,36 @@ describe("CheckYourAnswersPage", () => {
});
describe("Submit Order", () => {
+ it("clears state and redirects to start when revisited via browser back after submission", async () => {
+ mockNavigationType = "POP";
+
+ render(
+ <>
+
+
+ >,
+ {
+ wrapper: (props) => (
+
+ ),
+ },
+ );
+
+ await waitFor(() => {
+ expect(mockClearAddresses).toHaveBeenCalled();
+ expect(mockResetNavigation).toHaveBeenCalledWith(RoutePath.GetSelfTestKitPage, {
+ replace: true,
+ });
+ expect(screen.getByTestId("order-reference")).toHaveTextContent("");
+ });
+ });
+
it("shows error when submitting without consent", async () => {
render(, { wrapper: TestWrapper });
diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/OrderSubmittedPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/OrderSubmittedPage.test.tsx
index 31e0807f..c6fde76a 100644
--- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/OrderSubmittedPage.test.tsx
+++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/OrderSubmittedPage.test.tsx
@@ -1,13 +1,14 @@
-import { CreateOrderProvider, JourneyNavigationProvider, useCreateOrderContext } from "@/state";
import { render, screen } from "@testing-library/react";
-
+import { useEffect } from "react";
import { MemoryRouter } from "react-router-dom";
+
import OrderSubmittedPage from "@/routes/get-self-test-kit-for-HIV-journey/OrderSubmittedPage";
-import { useEffect } from "react";
+import { CreateOrderProvider, JourneyNavigationProvider, useCreateOrderContext } from "@/state";
const mockGoToStep = jest.fn();
const mockSetReturnToStep = jest.fn();
const mockGoBack = jest.fn();
+const mockResetNavigation = jest.fn();
jest.mock("@/state", () => {
const actual = jest.requireActual("@/state");
@@ -21,6 +22,7 @@ jest.mock("@/state", () => {
goBack: mockGoBack,
canGoBack: () => false,
clearHistory: jest.fn(),
+ resetNavigation: mockResetNavigation,
setReturnToStep: mockSetReturnToStep,
}),
};
diff --git a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage.test.tsx b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage.test.tsx
index 7466ba75..5faceb6a 100644
--- a/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage.test.tsx
+++ b/ui/src/__tests__/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage.test.tsx
@@ -1,4 +1,11 @@
import "@testing-library/jest-dom";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { useEffect } from "react";
+import { MemoryRouter } from "react-router-dom";
+
+import { JourneyStepNames } from "@/lib/models/route-paths";
+import laLookupService from "@/lib/services/la-lookup-service";
+import SelectDeliveryAddressPage from "@/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage";
import {
AuthContext,
AuthUser,
@@ -7,12 +14,6 @@ import {
PostcodeLookupProvider,
useCreateOrderContext,
} from "@/state";
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
-import { JourneyStepNames } from "@/lib/models/route-paths";
-import { MemoryRouter } from "react-router-dom";
-import SelectDeliveryAddressPage from "@/routes/get-self-test-kit-for-HIV-journey/SelectDeliveryAddressPage";
-import laLookupService from "@/lib/services/la-lookup-service";
-import { useEffect } from "react";
const FIXED_TODAY = new Date(2026, 2, 4); // March 4, 2026
@@ -26,6 +27,7 @@ const mockNavigationContext: {
goBack: jest.Mock;
canGoBack: jest.Mock;
clearHistory: jest.Mock;
+ resetNavigation: jest.Mock;
stepHistory: string[];
returnToStep: string | null;
setReturnToStep: jest.Mock;
@@ -35,6 +37,7 @@ const mockNavigationContext: {
goBack: jest.fn(),
canGoBack: jest.fn(() => true),
clearHistory: jest.fn(),
+ resetNavigation: jest.fn(),
stepHistory: ["enter-delivery-address", "select-delivery-address"],
returnToStep: null,
setReturnToStep: jest.fn(),
diff --git a/ui/src/__tests__/state/NavigationContext.test.tsx b/ui/src/__tests__/state/NavigationContext.test.tsx
new file mode 100644
index 00000000..b22cbf5b
--- /dev/null
+++ b/ui/src/__tests__/state/NavigationContext.test.tsx
@@ -0,0 +1,91 @@
+import "@testing-library/jest-dom";
+import { act, renderHook, waitFor } from "@testing-library/react";
+import { MemoryRouter } from "react-router-dom";
+
+import { JourneyStepNames, RoutePath } from "@/lib/models/route-paths";
+import { SESSION_STORAGE_KEYS } from "@/lib/services/session-service";
+import { JourneyNavigationProvider, useJourneyNavigationContext } from "@/state/NavigationContext";
+
+function TestWrapper({ children }: Readonly<{ children: React.ReactNode }>) {
+ return (
+
+ {children}
+
+ );
+}
+
+describe("NavigationContext", () => {
+ beforeEach(() => {
+ globalThis.sessionStorage.clear();
+ });
+
+ describe("JourneyNavigationProvider", () => {
+ it("provides the current step as the initial history", () => {
+ const { result } = renderHook(() => useJourneyNavigationContext(), {
+ wrapper: TestWrapper,
+ });
+
+ expect(result.current.currentStep).toBe(JourneyStepNames.CheckYourAnswers);
+ expect(result.current.stepHistory).toEqual([JourneyStepNames.CheckYourAnswers]);
+ expect(result.current.returnToStep).toBeNull();
+ });
+
+ it("rehydrates persisted navigation and appends the current step when needed", () => {
+ globalThis.sessionStorage.setItem(
+ SESSION_STORAGE_KEYS.journeyNavigation,
+ JSON.stringify({
+ stepHistory: [JourneyStepNames.EnterMobileNumber],
+ returnToStep: JourneyStepNames.CheckYourAnswers,
+ }),
+ );
+
+ const { result } = renderHook(() => useJourneyNavigationContext(), {
+ wrapper: TestWrapper,
+ });
+
+ expect(result.current.stepHistory).toEqual([
+ JourneyStepNames.EnterMobileNumber,
+ JourneyStepNames.CheckYourAnswers,
+ ]);
+ expect(result.current.returnToStep).toBe(JourneyStepNames.CheckYourAnswers);
+ });
+
+ it("resetNavigation clears in-memory navigation and storage for a redirect target", async () => {
+ const { result } = renderHook(() => useJourneyNavigationContext(), {
+ wrapper: TestWrapper,
+ });
+
+ act(() => {
+ result.current.setReturnToStep(JourneyStepNames.CheckYourAnswers);
+ });
+
+ await waitFor(() => {
+ expect(
+ globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.journeyNavigation),
+ ).not.toBeNull();
+ });
+
+ act(() => {
+ result.current.resetNavigation(RoutePath.GetSelfTestKitPage);
+ });
+
+ await waitFor(() => {
+ expect(result.current.currentStep).toBe(RoutePath.GetSelfTestKitPage);
+ });
+
+ expect(result.current.returnToStep).toBeNull();
+ expect(result.current.stepHistory).toEqual([RoutePath.GetSelfTestKitPage]);
+ expect(globalThis.sessionStorage.getItem(SESSION_STORAGE_KEYS.journeyNavigation)).toBeNull();
+ });
+ });
+
+ describe("useJourneyNavigationContext", () => {
+ it("throws when used outside provider", () => {
+ expect(() => {
+ renderHook(() => useJourneyNavigationContext());
+ }).toThrow("useJourneyNavigationContext must be used within a JourneyNavigationProvider");
+ });
+ });
+});
diff --git a/ui/src/app.tsx b/ui/src/app.tsx
index 957c65b0..ffe9e644 100644
--- a/ui/src/app.tsx
+++ b/ui/src/app.tsx
@@ -1,12 +1,11 @@
import * as React from "react";
-import { RouterProvider, createBrowserRouter } from "react-router-dom";
+import { Navigate, RouterProvider, createBrowserRouter } from "react-router-dom";
import ErrorRedirect from "./components/ErrorRedirect";
import JourneyLayout from "./layouts/JourneyLayout";
import MainLayout from "./layouts/MainLayout";
import { JourneyStepNames, RoutePath } from "./lib/models/route-paths";
import CallbackPage from "./routes/CallbackPage";
-import HomePage from "./routes/HomePage";
import HomeTestPrivacyPolicyPage from "./routes/HomeTestPrivacyPolicyPage";
import HomeTestTermsOfUsePage from "./routes/HomeTestTermsOfUsePage";
import LoginPage from "./routes/LoginPage";
@@ -75,7 +74,7 @@ const router = createBrowserRouter([
children: [
{
path: RoutePath.HomePage,
- element: ,
+ element: ,
},
{
path: RoutePath.OrderTrackingPage,
diff --git a/ui/src/routes/HomePage.tsx b/ui/src/routes/HomePage.tsx
deleted file mode 100644
index 37e4eb8f..00000000
--- a/ui/src/routes/HomePage.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { RoutePath } from "@/lib/models/route-paths";
-import { useEffect } from "react";
-import { useNavigate } from "react-router-dom";
-
-export default function HomePage() {
- const navigate = useNavigate();
-
- useEffect(() => {
- navigate(RoutePath.GetSelfTestKitPage);
- }, [navigate]);
-
- return
;
-}
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 71762a9b..62a5a7ac 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
@@ -1,16 +1,21 @@
"use client";
import { Button, Checkboxes, ErrorSummary, SummaryList } from "nhsuk-react-components";
-import React, { useState } from "react";
+import React, { useLayoutEffect, useState } from "react";
+import { useNavigationType } from "react-router-dom";
import { useAsyncErrorHandler, useContent } from "@/hooks";
import FormPageLayout from "@/layouts/FormPageLayout";
-import { JourneyStepNames } from "@/lib/models/route-paths";
+import { JourneyStepNames, RoutePath } from "@/lib/models/route-paths";
import orderService, { OrderServiceRequest } from "@/lib/services/order-service";
-import { useAuth, useCreateOrderContext, useJourneyNavigationContext } from "@/state";
+import {
+ useAuth,
+ useCreateOrderContext,
+ useJourneyNavigationContext,
+ usePostcodeLookup,
+} from "@/state";
// TODO: update to dynamically render supplier based on API (probably stored in state)
-// TODO: add order reference number to state when order is submitted (orderAnswers.orderReferenceNumber)
function formatAddress(address: {
addressLine1?: string;
@@ -39,10 +44,14 @@ function formatUserName(user?: { givenName: string; familyName: string } | null)
}
export default function CheckYourAnswersPage() {
- const { orderAnswers, updateOrderAnswers } = useCreateOrderContext();
- const { goToStep, goBack, stepHistory, setReturnToStep } = useJourneyNavigationContext();
+ const navigationType = useNavigationType();
+ const { orderAnswers, updateOrderAnswers, reset } = useCreateOrderContext();
+ const { goToStep, goBack, stepHistory, resetNavigation, setReturnToStep } =
+ useJourneyNavigationContext();
+ const { clearAddresses } = usePostcodeLookup();
const { user } = useAuth();
const { commonContent, "check-your-answers": content } = useContent();
+ const hasSubmittedOrder = orderAnswers.orderReferenceNumber != null;
const [consentChecked, setConsentChecked] = useState(
orderAnswers.consentCheckboxChecked ?? false,
@@ -51,6 +60,16 @@ export default function CheckYourAnswersPage() {
const supplierName = orderAnswers.supplier?.[0]?.name || "[Supplier]";
+ useLayoutEffect(() => {
+ if (!hasSubmittedOrder || navigationType !== "POP") {
+ return;
+ }
+
+ reset();
+ clearAddresses();
+ resetNavigation(RoutePath.GetSelfTestKitPage, { replace: true });
+ }, [clearAddresses, hasSubmittedOrder, navigationType, reset, resetNavigation]);
+
const handleChangeClick = (field: "address" | "mobile" | "comfort") => {
setReturnToStep(JourneyStepNames.CheckYourAnswers);
@@ -140,6 +159,10 @@ export default function CheckYourAnswersPage() {
? formatAddress(orderAnswers.deliveryAddress)
: [];
+ if (hasSubmittedOrder && navigationType === "POP") {
+ return null;
+ }
+
return (
void;
canGoBack: () => boolean;
clearHistory: () => void;
+ resetNavigation: (step?: Step, options?: { replace?: boolean }) => void;
setReturnToStep: (step: Step | null) => void;
}
@@ -134,6 +135,22 @@ export function JourneyNavigationProvider({ children }: Readonly<{ children: Rea
}));
}, [currentStep]);
+ const resetNavigation = useCallback(
+ (step: Step = currentStep, options?: { replace?: boolean }) => {
+ setNavigation({
+ stepHistory: [step],
+ returnToStep: null,
+ });
+
+ sessionService.clearJourneyNavigation();
+
+ if (step !== currentStep || options?.replace === true) {
+ navigate(getPathForStep(step), { replace: options?.replace });
+ }
+ },
+ [currentStep, navigate],
+ );
+
const setReturnToStep = useCallback((step: Step | null) => {
setNavigation((previousNavigation) => ({
...previousNavigation,
@@ -150,6 +167,7 @@ export function JourneyNavigationProvider({ children }: Readonly<{ children: Rea
goBack,
canGoBack,
clearHistory,
+ resetNavigation,
setReturnToStep,
}),
[
@@ -160,6 +178,7 @@ export function JourneyNavigationProvider({ children }: Readonly<{ children: Rea
goBack,
canGoBack,
clearHistory,
+ resetNavigation,
setReturnToStep,
],
);