66 useLocation
77} from "react-router-dom"
88import { render , screen , waitFor } from "@testing-library/react"
9+ import userEvent from "@testing-library/user-event"
910import "@testing-library/jest-dom"
1011
1112import EpsTabs , { TabHeader } from "@/components/EpsTabs"
@@ -15,8 +16,8 @@ function LocationIndicator() {
1516 return < div data-testid = "current-path" > { location . pathname } </ div >
1617}
1718
18- type HarnessProps = { variant ?: "default" | "large" }
19- function Harness ( { variant = "default" } : HarnessProps ) {
19+ type HarnessProps = { variant ?: "default" | "large" ; includeQuery ?: boolean }
20+ function Harness ( { variant = "default" , includeQuery = false } : HarnessProps ) {
2021 const location = useLocation ( )
2122 const tabHeaderArray : Array < TabHeader > = [
2223 { title : "(1)" , link : "/prescription-list-current" } ,
@@ -26,12 +27,13 @@ function Harness({variant = "default"}: HarnessProps) {
2627
2728 return (
2829 < EpsTabs
29- activeTabPath = { location . pathname }
30+ activeTabPath = { includeQuery ? location . pathname + location . search : location . pathname }
3031 tabHeaderArray = { tabHeaderArray }
3132 variant = { variant }
3233 >
3334 < div >
3435 < input data-testid = "dummy-input" />
36+ < textarea data-testid = "dummy-textarea" />
3537 < LocationIndicator />
3638 < div data-testid = "panel-content" > Panel</ div >
3739 </ div >
@@ -53,31 +55,31 @@ describe("EpsTabs", () => {
5355 expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-current" )
5456
5557 // ArrowRight to future
56- window . dispatchEvent ( new KeyboardEvent ( "keydown" , { key : " ArrowRight" } ) )
58+ await userEvent . keyboard ( "{ ArrowRight}" )
5759 await waitFor ( ( ) => {
5860 expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-future" )
5961 } )
6062
6163 // ArrowRight to past
62- window . dispatchEvent ( new KeyboardEvent ( "keydown" , { key : " ArrowRight" } ) )
64+ await userEvent . keyboard ( "{ ArrowRight}" )
6365 await waitFor ( ( ) => {
6466 expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-past" )
6567 } )
6668
6769 // ArrowRight at last stays on past
68- window . dispatchEvent ( new KeyboardEvent ( "keydown" , { key : " ArrowRight" } ) )
70+ await userEvent . keyboard ( "{ ArrowRight}" )
6971 await waitFor ( ( ) => {
7072 expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-past" )
7173 } )
7274
7375 // ArrowLeft goes back to future
74- window . dispatchEvent ( new KeyboardEvent ( "keydown" , { key : " ArrowLeft" } ) )
76+ await userEvent . keyboard ( "{ ArrowLeft}" )
7577 await waitFor ( ( ) => {
7678 expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-future" )
7779 } )
7880 } )
7981
80- it ( "does not navigate when focus is inside an input" , ( ) => {
82+ it ( "does not navigate when focus is inside an input" , async ( ) => {
8183 render (
8284 < MemoryRouter initialEntries = { [ "/prescription-list-current" ] } >
8385 < Routes >
@@ -89,8 +91,75 @@ describe("EpsTabs", () => {
8991 // Focus input then press ArrowRight – should not change tab
9092 const input = screen . getByTestId ( "dummy-input" ) as HTMLInputElement
9193 input . focus ( )
92- window . dispatchEvent ( new KeyboardEvent ( "keydown" , { key : "ArrowRight" } ) )
94+ await userEvent . keyboard ( "{ArrowRight}" )
95+ expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-current" )
96+ } )
97+
98+ it ( "does not navigate when focus is inside a textarea" , async ( ) => {
99+ render (
100+ < MemoryRouter initialEntries = { [ "/prescription-list-current" ] } >
101+ < Routes >
102+ < Route path = "*" element = { < Harness /> } />
103+ </ Routes >
104+ </ MemoryRouter >
105+ )
106+
107+ const textarea = screen . getByTestId ( "dummy-textarea" ) as HTMLTextAreaElement
108+ textarea . focus ( )
109+ await userEvent . keyboard ( "{ArrowRight}" )
110+ expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-current" )
111+ } )
112+
113+ it ( "navigates correctly when activeTabPath includes a query string" , async ( ) => {
114+ render (
115+ < MemoryRouter initialEntries = { [ "/prescription-list-current?nhsNumber=123456" ] } >
116+ < Routes >
117+ < Route path = "*" element = { < Harness includeQuery /> } />
118+ </ Routes >
119+ </ MemoryRouter >
120+ )
121+
122+ // With query in activeTabPath, startsWith should still match and allow navigation
93123 expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-current" )
124+ await userEvent . keyboard ( "{ArrowRight}" )
125+ await waitFor ( ( ) => {
126+ expect ( screen . getByTestId ( "current-path" ) ) . toHaveTextContent ( "/prescription-list-future" )
127+ } )
128+ } )
129+
130+ it ( "cleans up keydown listener on unmount" , async ( ) => {
131+ const { unmount} = render (
132+ < MemoryRouter initialEntries = { [ "/prescription-list-current" ] } >
133+ < Routes >
134+ < Route path = "*" element = { < Harness /> } />
135+ </ Routes >
136+ </ MemoryRouter >
137+ )
138+
139+ unmount ( )
140+ // Dispatching events after unmount should not change the path
141+ window . dispatchEvent ( new KeyboardEvent ( "keydown" , { key : "ArrowRight" } ) )
142+ // Nothing to assert about path change here since component is unmounted;
143+ // this test ensures no errors are thrown during cleanup.
144+ expect ( true ) . toBe ( true )
145+ } )
146+
147+ it ( "sets aria-selected and tabIndex correctly for active/inactive tabs" , ( ) => {
148+ render (
149+ < MemoryRouter initialEntries = { [ "/prescription-list-current" ] } >
150+ < Routes >
151+ < Route path = "*" element = { < Harness /> } />
152+ </ Routes >
153+ </ MemoryRouter >
154+ )
155+
156+ const currentTab = screen . getByTestId ( "eps-tab-heading /prescription-list-current" )
157+ const futureTab = screen . getByTestId ( "eps-tab-heading /prescription-list-future" )
158+
159+ expect ( currentTab ) . toHaveAttribute ( "aria-selected" , "true" )
160+ expect ( currentTab ) . toHaveAttribute ( "tabIndex" , "0" )
161+ expect ( futureTab ) . toHaveAttribute ( "aria-selected" , "false" )
162+ expect ( futureTab ) . toHaveAttribute ( "tabIndex" , "-1" )
94163 } )
95164
96165 it ( "applies the large variant class" , ( ) => {
0 commit comments