@@ -34,8 +34,25 @@ describe("YearDropdown", () => {
3434 } ) ;
3535
3636 describe ( "scroll mode" , ( ) => {
37+ const selectedYear = 2015 ;
38+
3739 beforeEach ( function ( ) {
38- yearDropdown = getYearDropdown ( ) ;
40+ yearDropdown = getYearDropdown ( {
41+ year : selectedYear ,
42+ } ) ;
43+ } ) ;
44+
45+ it ( "read view has correct ARIA attributes and toggles aria-expanded" , ( ) => {
46+ const yearReadView = safeQuerySelector < HTMLButtonElement > (
47+ yearDropdown ,
48+ ".react-datepicker__year-read-view" ,
49+ ) ;
50+ expect ( yearReadView . getAttribute ( "aria-haspopup" ) ) . toBe ( "listbox" ) ;
51+ expect ( yearReadView . getAttribute ( "aria-label" ) ) . toBe ( "Select Year" ) ;
52+ expect ( yearReadView . getAttribute ( "aria-expanded" ) ) . toBe ( "false" ) ;
53+
54+ fireEvent . click ( yearReadView ) ;
55+ expect ( yearReadView . getAttribute ( "aria-expanded" ) ) . toBe ( "true" ) ;
3956 } ) ;
4057
4158 it ( "shows the selected year in the initial view" , ( ) => {
@@ -138,15 +155,109 @@ describe("YearDropdown", () => {
138155 fireEvent . keyDown ( document . activeElement ! , { key : "Enter" } ) ;
139156 expect ( lastOnChangeValue ) . toEqual ( 2016 ) ;
140157 } ) ;
158+
159+ it ( "options expose correct ARIA attributes" , ( ) => {
160+ const yearReadView = safeQuerySelector (
161+ yearDropdown ,
162+ ".react-datepicker__year-read-view" ,
163+ ) ;
164+ fireEvent . click ( yearReadView ) ;
165+
166+ const yearOptions = safeQuerySelectorAll < HTMLDivElement > (
167+ yearDropdown ,
168+ ".react-datepicker__year-option" ,
169+ 7 ,
170+ ) ;
171+
172+ // Find the selected year option by text
173+ const selected = yearOptions . find ( ( el ) =>
174+ el . textContent ?. includes ( selectedYear . toString ( ) ) ,
175+ ) ! ;
176+ expect ( selected . getAttribute ( "aria-selected" ) ) . toBe ( "true" ) ;
177+ expect ( selected . getAttribute ( "aria-label" ) ) . toBe (
178+ `Select Year ${ selectedYear } ` ,
179+ ) ;
180+
181+ // Find a non-selected year option and ensure aria-selected is not present
182+ const nonSelected =
183+ yearOptions . find (
184+ ( el ) => el . textContent ?. trim ( ) === ( selectedYear - 1 ) . toString ( ) ,
185+ ) ??
186+ yearOptions . find (
187+ ( el ) => el . textContent ?. trim ( ) === ( selectedYear + 1 ) . toString ( ) ,
188+ ) ;
189+ expect ( nonSelected ) . toBeTruthy ( ) ;
190+ expect ( nonSelected ! . getAttribute ( "aria-selected" ) ) . toBeNull ( ) ;
191+ const nonSelectedYear = nonSelected ! . textContent ! . trim ( ) ;
192+ expect ( nonSelected ! . getAttribute ( "aria-label" ) ) . toBe (
193+ `Select Year ${ nonSelectedYear } ` ,
194+ ) ;
195+ } ) ;
196+
197+ it ( "pressing Escape closes the dropdown (onCancel)" , ( ) => {
198+ const yearReadView = safeQuerySelector (
199+ yearDropdown ,
200+ ".react-datepicker__year-read-view" ,
201+ ) ;
202+ fireEvent . click ( yearReadView ) ;
203+
204+ const yearOptions = safeQuerySelectorAll < HTMLDivElement > (
205+ yearDropdown ,
206+ ".react-datepicker__year-option" ,
207+ 7 ,
208+ ) ;
209+ // Focus the selected option and press Escape
210+ const selected = yearOptions . find ( ( el ) =>
211+ el . textContent ?. includes ( "2015" ) ,
212+ ) ! ;
213+ selected . focus ( ) ;
214+ fireEvent . keyDown ( selected , { key : "Escape" } ) ;
215+
216+ const optionsView = yearDropdown ?. querySelectorAll (
217+ "react-datepicker__year-dropdown" ,
218+ ) ;
219+ expect ( optionsView ) . toHaveLength ( 0 ) ;
220+ } ) ;
221+
222+ it ( "clicking 'Show later years' shifts the years forward by one" , ( ) => {
223+ const yearReadView = safeQuerySelector (
224+ yearDropdown ,
225+ ".react-datepicker__year-read-view" ,
226+ ) ;
227+ fireEvent . click ( yearReadView ) ;
228+
229+ // The first option is the 'Show later years' control when no maxDate is provided
230+ const yearOptionsBefore = safeQuerySelectorAll < HTMLDivElement > (
231+ yearDropdown ,
232+ ".react-datepicker__year-option" ,
233+ 7 ,
234+ ) ;
235+ const firstYearBefore = Number (
236+ yearOptionsBefore [ 1 ] ! . textContent ?. trim ( ) , // index 0 is the navigation control
237+ ) ;
238+
239+ // Click the navigation control to shift years
240+ fireEvent . click ( yearOptionsBefore [ 0 ] ! ) ;
241+
242+ const yearOptionsAfter = safeQuerySelectorAll < HTMLDivElement > (
243+ yearDropdown ,
244+ ".react-datepicker__year-option" ,
245+ 7 ,
246+ ) ;
247+ const firstYearAfter = Number ( yearOptionsAfter [ 1 ] ! . textContent ?. trim ( ) ) ;
248+ expect ( firstYearAfter ) . toBe ( firstYearBefore + 1 ) ;
249+ } ) ;
141250 } ) ;
142251
143252 describe ( "select mode" , ( ) => {
253+ const selectedYear = 2015 ;
254+
144255 it ( "renders a select with default year range options" , ( ) => {
145256 yearDropdown = getYearDropdown ( { dropdownMode : "select" } ) ;
146257 const select : NodeListOf < HTMLSelectElement > =
147258 yearDropdown . querySelectorAll ( ".react-datepicker__year-select" ) ;
148259 expect ( select ) . toHaveLength ( 1 ) ;
149- expect ( select [ 0 ] ?. value ) . toBe ( "2015" ) ;
260+ expect ( select [ 0 ] ?. value ) . toBe ( selectedYear . toString ( ) ) ;
150261
151262 const options = select [ 0 ] ?. querySelectorAll ( "option" ) ?? [ ] ;
152263 expect ( Array . from ( options ) . map ( ( o ) => o . textContent ) ) . toEqual (
@@ -206,5 +317,32 @@ describe("YearDropdown", () => {
206317 expect ( onSelectSpy ) . toHaveBeenCalledTimes ( 1 ) ;
207318 expect ( setOpenSpy ) . toHaveBeenCalledTimes ( 1 ) ;
208319 } ) ;
320+
321+ it ( "select and options expose correct ARIA attributes" , ( ) => {
322+ yearDropdown = getYearDropdown ( { dropdownMode : "select" } ) ;
323+ const select : HTMLSelectElement =
324+ yearDropdown . querySelector ( ".react-datepicker__year-select" ) ??
325+ new HTMLSelectElement ( ) ;
326+
327+ expect ( select . getAttribute ( "aria-label" ) ) . toBe ( "Select Year" ) ;
328+
329+ const options = Array . from (
330+ select . querySelectorAll ( "option" ) ,
331+ ) as HTMLOptionElement [ ] ;
332+ const opt2015 = options . find ( ( o ) => o . value === selectedYear . toString ( ) ) ! ;
333+ const opt2014 = options . find (
334+ ( o ) => o . value === ( selectedYear - 1 ) . toString ( ) ,
335+ ) ! ;
336+
337+ expect ( opt2015 . getAttribute ( "aria-selected" ) ) . toBe ( "true" ) ;
338+ expect ( opt2015 . getAttribute ( "aria-label" ) ) . toBe (
339+ `Select Year ${ selectedYear } ` ,
340+ ) ;
341+
342+ expect ( opt2014 . getAttribute ( "aria-selected" ) ) . toBe ( "false" ) ;
343+ expect ( opt2014 . getAttribute ( "aria-label" ) ) . toBe (
344+ `Select Year ${ selectedYear - 1 } ` ,
345+ ) ;
346+ } ) ;
209347 } ) ;
210348} ) ;
0 commit comments