Skip to content

Commit 85aa178

Browse files
bencegadanyi1-nhsjonathanwelch1-nhsMatthewPopat-NHS
authored
Update: [AEA-5328] - Adds back go back button functionality (#1315)
## Summary ✨ New Feature ### Details Added "go back" link functionality to fix an accessibility audit issue and make navigation more consistent across the Prescription Tracker UI #### What’s new: - `NavigationProvider` – keeps track of history + search params - `useBackNavigation` hook – centralised back nav logic - `EpsBackLink` component – reusable, accessible back link - Smart routing: a. behaves like normal browser back in standard flows b. preserves search context (e.g. list → results → patient search) c. treats search list tabs as a single page (avoids polluting history) d. context-aware: if navigating from a future-dated list → details, “Back” takes you to the same list, not current prescriptions #### Other improvements: - Keeps original search params when going back - Better breadcrumb behaviour across entry points (basic details, NHS number, prescription ID) - WCAG fix: back link now sits outside `<main>` - changes `"Go Back"` strings to `"Back"` --------- Signed-off-by: Bence Gadanyi <[email protected]> Co-authored-by: Jonno <[email protected]> Co-authored-by: MatthewPopat-NHS <[email protected]> Co-authored-by: jonathanwelch1-nhs <[email protected]>
1 parent 7df5f8f commit 85aa178

38 files changed

+2052
-427
lines changed

packages/cpt-ui/__tests__/BasicDetailsSearch.test.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {STRINGS} from "@/constants/ui-strings/BasicDetailsSearchStrings"
2121
import {FRONTEND_PATHS} from "@/constants/environment"
2222
import {AuthContext, AuthContextType} from "@/context/AuthProvider"
2323
import {SearchContext, SearchProviderContextType} from "@/context/SearchProvider"
24+
import {NavigationProvider} from "@/context/NavigationProvider"
2425

2526
jest.mock("react-router-dom", () => {
2627
const actual = jest.requireActual("react-router-dom")
@@ -68,10 +69,12 @@ const mockSetLastName = jest.fn()
6869
const mockSetDobDay = jest.fn()
6970
const mockSetDobMonth = jest.fn()
7071
const mockSetDobYear = jest.fn()
71-
const mockSetPostcode =jest.fn()
72+
const mockSetPostcode = jest.fn()
7273
const mockSetNhsNumber = jest.fn()
7374
const mockGetAllSearchParameters = jest.fn()
7475
const mockSetAllSearchParameters = jest.fn()
76+
const mockSetSearchType = jest.fn()
77+
7578
const defaultSearchState: SearchProviderContextType = {
7679
prescriptionId: undefined,
7780
issueNumber: undefined,
@@ -82,6 +85,7 @@ const defaultSearchState: SearchProviderContextType = {
8285
dobYear: undefined,
8386
postcode: undefined,
8487
nhsNumber: undefined,
88+
searchType: undefined,
8589
clearSearchParameters: mockClearSearchParameters,
8690
setPrescriptionId: mockSetPrescriptionId,
8791
setIssueNumber: mockSetIssueNumber,
@@ -93,7 +97,8 @@ const defaultSearchState: SearchProviderContextType = {
9397
setPostcode: mockSetPostcode,
9498
setNhsNumber: mockSetNhsNumber,
9599
getAllSearchParameters: mockGetAllSearchParameters,
96-
setAllSearchParameters: mockSetAllSearchParameters
100+
setAllSearchParameters: mockSetAllSearchParameters,
101+
setSearchType: mockSetSearchType
97102
}
98103

99104
const LocationDisplay = () => {
@@ -106,10 +111,12 @@ const renderWithRouter = (ui: React.ReactElement, searchState: SearchProviderCon
106111
<AuthContext.Provider value={signedInAuthState}>
107112
<SearchContext.Provider value={searchState}>
108113
<MemoryRouter initialEntries={["/search"]}>
109-
<Routes>
110-
<Route path="/search" element={ui} />
111-
<Route path="*" element={<LocationDisplay />} />
112-
</Routes>
114+
<NavigationProvider>
115+
<Routes>
116+
<Route path="/search" element={ui} />
117+
<Route path="*" element={<LocationDisplay />} />
118+
</Routes>
119+
</NavigationProvider>
113120
</MemoryRouter>
114121
</SearchContext.Provider>
115122
</AuthContext.Provider>

packages/cpt-ui/__tests__/BasicDetailsSearchResultsPage.test.tsx

Lines changed: 124 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,37 @@ import {SearchResultsPageStrings} from "@/constants/ui-strings/BasicDetailsSearc
1111
import http from "@/helpers/axios"
1212
import {AuthContext, type AuthContextType} from "@/context/AuthProvider"
1313
import {SearchContext, SearchProviderContextType} from "@/context/SearchProvider"
14+
import {NavigationProvider} from "@/context/NavigationProvider"
1415
import {AxiosError, AxiosHeaders} from "axios"
1516

1617
// Mock the axios module
1718
jest.mock("@/helpers/axios")
1819
const mockAxiosGet = http.get as jest.MockedFunction<typeof http.get>
1920

21+
const mockGetRelevantSearchParameters = jest.fn()
22+
const mockGetBackPath = jest.fn()
23+
const mockGoBack = jest.fn()
24+
const mockNavigationContext = {
25+
pushNavigation: jest.fn(),
26+
goBack: mockGoBack,
27+
getBackPath: mockGetBackPath,
28+
clearNavigation: jest.fn(),
29+
getCurrentEntry: jest.fn(),
30+
getNavigationStack: jest.fn(),
31+
canGoBack: jest.fn(),
32+
setOriginalSearchPage: jest.fn(),
33+
getOriginalSearchPage: jest.fn(),
34+
captureOriginalSearchParameters: jest.fn(),
35+
getOriginalSearchParameters: jest.fn(),
36+
getRelevantSearchParameters: mockGetRelevantSearchParameters,
37+
startNewNavigationSession: jest.fn()
38+
}
39+
40+
jest.mock("@/context/NavigationProvider", () => ({
41+
...jest.requireActual("@/context/NavigationProvider"),
42+
useNavigationContext: () => mockNavigationContext
43+
}))
44+
2045
const mockAuthContext: AuthContextType = {
2146
error: null,
2247
user: null,
@@ -49,7 +74,7 @@ const mockSetLastName = jest.fn()
4974
const mockSetDobDay = jest.fn()
5075
const mockSetDobMonth = jest.fn()
5176
const mockSetDobYear = jest.fn()
52-
const mockSetPostcode =jest.fn()
77+
const mockSetPostcode = jest.fn()
5378
const mockSetNhsNumber = jest.fn()
5479
const mockGetAllSearchParameters = jest.fn()
5580
const mockSetAllSearchParameters = jest.fn()
@@ -63,6 +88,7 @@ const defaultSearchState: SearchProviderContextType = {
6388
dobYear: undefined,
6489
postcode: undefined,
6590
nhsNumber: undefined,
91+
searchType: undefined,
6692
clearSearchParameters: mockClearSearchParameters,
6793
setPrescriptionId: mockSetPrescriptionId,
6894
setIssueNumber: mockSetIssueNumber,
@@ -73,6 +99,7 @@ const defaultSearchState: SearchProviderContextType = {
7399
setDobYear: mockSetDobYear,
74100
setPostcode: mockSetPostcode,
75101
setNhsNumber: mockSetNhsNumber,
102+
setSearchType: jest.fn(),
76103
getAllSearchParameters: mockGetAllSearchParameters,
77104
setAllSearchParameters: mockSetAllSearchParameters
78105
}
@@ -96,17 +123,20 @@ const mockPatients = [
96123
}
97124
]
98125

99-
function renderWithRouter() {
126+
function renderWithRouter(initialEntries = ["/patient-search-results"]) {
100127
return render(
101128
<AuthContext.Provider value={mockAuthContext}>
102129
<SearchContext.Provider value={defaultSearchState}>
103-
<MemoryRouter initialEntries={["/patient-search-results"]}>
104-
<Routes>
105-
<Route path="/patient-search-results" element={<BasicDetailsSearchResultsPage />} />
106-
<Route path="/login" element={<div data-testid="login-page-shown" />} />
107-
<Route path="/prescription-list-current" element={<div data-testid="prescription-list-shown" />} />
108-
<Route path="/search-by-basic-details" element={<div data-testid="search-page-shown" />} />
109-
</Routes>
130+
<MemoryRouter initialEntries={initialEntries}>
131+
<NavigationProvider>
132+
<Routes>
133+
<Route path="/patient-search-results" element={<BasicDetailsSearchResultsPage />} />
134+
<Route path="/login" element={<div data-testid="login-page-shown" />} />
135+
<Route path="/prescription-list-current" element={<div data-testid="prescription-list-shown" />} />
136+
<Route path="/search-by-basic-details" element={<div data-testid="search-page-shown" />} />
137+
<Route path="/search-by-prescription-id" element={<div data-testid="search-page-shown" />} />
138+
</Routes>
139+
</NavigationProvider>
110140
</MemoryRouter>
111141
</SearchContext.Provider>
112142
</AuthContext.Provider>
@@ -115,11 +145,24 @@ function renderWithRouter() {
115145

116146
describe("BasicDetailsSearchResultsPage", () => {
117147
beforeEach(() => {
148+
jest.clearAllMocks()
149+
118150
// Mock successful API response
119151
mockAxiosGet.mockResolvedValue({
120152
status: 200,
121153
data: mockPatients
122154
})
155+
156+
mockGetRelevantSearchParameters.mockReturnValue({
157+
firstName: "John",
158+
lastName: "Doe",
159+
dobDay: "01",
160+
dobMonth: "01",
161+
dobYear: "1990",
162+
postcode: "SW1A 1AA"
163+
})
164+
165+
mockGetBackPath.mockReturnValue("/search-by-basic-details")
123166
})
124167

125168
it("shows loading state initially", () => {
@@ -201,20 +244,42 @@ describe("BasicDetailsSearchResultsPage", () => {
201244

202245
await waitFor(() => {
203246
expect(screen.getByTestId("prescription-list-shown")).toBeInTheDocument()
204-
expect(mockClearSearchParameters).toHaveBeenCalled()
205-
expect(mockSetNhsNumber).toHaveBeenCalledWith("9726919207")
247+
expect(mockGetRelevantSearchParameters).toHaveBeenCalledWith(
248+
"basicDetails"
249+
)
250+
expect(mockSetAllSearchParameters).toHaveBeenCalledWith({
251+
firstName: "John",
252+
lastName: "Doe",
253+
dobDay: "01",
254+
dobMonth: "01",
255+
dobYear: "1990",
256+
postcode: "SW1A 1AA",
257+
nhsNumber: "9726919207"
258+
})
206259
})
207260
})
208261

209262
it("navigates to prescription list when clicking a patient row", async () => {
210263
renderWithRouter()
211264
await waitFor(() => {
212-
const firstPatientRow = screen.getByText("Issac Wolderton-Rodriguez").closest("tr")
265+
const firstPatientRow = screen
266+
.getByText("Issac Wolderton-Rodriguez")
267+
.closest("tr")
213268
fireEvent.click(firstPatientRow!)
214269

215270
expect(screen.getByTestId("prescription-list-shown")).toBeInTheDocument()
216-
expect(mockClearSearchParameters).toHaveBeenCalled()
217-
expect(mockSetNhsNumber).toHaveBeenCalledWith("9726919207")
271+
expect(mockGetRelevantSearchParameters).toHaveBeenCalledWith(
272+
"basicDetails"
273+
)
274+
expect(mockSetAllSearchParameters).toHaveBeenCalledWith({
275+
firstName: "John",
276+
lastName: "Doe",
277+
dobDay: "01",
278+
dobMonth: "01",
279+
dobYear: "1990",
280+
postcode: "SW1A 1AA",
281+
nhsNumber: "9726919207"
282+
})
218283
})
219284
})
220285

@@ -226,20 +291,35 @@ describe("BasicDetailsSearchResultsPage", () => {
226291
fireEvent.click(patientNameLink)
227292

228293
expect(screen.getByTestId("prescription-list-shown")).toBeInTheDocument()
229-
expect(mockClearSearchParameters).toHaveBeenCalled()
230-
expect(mockSetNhsNumber).toHaveBeenCalledWith("9726919207")
294+
expect(mockGetRelevantSearchParameters).toHaveBeenCalledWith(
295+
"basicDetails"
296+
)
297+
expect(mockSetAllSearchParameters).toHaveBeenCalledWith({
298+
firstName: "John",
299+
lastName: "Doe",
300+
dobDay: "01",
301+
dobMonth: "01",
302+
dobYear: "1990",
303+
postcode: "SW1A 1AA",
304+
nhsNumber: "9726919207"
305+
})
231306
})
232307
})
233308

234309
it("navigates back when clicking the back link", async () => {
235310
renderWithRouter()
236311

237312
await waitFor(() => {
238-
const backLink = screen.getByText(SearchResultsPageStrings.GO_BACK)
239-
fireEvent.click(backLink)
240-
241-
expect(screen.getByTestId("search-page-shown")).toBeInTheDocument()
313+
expect(
314+
screen.getByText(SearchResultsPageStrings.GO_BACK)
315+
).toBeInTheDocument()
242316
})
317+
318+
const backLink = screen.getByText(SearchResultsPageStrings.GO_BACK)
319+
fireEvent.click(backLink)
320+
321+
expect(mockGoBack).toHaveBeenCalled()
322+
expect(mockGetBackPath).toHaveBeenCalled()
243323
})
244324

245325
it("handles enter key navigation for patient rows", async () => {
@@ -250,8 +330,18 @@ describe("BasicDetailsSearchResultsPage", () => {
250330
fireEvent.keyDown(firstPatientRow!, {key: "Enter"})
251331

252332
expect(screen.getByTestId("prescription-list-shown")).toBeInTheDocument()
253-
expect(mockClearSearchParameters).toHaveBeenCalled()
254-
expect(mockSetNhsNumber).toHaveBeenCalledWith("9726919207")
333+
expect(mockGetRelevantSearchParameters).toHaveBeenCalledWith(
334+
"basicDetails"
335+
)
336+
expect(mockSetAllSearchParameters).toHaveBeenCalledWith({
337+
firstName: "John",
338+
lastName: "Doe",
339+
dobDay: "01",
340+
dobMonth: "01",
341+
dobYear: "1990",
342+
postcode: "SW1A 1AA",
343+
nhsNumber: "9726919207"
344+
})
255345
})
256346
})
257347

@@ -263,8 +353,18 @@ describe("BasicDetailsSearchResultsPage", () => {
263353
fireEvent.keyDown(firstPatientRow!, {key: " "})
264354

265355
expect(screen.getByTestId("prescription-list-shown")).toBeInTheDocument()
266-
expect(mockClearSearchParameters).toHaveBeenCalled()
267-
expect(mockSetNhsNumber).toHaveBeenCalledWith("9726919207")
356+
expect(mockGetRelevantSearchParameters).toHaveBeenCalledWith(
357+
"basicDetails"
358+
)
359+
expect(mockSetAllSearchParameters).toHaveBeenCalledWith({
360+
firstName: "John",
361+
lastName: "Doe",
362+
dobDay: "01",
363+
dobMonth: "01",
364+
dobYear: "1990",
365+
postcode: "SW1A 1AA",
366+
nhsNumber: "9726919207"
367+
})
268368
})
269369
})
270370

0 commit comments

Comments
 (0)