Skip to content
216 changes: 216 additions & 0 deletions packages/cpt-ui/__tests__/AccessibilityStatementPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import "@testing-library/jest-dom"
import React, {useState} from "react"
import {render, screen} from "@testing-library/react"
import {MemoryRouter} from "react-router-dom"
import {AuthContext, type AuthContextType} from "@/context/AuthProvider"
import AccessibilityStatementPage from "@/pages/AccessibilityStatementPage"
import {AccessibilityStatementStrings} from "@/constants/ui-strings/AccessibilityStatementStrings"
import {mockAuthState} from "./mocks/AuthStateMock"

jest.mock("@/helpers/awsRum")
jest.mock("@/context/configureAmplify")

jest.mock("@/constants/environment", () => ({
AUTH_CONFIG: {
USER_POOL_ID: "test-pool-id",
USER_POOL_CLIENT_ID: "test-client-id",
HOSTED_LOGIN_DOMAIN: "test.domain",
REDIRECT_SIGN_IN: "http://localhost:3000",
REDIRECT_SIGN_OUT: "http://localhost:3000/logout"
},
APP_CONFIG: {
REACT_LOG_LEVEL: "debug"
},
API_ENDPOINTS: {
CIS2_SIGNOUT_ENDPOINT: "/api/cis2-signout"
},
FRONTEND_PATHS: {
LOGIN: "/login",
SEARCH_BY_PRESCRIPTION_ID: "/search-by-prescription-id"
}
}))

const signedOutAuthState: AuthContextType = {
...mockAuthState,
isSignedIn: false
}

const signedInAuthState: AuthContextType = {
...mockAuthState,
isSignedIn: true
}

const MockAuthProvider = ({
children,
authState
}: {
children: React.ReactNode;
authState: AuthContextType;
}) => {
const [state] = useState<AuthContextType>(authState)
return <AuthContext.Provider value={state}>{children}</AuthContext.Provider>
}

const renderPage = (authState: AuthContextType = signedOutAuthState) => {
return render(
<MockAuthProvider authState={authState}>
<MemoryRouter>
<AccessibilityStatementPage />
</MemoryRouter>
</MockAuthProvider>
)
}

describe("AccessibilityStatementPage", () => {
describe("page structure", () => {
it("renders the main container", () => {
renderPage()
expect(screen.getByRole("main")).toBeInTheDocument()
})

it("renders the page heading", () => {
renderPage()
expect(
screen.getByRole("heading", {level: 1, name: AccessibilityStatementStrings.header})
).toBeInTheDocument()
})

it("renders the breadcrumb Home link", () => {
renderPage()
expect(screen.getByRole("link", {name: AccessibilityStatementStrings.home})).toBeInTheDocument()
})
})

describe("section headings", () => {
beforeEach(() => {
renderPage()
})

it("renders the known issues heading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.knownIssues.header})
).toBeInTheDocument()
})

it("renders the feedback and contact information heading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.feedbackContactInformation.header})
).toBeInTheDocument()
})

it("renders the enforcement procedure heading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.enforcementProcedure.header})
).toBeInTheDocument()
})

it("renders the technical information heading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.technicalInformation.header})
).toBeInTheDocument()
})

it("renders the compliance status heading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.complianceStatus.header})
).toBeInTheDocument()
})

it("renders the non-accessible content heading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.nonaccessibleContent.header})
).toBeInTheDocument()
})

it("renders the non-compliance subheading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.nonaccessibleContent.subheader})
).toBeInTheDocument()
})

it("renders the improving accessibility heading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.improvingAccessibility.header})
).toBeInTheDocument()
})

it("renders the preparation subheading", () => {
expect(
screen.getByRole("heading", {name: AccessibilityStatementStrings.improvingAccessibility.subheader})
).toBeInTheDocument()
})
})

describe("links rendered via EpsRichText", () => {
beforeEach(() => {
renderPage()
})

it("renders the Prescription Tracker link in the opening paragraph", () => {
const link = screen.getByRole("link", {name: "Prescription Tracker"})
expect(link).toHaveAttribute("href", "/site")
expect(link).not.toHaveAttribute("target")
})

it("renders the AbilityNet external link", () => {
const link = screen.getByRole("link", {name: "AbilityNet (opens in new tab)"})
expect(link).toHaveAttribute("href", "https://www.abilitynet.org.uk/")
expect(link).toHaveAttribute("target", "_blank")
expect(link).toHaveAttribute("rel", "noreferrer")
})

it("renders the email link in the feedback section", () => {
const links = screen.getAllByRole("link", {name: "epssupport@nhs.net"})
expect(links).toHaveLength(2)
links.forEach((link) => {
expect(link).toHaveAttribute("href", "mailto:epssupport@nhs.net")
})
})

it("renders the Equality Advisory Service external link", () => {
const link = screen.getByRole("link", {
name: "Equality Advisory and Support Service (opens in new tab)"
})
expect(link).toHaveAttribute("href", "https://www.equalityadvisoryservice.com/")
expect(link).toHaveAttribute("target", "_blank")
expect(link).toHaveAttribute("rel", "noreferrer")
})
})

describe("list content", () => {
it("renders the opening section accessibility features list", () => {
renderPage()
AccessibilityStatementStrings.openingSection.listItems.forEach((item) => {
expect(screen.getByText(item)).toBeInTheDocument()
})
})

it("renders the known issues list", () => {
renderPage()
AccessibilityStatementStrings.knownIssues.listItems.forEach((item) => {
expect(screen.getByText(item)).toBeInTheDocument()
})
})

it("renders the non-compliance sub-list", () => {
renderPage()
AccessibilityStatementStrings.nonaccessibleContent.subListItems.forEach((item) => {
expect(screen.getByText(item.trim())).toBeInTheDocument()
})
})
})

describe("breadcrumb home link destination", () => {
it("links to the login page when signed out", () => {
renderPage(signedOutAuthState)
const homeLink = screen.getByRole("link", {name: AccessibilityStatementStrings.home})
expect(homeLink).toHaveAttribute("href", "/login")
})

it("links to the search page when signed in", () => {
renderPage(signedInAuthState)
const homeLink = screen.getByRole("link", {name: AccessibilityStatementStrings.home})
expect(homeLink).toHaveAttribute("href", "/search-by-prescription-id")
})
})
})
93 changes: 93 additions & 0 deletions packages/cpt-ui/__tests__/EpsRichText.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import "@testing-library/jest-dom"
import React from "react"
import {render, screen} from "@testing-library/react"
import EpsRichText from "@/components/EpsRichText"
import type {RichTextNode} from "@/components/EpsRichText"

describe("EpsRichText", () => {
describe("plain string content", () => {
it("renders a single string", () => {
render(<EpsRichText content="Hello world" />)
expect(screen.getByText("Hello world")).toBeInTheDocument()
})

it("renders multiple strings from an array", () => {
const {container} = render(<EpsRichText content={["Hello ", "world"]} />)
expect(container).toHaveTextContent("Hello world")
})
})

describe("link node content", () => {
it("renders a single internal link node", () => {
const node: RichTextNode = {text: "Prescription Tracker", href: "/site"}
render(<EpsRichText content={node} />)
const link = screen.getByRole("link", {name: "Prescription Tracker"})
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute("href", "/site")
expect(link).not.toHaveAttribute("target")
expect(link).not.toHaveAttribute("rel")
})

it("renders an external link with target and rel attributes", () => {
const node: RichTextNode = {
text: "AbilityNet (opens in new tab)",
href: "https://www.abilitynet.org.uk/",
external: true
}
render(<EpsRichText content={node} />)
const link = screen.getByRole("link", {name: "AbilityNet (opens in new tab)"})
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute("href", "https://www.abilitynet.org.uk/")
expect(link).toHaveAttribute("target", "_blank")
expect(link).toHaveAttribute("rel", "noreferrer")
})

it("does not add target/rel when external is false", () => {
const node: RichTextNode = {text: "Click here", href: "/somewhere", external: false}
render(<EpsRichText content={node} />)
const link = screen.getByRole("link", {name: "Click here"})
expect(link).not.toHaveAttribute("target")
expect(link).not.toHaveAttribute("rel")
})
})

describe("mixed array content", () => {
it("renders a mix of strings and link nodes", () => {
const content: Array<RichTextNode> = [
"This applies to the ",
{text: "Prescription Tracker", href: "/site"},
"."
]
const {container} = render(<EpsRichText content={content} />)
expect(container).toHaveTextContent(/This applies to the/)
expect(container).toHaveTextContent(/\.$/)
const link = screen.getByRole("link", {name: "Prescription Tracker"})
expect(link).toHaveAttribute("href", "/site")
})

it("renders multiple links in one array", () => {
const content: Array<RichTextNode> = [
{text: "First link", href: "/first"},
" and ",
{text: "Second link", href: "/second", external: true}
]
render(<EpsRichText content={content} />)
const first = screen.getByRole("link", {name: "First link"})
const second = screen.getByRole("link", {name: "Second link"})
expect(first).toHaveAttribute("href", "/first")
expect(first).not.toHaveAttribute("target")
expect(second).toHaveAttribute("href", "/second")
expect(second).toHaveAttribute("target", "_blank")
})

it("renders a mailto link correctly", () => {
const content: Array<RichTextNode> = [
"Contact us at ",
{text: "epssupport@nhs.net", href: "mailto:epssupport@nhs.net"}
]
render(<EpsRichText content={content} />)
const link = screen.getByRole("link", {name: "epssupport@nhs.net"})
expect(link).toHaveAttribute("href", "mailto:epssupport@nhs.net")
})
})
})
2 changes: 2 additions & 0 deletions packages/cpt-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import PrivacyNoticePage from "./pages/PrivacyNoticePage"
import SessionSelectionPage from "./pages/SessionSelection"
import NoPrescriptionsFoundPage from "@/pages/NoPrescriptionsFoundPage"
import NoPatientsFoundPage from "@/pages/NoPatientsFoundPage"
import AccessibilityStatementPage from "./pages/AccessibilityStatementPage"

import {FRONTEND_PATHS} from "@/constants/environment"
import SessionLoggedOutPage from "./pages/SessionLoggedOut"
Expand Down Expand Up @@ -106,6 +107,7 @@ function AppContent() {
<Route path={FRONTEND_PATHS.NO_PATIENT_FOUND} element={<NoPatientsFoundPage />} />
<Route path={FRONTEND_PATHS.NO_PRESCRIPTIONS_FOUND} element={<NoPrescriptionsFoundPage />} />
<Route path={FRONTEND_PATHS.PRIVACY_NOTICE} element={<PrivacyNoticePage />} />
<Route path={FRONTEND_PATHS.ACCESSIBILITY_STATEMENT} element={<AccessibilityStatementPage />} />
</Route>
</Routes>
</NavigationProvider>
Expand Down
35 changes: 35 additions & 0 deletions packages/cpt-ui/src/components/EpsRichText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react"

export type RichTextLinkNode = {
text: string
href: string
external?: boolean
}

export type RichTextNode = string | RichTextLinkNode
export type RichTextContent = RichTextNode | Array<RichTextNode>

interface EpsRichTextProps {
content: RichTextContent
}

export default function EpsRichText({content}: EpsRichTextProps) {

Check warning on line 16 in packages/cpt-ui/src/components/EpsRichText.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark the props of the component as read-only.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-prescription-tracker-ui&issues=AZ0go7tEXafjTJA5eJte&open=AZ0go7tEXafjTJA5eJte&pullRequest=1949
const nodes = Array.isArray(content) ? content : [content]
return (
<>
{nodes.map((node, i) =>
typeof node === "string" ? (
<React.Fragment key={i}>{node}</React.Fragment>

Check warning on line 22 in packages/cpt-ui/src/components/EpsRichText.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-prescription-tracker-ui&issues=AZ0go7tEXafjTJA5eJtf&open=AZ0go7tEXafjTJA5eJtf&pullRequest=1949
) : (
<a
key={i}

Check warning on line 25 in packages/cpt-ui/src/components/EpsRichText.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-prescription-tracker-ui&issues=AZ0go7tEXafjTJA5eJtg&open=AZ0go7tEXafjTJA5eJtg&pullRequest=1949
href={node.href}
{...(node.external ? {target: "_blank", rel: "noreferrer"} : {})}

Check warning on line 27 in packages/cpt-ui/src/components/EpsRichText.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-prescription-tracker-ui&issues=AZ0go7tEXafjTJA5eJth&open=AZ0go7tEXafjTJA5eJth&pullRequest=1949
>
{node.text}
</a>
)
)}
</>
)
}
2 changes: 2 additions & 0 deletions packages/cpt-ui/src/constants/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const FRONTEND_PATHS = {
NO_PATIENT_FOUND: "/no-patient-found",
NO_PRESCRIPTIONS_FOUND: "/no-prescriptions-found",
PRIVACY_NOTICE: "/privacy-notice",
ACCESSIBILITY_STATEMENT: "/accessibility-statement",
COOKIES_SELECTED: "/cookies-selected",
SESSION_SELECTION: "/select-active-session",
NOT_FOUND: "/notfound"
Expand All @@ -89,6 +90,7 @@ export const PUBLIC_PATHS = [
FRONTEND_PATHS.PRIVACY_NOTICE,
FRONTEND_PATHS.COOKIES_SELECTED,
FRONTEND_PATHS.NOT_FOUND,
FRONTEND_PATHS.ACCESSIBILITY_STATEMENT,
"/"
] as const

Expand Down
Loading
Loading