Skip to content
6 changes: 4 additions & 2 deletions ui/src/__tests__/components/FormBackLink.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import "@testing-library/jest-dom";

import { fireEvent, render, screen } from "@testing-library/react";

import { FormBackLink } from "@/components/FormBackLink";
Expand All @@ -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", () => ({
Expand Down Expand Up @@ -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();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jest.mock("@/hooks", () => ({

const goBackMock = jest.fn();
const goToStepMock = jest.fn();
const resetNavigationMock = jest.fn();

const TestProvider = ({
children,
Expand Down Expand Up @@ -77,6 +78,7 @@ const TestProvider = ({
goToStep: goToStepMock,
canGoBack: () => history.length > 1,
clearHistory: jest.fn(),
resetNavigation: resetNavigationMock,
setReturnToStep: jest.fn(),
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
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,
CreateOrderProvider,
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");
Expand All @@ -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,
}),
};
});

Expand Down Expand Up @@ -79,10 +107,10 @@ function StateSeeder({
function AuthSeeder({
children,
user = defaultAuthUser,
}: {
}: Readonly<{
children: React.ReactNode;
user?: AuthUser;
}) {
}>) {
const { setUser } = useAuth();

useEffect(() => {
Expand Down Expand Up @@ -139,6 +167,7 @@ describe("CheckYourAnswersPage", () => {

beforeEach(() => {
jest.clearAllMocks();
mockNavigationType = "PUSH";
});

describe("Component Rendering", () => {
Expand Down Expand Up @@ -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(
<>
<CheckYourAnswersPage />
<OrderReferenceObserver />
</>,
{
wrapper: (props) => (
<TestWrapper
orderData={{
...defaultOrderData,
orderReferenceNumber: 123,
}}
{...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(<CheckYourAnswersPage />, { wrapper: TestWrapper });

Expand Down
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -21,6 +22,7 @@ jest.mock("@/state", () => {
goBack: mockGoBack,
canGoBack: () => false,
clearHistory: jest.fn(),
resetNavigation: mockResetNavigation,
setReturnToStep: mockSetReturnToStep,
}),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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

Expand All @@ -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;
Expand All @@ -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(),
Expand Down
91 changes: 91 additions & 0 deletions ui/src/__tests__/state/NavigationContext.test.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<MemoryRouter
initialEntries={[`${RoutePath.GetSelfTestKitPage}/${JourneyStepNames.CheckYourAnswers}`]}
>
<JourneyNavigationProvider>{children}</JourneyNavigationProvider>
</MemoryRouter>
);
}

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");
});
});
});
5 changes: 2 additions & 3 deletions ui/src/app.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -75,7 +74,7 @@ const router = createBrowserRouter([
children: [
{
path: RoutePath.HomePage,
element: <HomePage />,
element: <Navigate to={RoutePath.GetSelfTestKitPage} replace />,
},
{
path: RoutePath.OrderTrackingPage,
Expand Down
13 changes: 0 additions & 13 deletions ui/src/routes/HomePage.tsx

This file was deleted.

Loading
Loading