@@ -36,6 +36,22 @@ import {
3636 setupMockResizeObserver ,
3737} from "./test_utils" ;
3838
39+ const renderDatePickerWithRef = (
40+ props : React . ComponentProps < typeof DatePicker > ,
41+ ) => {
42+ let instance : DatePicker | null = null ;
43+ const result = render (
44+ < DatePicker
45+ ref = { ( node ) => {
46+ instance = node ;
47+ } }
48+ { ...props }
49+ /> ,
50+ ) ;
51+
52+ return { ...result , instance : instance as DatePicker | null } ;
53+ } ;
54+
3955function getSelectedDayNode ( container : HTMLElement ) {
4056 return (
4157 container . querySelector ( '.react-datepicker__day[tabindex="0"]' ) ?? undefined
@@ -5014,6 +5030,195 @@ describe("DatePicker", () => {
50145030 jest . useRealTimers ( ) ;
50155031 } ) ;
50165032
5033+ it ( "deferFocusInput cancels pending timeouts before focusing input" , ( ) => {
5034+ jest . useFakeTimers ( ) ;
5035+ const { instance } = renderDatePickerWithRef ( {
5036+ selected : newDate ( ) ,
5037+ onChange : ( ) => { } ,
5038+ } ) ;
5039+
5040+ expect ( instance ) . not . toBeNull ( ) ;
5041+
5042+ const setFocusSpy = jest
5043+ . spyOn ( instance as DatePicker , "setFocus" )
5044+ . mockImplementation ( ( ) => undefined ) ;
5045+
5046+ act ( ( ) => {
5047+ instance ?. deferFocusInput ( ) ;
5048+ instance ?. deferFocusInput ( ) ;
5049+ } ) ;
5050+
5051+ jest . advanceTimersByTime ( 1 ) ;
5052+
5053+ expect ( setFocusSpy ) . toHaveBeenCalledTimes ( 2 ) ;
5054+
5055+ setFocusSpy . mockRestore ( ) ;
5056+ jest . useRealTimers ( ) ;
5057+ } ) ;
5058+
5059+ it ( "clears ranges when changed date is null and start date exists" , ( ) => {
5060+ const onChange = jest . fn ( ) ;
5061+ const startDate = new Date ( "2024-01-15T00:00:00" ) ;
5062+ const { instance } = renderDatePickerWithRef ( {
5063+ inline : true ,
5064+ selectsRange : true ,
5065+ startDate,
5066+ endDate : null ,
5067+ selected : null ,
5068+ onChange,
5069+ } ) ;
5070+
5071+ act ( ( ) => {
5072+ instance ?. setSelected ( null ) ;
5073+ } ) ;
5074+
5075+ expect ( onChange ) . toHaveBeenCalledWith ( [ null , null ] , undefined ) ;
5076+ } ) ;
5077+
5078+ it ( "reports input errors when escaping with invalid preSelection" , ( ) => {
5079+ const onInputError = jest . fn ( ) ;
5080+ const { container, instance } = renderDatePickerWithRef ( {
5081+ selected : null ,
5082+ onChange : ( ) => { } ,
5083+ onInputError,
5084+ } ) ;
5085+
5086+ act ( ( ) => {
5087+ instance ?. setState ( { preSelection : "invalid-date" as unknown as Date } ) ;
5088+ } ) ;
5089+
5090+ const input = safeQuerySelector < HTMLInputElement > ( container , "input" ) ;
5091+ fireEvent . keyDown ( input , { key : "Escape" } ) ;
5092+
5093+ expect ( onInputError ) . toHaveBeenCalled ( ) ;
5094+ } ) ;
5095+
5096+ it ( "reports input errors when input key down completes with invalid preSelection" , ( ) => {
5097+ const onInputError = jest . fn ( ) ;
5098+ const { container, instance } = renderDatePickerWithRef ( {
5099+ selected : null ,
5100+ onChange : ( ) => { } ,
5101+ onInputError,
5102+ } ) ;
5103+
5104+ act ( ( ) => {
5105+ instance ?. setState ( { preSelection : "invalid-date" as unknown as Date } ) ;
5106+ } ) ;
5107+
5108+ const input = safeQuerySelector < HTMLInputElement > ( container , "input" ) ;
5109+ act ( ( ) => {
5110+ fireEvent . keyDown ( input , { key : "Tab" } ) ;
5111+ } ) ;
5112+
5113+ expect ( onInputError ) . toHaveBeenCalled ( ) ;
5114+ } ) ;
5115+
5116+ it ( "reports input errors when unsupported key is pressed in calendar grid" , ( ) => {
5117+ const onInputError = jest . fn ( ) ;
5118+ const { instance } = renderDatePickerWithRef ( {
5119+ selected : newDate ( ) ,
5120+ onChange : ( ) => { } ,
5121+ onInputError,
5122+ inline : true ,
5123+ } ) ;
5124+
5125+ act ( ( ) => {
5126+ instance ?. setState ( { preSelection : newDate ( ) } ) ;
5127+ } ) ;
5128+
5129+ act ( ( ) => {
5130+ instance ?. onDayKeyDown ( {
5131+ key : "A" ,
5132+ shiftKey : false ,
5133+ preventDefault : jest . fn ( ) ,
5134+ target : document . createElement ( "div" ) ,
5135+ } as unknown as React . KeyboardEvent < HTMLDivElement > ) ;
5136+ } ) ;
5137+
5138+ expect ( onInputError ) . toHaveBeenCalled ( ) ;
5139+ } ) ;
5140+
5141+ it ( "reports input errors when escape is pressed within the calendar grid" , ( ) => {
5142+ const onInputError = jest . fn ( ) ;
5143+ const { instance } = renderDatePickerWithRef ( {
5144+ selected : null ,
5145+ onChange : ( ) => { } ,
5146+ onInputError,
5147+ inline : true ,
5148+ } ) ;
5149+
5150+ act ( ( ) => {
5151+ instance ?. setState ( { preSelection : "invalid-date" as unknown as Date } ) ;
5152+ } ) ;
5153+
5154+ act ( ( ) => {
5155+ instance ?. onDayKeyDown ( {
5156+ key : "Escape" ,
5157+ shiftKey : false ,
5158+ preventDefault : jest . fn ( ) ,
5159+ target : document . createElement ( "div" ) ,
5160+ } as unknown as React . KeyboardEvent < HTMLDivElement > ) ;
5161+ } ) ;
5162+
5163+ expect ( onInputError ) . toHaveBeenCalled ( ) ;
5164+ } ) ;
5165+
5166+ describe ( "aria-live messaging" , ( ) => {
5167+ it ( "describes range selections" , ( ) => {
5168+ const startDate = new Date ( "2024-01-01T00:00:00" ) ;
5169+ const endDate = new Date ( "2024-01-02T00:00:00" ) ;
5170+ const { instance } = renderDatePickerWithRef ( {
5171+ selectsRange : true ,
5172+ startDate,
5173+ endDate,
5174+ selected : endDate ,
5175+ } ) ;
5176+
5177+ const message = instance ?. renderAriaLiveRegion ( ) ;
5178+ expect ( message ?. props . children ) . toContain ( "Selected start date" ) ;
5179+ } ) ;
5180+
5181+ it ( "describes time-only selections" , ( ) => {
5182+ const { instance } = renderDatePickerWithRef ( {
5183+ showTimeSelectOnly : true ,
5184+ selected : new Date ( "2024-01-01T12:00:00" ) ,
5185+ } ) ;
5186+
5187+ const message = instance ?. renderAriaLiveRegion ( ) ;
5188+ expect ( message ?. props . children ) . toContain ( "Selected time:" ) ;
5189+ } ) ;
5190+
5191+ it ( "describes year picker selections" , ( ) => {
5192+ const { instance } = renderDatePickerWithRef ( {
5193+ showYearPicker : true ,
5194+ selected : new Date ( "2024-01-01T00:00:00" ) ,
5195+ } ) ;
5196+
5197+ const message = instance ?. renderAriaLiveRegion ( ) ;
5198+ expect ( message ?. props . children ) . toContain ( "Selected year:" ) ;
5199+ } ) ;
5200+
5201+ it ( "describes month-year picker selections" , ( ) => {
5202+ const { instance } = renderDatePickerWithRef ( {
5203+ showMonthYearPicker : true ,
5204+ selected : new Date ( "2024-03-01T00:00:00" ) ,
5205+ } ) ;
5206+
5207+ const message = instance ?. renderAriaLiveRegion ( ) ;
5208+ expect ( message ?. props . children ) . toContain ( "Selected month:" ) ;
5209+ } ) ;
5210+
5211+ it ( "describes quarter picker selections" , ( ) => {
5212+ const { instance } = renderDatePickerWithRef ( {
5213+ showQuarterYearPicker : true ,
5214+ selected : new Date ( "2024-06-01T00:00:00" ) ,
5215+ } ) ;
5216+
5217+ const message = instance ?. renderAriaLiveRegion ( ) ;
5218+ expect ( message ?. props . children ) . toContain ( "Selected quarter:" ) ;
5219+ } ) ;
5220+ } ) ;
5221+
50175222 it ( "should handle focus on year dropdown" , ( ) => {
50185223 const { container } = render (
50195224 < DatePicker
0 commit comments