Skip to content

Commit 7eaa507

Browse files
copied over previous PR
1 parent 329e0f9 commit 7eaa507

File tree

15 files changed

+787
-441
lines changed

15 files changed

+787
-441
lines changed

.devcontainer/Dockerfile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
FROM mcr.microsoft.com/devcontainers/base:ubuntu
1+
FROM ubuntu:22.04
22
ARG TARGETARCH
33
ENV TARGETARCH=${TARGETARCH}
44

5+
# Install essential packages first
6+
RUN apt-get update && apt-get install -y \
7+
curl \
8+
wget \
9+
git \
10+
sudo \
11+
&& apt-get clean \
12+
&& rm -rf /var/lib/apt/lists/*
13+
14+
# Create vscode user with sudo access
15+
RUN groupadd -r vscode && useradd -r -g vscode -m -s /bin/bash vscode \
16+
&& echo "vscode ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
17+
518
ARG ASDF_VERSION
619
COPY .tool-versions.asdf /tmp/.tool-versions.asdf
720

package-lock.json

Lines changed: 49 additions & 136 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import "@testing-library/jest-dom"
2+
import {render, screen} from "@testing-library/react"
3+
import {BrowserRouter} from "react-router-dom"
4+
import React from "react"
5+
import App from "@/App"
6+
7+
// Mock all the context providers
8+
jest.mock("@/context/AuthProvider", () => ({
9+
AuthProvider: ({children}: {children: React.ReactNode}) => <div>{children}</div>
10+
}))
11+
12+
jest.mock("@/context/AccessProvider", () => ({
13+
AccessProvider: ({children}: {children: React.ReactNode}) => <div>{children}</div>
14+
}))
15+
16+
jest.mock("@/context/SearchProvider", () => ({
17+
SearchProvider: ({children}: {children: React.ReactNode}) => <div>{children}</div>
18+
}))
19+
20+
jest.mock("@/context/NavigationProvider", () => ({
21+
NavigationProvider: ({children}: {children: React.ReactNode}) => <div>{children}</div>
22+
}))
23+
24+
jest.mock("@/context/PatientDetailsProvider", () => ({
25+
PatientDetailsProvider: ({children}: {children: React.ReactNode}) => <div>{children}</div>
26+
}))
27+
28+
jest.mock("@/context/PrescriptionInformationProvider", () => ({
29+
PrescriptionInformationProvider: ({children}: {children: React.ReactNode}) => <div>{children}</div>
30+
}))
31+
32+
// Mock Layout component
33+
jest.mock("@/Layout", () => {
34+
return function MockLayout({children}: {children: React.ReactNode}) {
35+
return <div data-testid="layout">{children}</div>
36+
}
37+
})
38+
39+
// Mock all the page components
40+
jest.mock("@/pages/LoginPage", () => () => <div>Login Page</div>)
41+
jest.mock("@/pages/LogoutPage", () => () => <div>Logout Page</div>)
42+
jest.mock("@/pages/SelectYourRolePage", () => () => <div>Select Your Role</div>)
43+
jest.mock("@/pages/ChangeRolePage", () => () => <div>Change Role</div>)
44+
jest.mock(
45+
"@/pages/SearchPrescriptionPage",
46+
() => () => <div>Search Prescription</div>
47+
)
48+
jest.mock("@/pages/YourSelectedRolePage", () => () => <div>Your Selected Role</div>)
49+
jest.mock("@/pages/NotFoundPage", () => () => <div>Not Found</div>)
50+
jest.mock("@/pages/PrescriptionListPage", () => () => <div>Prescription List</div>)
51+
jest.mock(
52+
"@/pages/PrescriptionDetailsPage",
53+
() => () => <div>Prescription Details</div>
54+
)
55+
jest.mock("@/pages/CookiePolicyPage", () => () => <div>Cookie Policy</div>)
56+
jest.mock("@/pages/CookieSettingsPage", () => () => <div>Cookie Settings</div>)
57+
jest.mock(
58+
"@/pages/BasicDetailsSearchResultsPage",
59+
() => () => <div>Search Results</div>
60+
)
61+
jest.mock("@/pages/PrivacyNoticePage", () => () => <div>Privacy Notice</div>)
62+
jest.mock("@/pages/SessionSelection", () => () => <div>Session Selection</div>)
63+
jest.mock("@/pages/SessionLoggedOut", () => () => <div>Session Logged Out</div>)
64+
65+
// Mock EPSCookieBanner
66+
jest.mock("@/components/EPSCookieBanner", () => () => <div>Cookie Banner</div>)
67+
68+
describe("App", () => {
69+
it("renders the skip link for regular pages", () => {
70+
render(
71+
<BrowserRouter>
72+
<App />
73+
</BrowserRouter>
74+
)
75+
76+
const skipLink = screen.getByTestId("eps_header_skipLink")
77+
expect(skipLink).toBeInTheDocument()
78+
expect(skipLink).toHaveAttribute("href", "#main-content")
79+
expect(skipLink).toHaveTextContent("Skip to main content")
80+
expect(skipLink).toHaveClass("nhsuk-skip-link")
81+
})
82+
})

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {AccessContext} from "@/context/AccessProvider"
88

99
// Mock the strings module
1010
jest.mock("@/constants/ui-strings/HeaderStrings", () => ({
11-
HEADER_SERVICE: "Prescription Tracker",
11+
HEADER_SERVICE: "Prescription Tracker (Pilot)",
1212
HEADER_EXIT_BUTTON: "Exit",
1313
HEADER_EXIT_TARGET: "/exit",
1414
HEADER_CHANGE_ROLE_BUTTON: "Change role",
@@ -108,7 +108,7 @@ describe("EpsHeader", () => {
108108

109109
it("displays the correct service name in the header", () => {
110110
expect(screen.getByTestId("eps_header_serviceName")).toHaveTextContent(
111-
"Prescription Tracker"
111+
"Prescription Tracker (Pilot)"
112112
)
113113
})
114114

@@ -137,7 +137,7 @@ describe("EpsHeader", () => {
137137

138138
it("displays the correct service name in the header", () => {
139139
expect(screen.getByTestId("eps_header_serviceName")).toHaveTextContent(
140-
"Prescription Tracker"
140+
"Prescription Tracker (Pilot)"
141141
)
142142
})
143143

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

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,11 @@ describe("RoleSelectionPage", () => {
369369
it("handles click on role card", async () => {
370370
render(<RoleSelectionPage contentText={defaultContentText} />)
371371

372-
const orgFocusArea = screen.getByRole("heading", {name: /Test Pharmacy/}).closest(".eps-card__org-focus-area")
373-
expect(orgFocusArea).toBeInTheDocument()
372+
const card = screen.getByTestId("eps-card")
373+
expect(card).toBeInTheDocument()
374374

375375
await act(async () => {
376-
fireEvent.click(orgFocusArea!)
376+
fireEvent.click(card)
377377
})
378378

379379
await waitFor(() => {
@@ -385,11 +385,11 @@ describe("RoleSelectionPage", () => {
385385
it("handles Enter key press on role card", async () => {
386386
render(<RoleSelectionPage contentText={defaultContentText} />)
387387

388-
const orgFocusArea = screen.getByRole("heading", {name: /Test Pharmacy/}).closest(".eps-card__org-focus-area")
389-
expect(orgFocusArea).toBeInTheDocument()
388+
const card = screen.getByTestId("eps-card")
389+
expect(card).toBeInTheDocument()
390390

391391
await act(async () => {
392-
fireEvent.keyDown(orgFocusArea!, {key: "Enter"})
392+
fireEvent.keyDown(card, {key: "Enter"})
393393
})
394394

395395
await waitFor(() => {
@@ -401,11 +401,11 @@ describe("RoleSelectionPage", () => {
401401
it("handles Space key press on role card", async () => {
402402
render(<RoleSelectionPage contentText={defaultContentText} />)
403403

404-
const orgFocusArea = screen.getByRole("heading", {name: /Test Pharmacy/}).closest(".eps-card__org-focus-area")
405-
expect(orgFocusArea).toBeInTheDocument()
404+
const card = screen.getByTestId("eps-card")
405+
expect(card).toBeInTheDocument()
406406

407407
await act(async () => {
408-
fireEvent.keyDown(orgFocusArea!, {key: " "})
408+
fireEvent.keyDown(card, {key: " "})
409409
})
410410

411411
await waitFor(() => {
@@ -417,14 +417,14 @@ describe("RoleSelectionPage", () => {
417417
it("ignores other key presses on role card", async () => {
418418
render(<RoleSelectionPage contentText={defaultContentText} />)
419419

420-
const orgFocusArea = screen.getByRole("heading", {name: /Test Pharmacy/}).closest(".eps-card__org-focus-area")
421-
expect(orgFocusArea).toBeInTheDocument()
420+
const card = screen.getByTestId("eps-card")
421+
expect(card).toBeInTheDocument()
422422

423423
mockNavigate.mockClear()
424424
mockUpdateSelectedRole.mockClear()
425425

426426
await act(async () => {
427-
fireEvent.keyDown(orgFocusArea!, {key: "Tab"})
427+
fireEvent.keyDown(card, {key: "Tab"})
428428
})
429429

430430
expect(mockUpdateSelectedRole).not.toHaveBeenCalled()
@@ -445,10 +445,10 @@ describe("RoleSelectionPage", () => {
445445

446446
render(<RoleSelectionPage contentText={defaultContentText} />)
447447

448-
const orgFocusArea = screen.getByRole("heading", {name: /Test Pharmacy/}).closest(".eps-card__org-focus-area")
448+
const card = screen.getByTestId("eps-card")
449449

450450
await act(async () => {
451-
fireEvent.click(orgFocusArea!)
451+
fireEvent.click(card)
452452
})
453453

454454
await waitFor(() => {
@@ -468,10 +468,10 @@ describe("RoleSelectionPage", () => {
468468

469469
render(<RoleSelectionPage contentText={defaultContentText} />)
470470

471-
const orgFocusArea = screen.getByRole("heading", {name: /Test Pharmacy/}).closest(".eps-card__org-focus-area")
471+
const card = screen.getByTestId("eps-card")
472472

473473
await act(async () => {
474-
fireEvent.click(orgFocusArea!)
474+
fireEvent.click(card)
475475
})
476476

477477
await waitFor(() => {
@@ -484,10 +484,7 @@ describe("RoleSelectionPage", () => {
484484

485485
const card = screen.getByTestId("eps-card")
486486
expect(card).toHaveClass("nhsuk-card", "nhsuk-card--primary", "nhsuk-u-margin-bottom-4")
487-
488-
const orgFocusArea = screen.getByRole("heading", {name: /Test Pharmacy/}).closest(".eps-card__org-focus-area")
489-
expect(orgFocusArea).toHaveClass("eps-card__org-focus-area")
490-
expect(orgFocusArea).toHaveAttribute("tabIndex", "0")
487+
expect(card).toHaveAttribute("tabIndex", "0")
491488

492489
const heading = screen.getByRole("heading", {name: /Test Pharmacy/})
493490
expect(heading).toHaveClass("nhsuk-heading-s", "eps-card__org-name")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import "@testing-library/jest-dom"
2+
import * as HeaderStrings from "@/constants/ui-strings/HeaderStrings"
3+
4+
describe("HeaderStrings", () => {
5+
it("exports all header string constants", () => {
6+
expect(HeaderStrings.HEADER_SERVICE).toBe("Prescription Tracker (pilot)")
7+
expect(HeaderStrings.HEADER_CHANGE_ROLE_BUTTON).toBe("Change role")
8+
expect(HeaderStrings.HEADER_SELECT_YOUR_ROLE_BUTTON).toBe("Select Your Role")
9+
expect(HeaderStrings.HEADER_PRESCRIPTION_SEARCH_BUTTON).toBe("Search for a prescription")
10+
expect(HeaderStrings.HEADER_EXIT_BUTTON).toBe("Exit")
11+
expect(HeaderStrings.HEADER_FEEDBACK_BUTTON).toBe("Give feedback (opens in new tab)")
12+
expect(HeaderStrings.HEADER_LOG_OUT_BUTTON).toBe("Log out")
13+
expect(HeaderStrings.HEADER_SKIP_TO_MAIN_CONTENT).toBe("Skip to main content")
14+
})
15+
16+
it("has the correct feedback URL", () => {
17+
expect(HeaderStrings.HEADER_FEEDBACK_TARGET).toBe(
18+
"https://feedback.digital.nhs.uk/jfe/form/SV_ahG2dymAdr0oRz8"
19+
)
20+
})
21+
22+
it("has the correct target paths", () => {
23+
expect(HeaderStrings.HEADER_EXIT_TARGET).toBe("/")
24+
expect(HeaderStrings.HEADER_CHANGE_ROLE_TARGET).toBe("/change-your-role")
25+
expect(HeaderStrings.HEADER_SELECT_YOUR_ROLE_TARGET).toBe("/select-your-role")
26+
expect(HeaderStrings.HEADER_PRESCRIPTION_SEARCH_TARGET).toBe("/search-by-prescription-id")
27+
})
28+
})

packages/cpt-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"identity-obj-proxy": "^3.0.0",
2121
"nhsuk-frontend": "9.6.4",
2222
"nhsuk-react-components": "^5.0.0",
23+
"nhsuk-react-components-extensions": "^2.3.0-beta",
2324
"pino": "^10.1.0",
2425
"react": "18.3.1",
2526
"react-dom": "18.3.1",

packages/cpt-ui/src/App.tsx

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Routes, Route} from "react-router-dom"
1+
import {Routes, Route, useLocation} from "react-router-dom"
22
import {AuthProvider} from "@/context/AuthProvider"
33
import {AccessProvider} from "@/context/AccessProvider"
44
import {SearchProvider} from "@/context/SearchProvider"
@@ -25,46 +25,71 @@ import SessionSelectionPage from "./pages/SessionSelection"
2525

2626
import {FRONTEND_PATHS} from "@/constants/environment"
2727
import SessionLoggedOutPage from "./pages/SessionLoggedOut"
28+
import {HEADER_SKIP_TO_MAIN_CONTENT} from "@/constants/ui-strings/HeaderStrings"
29+
30+
function AppContent() {
31+
const location = useLocation()
32+
33+
// Check if we're on a prescription list or prescription details page
34+
const isPrescriptionPage =
35+
location.pathname === FRONTEND_PATHS.PRESCRIPTION_LIST_CURRENT ||
36+
location.pathname === FRONTEND_PATHS.PRESCRIPTION_LIST_FUTURE ||
37+
location.pathname === FRONTEND_PATHS.PRESCRIPTION_LIST_PAST ||
38+
location.pathname.startsWith(FRONTEND_PATHS.PRESCRIPTION_DETAILS_PAGE)
39+
40+
const skipTarget = isPrescriptionPage ? "#patient-details-banner" : "#main-content"
41+
42+
return (
43+
<PatientDetailsProvider>
44+
<EPSCookieBanner />
45+
<a
46+
href={skipTarget}
47+
className="nhsuk-skip-link"
48+
data-testid="eps_header_skipLink"
49+
>
50+
{HEADER_SKIP_TO_MAIN_CONTENT}
51+
</a>
52+
<PrescriptionInformationProvider>
53+
<SearchProvider>
54+
<NavigationProvider>
55+
<Routes>
56+
<Route path="/" element={<Layout />}>
57+
{/* Public cookie routes */}
58+
<Route path="cookies" element={<CookiePolicyPage />} />
59+
<Route path="cookies-selected" element={<CookieSettingsPage />} />
60+
61+
{/* Your existing routes */}
62+
<Route path="*" element={<NotFoundPage />} />
63+
<Route path={FRONTEND_PATHS.SESSION_SELECTION} element={<SessionSelectionPage />} />
64+
<Route path={FRONTEND_PATHS.LOGIN} element={<LoginPage />} />
65+
<Route path={FRONTEND_PATHS.LOGOUT} element={<LogoutPage />} />
66+
<Route path={FRONTEND_PATHS.SESSION_LOGGED_OUT} element={<SessionLoggedOutPage />} />
67+
<Route path={FRONTEND_PATHS.SELECT_YOUR_ROLE} element={<SelectYourRolePage />} />
68+
<Route path={FRONTEND_PATHS.YOUR_SELECTED_ROLE} element={<YourSelectedRolePage />} />
69+
<Route path={FRONTEND_PATHS.CHANGE_YOUR_ROLE} element={<ChangeRolePage />} />
70+
<Route path={FRONTEND_PATHS.SEARCH_BY_PRESCRIPTION_ID} element={<SearchPrescriptionPage />} />
71+
<Route path={FRONTEND_PATHS.SEARCH_BY_NHS_NUMBER} element={<SearchPrescriptionPage />} />
72+
<Route path={FRONTEND_PATHS.SEARCH_BY_BASIC_DETAILS} element={<SearchPrescriptionPage />} />
73+
<Route path={FRONTEND_PATHS.PRESCRIPTION_LIST_CURRENT} element={<PrescriptionListPage />} />
74+
<Route path={FRONTEND_PATHS.PRESCRIPTION_LIST_FUTURE} element={<PrescriptionListPage />} />
75+
<Route path={FRONTEND_PATHS.PRESCRIPTION_LIST_PAST} element={<PrescriptionListPage />} />
76+
<Route path={FRONTEND_PATHS.PRESCRIPTION_DETAILS_PAGE} element={<PrescriptionDetailsPage />} />
77+
<Route path={FRONTEND_PATHS.PATIENT_SEARCH_RESULTS} element={<SearchResultsPage />} />
78+
<Route path={FRONTEND_PATHS.PRIVACY_NOTICE} element={<PrivacyNoticePage />} />
79+
</Route>
80+
</Routes>
81+
</NavigationProvider>
82+
</SearchProvider>
83+
</PrescriptionInformationProvider>
84+
</PatientDetailsProvider>
85+
)
86+
}
2887

2988
export default function App() {
3089
return (
3190
<AuthProvider>
3291
<AccessProvider>
33-
<PatientDetailsProvider>
34-
<EPSCookieBanner />
35-
<PrescriptionInformationProvider>
36-
<SearchProvider>
37-
<NavigationProvider>
38-
<Routes>
39-
<Route path="/" element={<Layout />}>
40-
{/* Public cookie routes */}
41-
<Route path="cookies" element={<CookiePolicyPage />} />
42-
<Route path="cookies-selected" element={<CookieSettingsPage />} />
43-
44-
{/* Your existing routes */}
45-
<Route path="*" element={<NotFoundPage />} />
46-
<Route path={FRONTEND_PATHS.SESSION_SELECTION} element={<SessionSelectionPage />} />
47-
<Route path={FRONTEND_PATHS.LOGIN} element={<LoginPage />} />
48-
<Route path={FRONTEND_PATHS.LOGOUT} element={<LogoutPage />} />
49-
<Route path={FRONTEND_PATHS.SESSION_LOGGED_OUT} element={<SessionLoggedOutPage />} />
50-
<Route path={FRONTEND_PATHS.SELECT_YOUR_ROLE} element={<SelectYourRolePage />} />
51-
<Route path={FRONTEND_PATHS.YOUR_SELECTED_ROLE} element={<YourSelectedRolePage />} />
52-
<Route path={FRONTEND_PATHS.CHANGE_YOUR_ROLE} element={<ChangeRolePage />} />
53-
<Route path={FRONTEND_PATHS.SEARCH_BY_PRESCRIPTION_ID} element={<SearchPrescriptionPage />} />
54-
<Route path={FRONTEND_PATHS.SEARCH_BY_NHS_NUMBER} element={<SearchPrescriptionPage />} />
55-
<Route path={FRONTEND_PATHS.SEARCH_BY_BASIC_DETAILS} element={<SearchPrescriptionPage />} />
56-
<Route path={FRONTEND_PATHS.PRESCRIPTION_LIST_CURRENT} element={<PrescriptionListPage />} />
57-
<Route path={FRONTEND_PATHS.PRESCRIPTION_LIST_FUTURE} element={<PrescriptionListPage />} />
58-
<Route path={FRONTEND_PATHS.PRESCRIPTION_LIST_PAST} element={<PrescriptionListPage />} />
59-
<Route path={FRONTEND_PATHS.PRESCRIPTION_DETAILS_PAGE} element={<PrescriptionDetailsPage />} />
60-
<Route path={FRONTEND_PATHS.PATIENT_SEARCH_RESULTS} element={<SearchResultsPage />} />
61-
<Route path={FRONTEND_PATHS.PRIVACY_NOTICE} element={<PrivacyNoticePage />} />
62-
</Route>
63-
</Routes>
64-
</NavigationProvider>
65-
</SearchProvider>
66-
</PrescriptionInformationProvider>
67-
</PatientDetailsProvider>
92+
<AppContent />
6893
</AccessProvider>
6994
</AuthProvider>
7095
)

0 commit comments

Comments
 (0)