@wordpress/components 29.11.0 → 29.13.1-next.719a03cbe.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/build/box-control/input-control.js +2 -2
- package/build/box-control/input-control.js.map +1 -1
- package/build/calendar/date-calendar/index.js +60 -0
- package/build/calendar/date-calendar/index.js.map +1 -0
- package/build/calendar/date-range-calendar/index.js +168 -0
- package/build/calendar/date-range-calendar/index.js.map +1 -0
- package/build/calendar/index.js +27 -0
- package/build/calendar/index.js.map +1 -0
- package/build/calendar/types.js +6 -0
- package/build/calendar/types.js.map +1 -0
- package/build/calendar/utils/constants.js +68 -0
- package/build/calendar/utils/constants.js.map +1 -0
- package/build/calendar/utils/day-cell.js +137 -0
- package/build/calendar/utils/day-cell.js.map +1 -0
- package/build/calendar/utils/misc.js +10 -0
- package/build/calendar/utils/misc.js.map +1 -0
- package/build/calendar/utils/use-controlled-value.js +58 -0
- package/build/calendar/utils/use-controlled-value.js.map +1 -0
- package/build/calendar/utils/use-localization-props.js +162 -0
- package/build/calendar/utils/use-localization-props.js.map +1 -0
- package/build/custom-gradient-picker/gradient-bar/control-points.js +1 -1
- package/build/custom-gradient-picker/gradient-bar/control-points.js.map +1 -1
- package/build/custom-select-control-v2/custom-select.js +3 -3
- package/build/custom-select-control-v2/custom-select.js.map +1 -1
- package/build/date-time/date/index.js +1 -1
- package/build/date-time/date/index.js.map +1 -1
- package/build/form-file-upload/index.js +4 -6
- package/build/form-file-upload/index.js.map +1 -1
- package/build/form-token-field/index.js +11 -1
- package/build/form-token-field/index.js.map +1 -1
- package/build/form-token-field/token.js +1 -1
- package/build/form-token-field/token.js.map +1 -1
- package/build/index.js +19 -0
- package/build/index.js.map +1 -1
- package/build/mobile/bottom-sheet/cell.native.js +2 -2
- package/build/mobile/bottom-sheet/cell.native.js.map +1 -1
- package/build/mobile/image/index.native.js +1 -1
- package/build/mobile/image/index.native.js.map +1 -1
- package/build/mobile/link-picker/index.native.js +1 -1
- package/build/mobile/link-picker/index.native.js.map +1 -1
- package/build/navigation/menu/menu-title-search.js +1 -1
- package/build/navigation/menu/menu-title-search.js.map +1 -1
- package/build/palette-edit/index.js +4 -4
- package/build/palette-edit/index.js.map +1 -1
- package/build-module/box-control/input-control.js +2 -2
- package/build-module/box-control/input-control.js.map +1 -1
- package/build-module/calendar/date-calendar/index.js +51 -0
- package/build-module/calendar/date-calendar/index.js.map +1 -0
- package/build-module/calendar/date-range-calendar/index.js +157 -0
- package/build-module/calendar/date-range-calendar/index.js.map +1 -0
- package/build-module/calendar/index.js +4 -0
- package/build-module/calendar/index.js.map +1 -0
- package/build-module/calendar/types.js +2 -0
- package/build-module/calendar/types.js.map +1 -0
- package/build-module/calendar/utils/constants.js +61 -0
- package/build-module/calendar/utils/constants.js.map +1 -0
- package/build-module/calendar/utils/day-cell.js +131 -0
- package/build-module/calendar/utils/day-cell.js.map +1 -0
- package/build-module/calendar/utils/misc.js +4 -0
- package/build-module/calendar/utils/misc.js.map +1 -0
- package/build-module/calendar/utils/use-controlled-value.js +51 -0
- package/build-module/calendar/utils/use-controlled-value.js.map +1 -0
- package/build-module/calendar/utils/use-localization-props.js +154 -0
- package/build-module/calendar/utils/use-localization-props.js.map +1 -0
- package/build-module/custom-gradient-picker/gradient-bar/control-points.js +1 -1
- package/build-module/custom-gradient-picker/gradient-bar/control-points.js.map +1 -1
- package/build-module/custom-select-control-v2/custom-select.js +4 -4
- package/build-module/custom-select-control-v2/custom-select.js.map +1 -1
- package/build-module/date-time/date/index.js +1 -1
- package/build-module/date-time/date/index.js.map +1 -1
- package/build-module/form-file-upload/index.js +4 -6
- package/build-module/form-file-upload/index.js.map +1 -1
- package/build-module/form-token-field/index.js +11 -1
- package/build-module/form-token-field/index.js.map +1 -1
- package/build-module/form-token-field/token.js +1 -1
- package/build-module/form-token-field/token.js.map +1 -1
- package/build-module/index.js +1 -0
- package/build-module/index.js.map +1 -1
- package/build-module/mobile/bottom-sheet/cell.native.js +2 -2
- package/build-module/mobile/bottom-sheet/cell.native.js.map +1 -1
- package/build-module/mobile/image/index.native.js +1 -1
- package/build-module/mobile/image/index.native.js.map +1 -1
- package/build-module/mobile/link-picker/index.native.js +1 -1
- package/build-module/mobile/link-picker/index.native.js.map +1 -1
- package/build-module/navigation/menu/menu-title-search.js +1 -1
- package/build-module/navigation/menu/menu-title-search.js.map +1 -1
- package/build-module/palette-edit/index.js +4 -4
- package/build-module/palette-edit/index.js.map +1 -1
- package/build-style/style-rtl.css +358 -5
- package/build-style/style.css +358 -5
- package/build-types/box-control/input-control.d.ts.map +1 -1
- package/build-types/box-control/utils.d.ts +7 -7
- package/build-types/calendar/date-calendar/index.d.ts +11 -0
- package/build-types/calendar/date-calendar/index.d.ts.map +1 -0
- package/build-types/calendar/date-range-calendar/index.d.ts +14 -0
- package/build-types/calendar/date-range-calendar/index.d.ts.map +1 -0
- package/build-types/calendar/index.d.ts +4 -0
- package/build-types/calendar/index.d.ts.map +1 -0
- package/build-types/calendar/stories/date-calendar.story.d.ts +16 -0
- package/build-types/calendar/stories/date-calendar.story.d.ts.map +1 -0
- package/build-types/calendar/stories/date-range-calendar.story.d.ts +16 -0
- package/build-types/calendar/stories/date-range-calendar.story.d.ts.map +1 -0
- package/build-types/calendar/test/__utils__/index.d.ts +10 -0
- package/build-types/calendar/test/__utils__/index.d.ts.map +1 -0
- package/build-types/calendar/test/date-calendar.d.ts +2 -0
- package/build-types/calendar/test/date-calendar.d.ts.map +1 -0
- package/build-types/calendar/test/date-range-calendar.d.ts +2 -0
- package/build-types/calendar/test/date-range-calendar.d.ts.map +1 -0
- package/build-types/calendar/types.d.ts +317 -0
- package/build-types/calendar/types.d.ts.map +1 -0
- package/build-types/calendar/utils/constants.d.ts +52 -0
- package/build-types/calendar/utils/constants.d.ts.map +1 -0
- package/build-types/calendar/utils/day-cell.d.ts +21 -0
- package/build-types/calendar/utils/day-cell.d.ts.map +1 -0
- package/build-types/calendar/utils/misc.d.ts +2 -0
- package/build-types/calendar/utils/misc.d.ts.map +1 -0
- package/build-types/calendar/utils/use-controlled-value.d.ts +27 -0
- package/build-types/calendar/utils/use-controlled-value.d.ts.map +1 -0
- package/build-types/calendar/utils/use-localization-props.d.ts +64 -0
- package/build-types/calendar/utils/use-localization-props.d.ts.map +1 -0
- package/build-types/custom-gradient-picker/constants.d.ts +6 -3
- package/build-types/custom-gradient-picker/constants.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/custom-select.d.ts.map +1 -1
- package/build-types/dimension-control/sizes.d.ts +15 -3
- package/build-types/dimension-control/sizes.d.ts.map +1 -1
- package/build-types/font-size-picker/constants.d.ts +2 -2
- package/build-types/font-size-picker/constants.d.ts.map +1 -1
- package/build-types/form-file-upload/index.d.ts.map +1 -1
- package/build-types/form-token-field/index.d.ts.map +1 -1
- package/build-types/index.d.ts +1 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/popover/overlay-middlewares.d.ts +6 -1
- package/build-types/popover/overlay-middlewares.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/box-control/input-control.tsx +14 -5
- package/src/calendar/date-calendar/README.md +250 -0
- package/src/calendar/date-calendar/index.tsx +55 -0
- package/src/calendar/date-range-calendar/README.md +287 -0
- package/src/calendar/date-range-calendar/index.tsx +203 -0
- package/src/calendar/index.tsx +3 -0
- package/src/calendar/stories/date-calendar.story.tsx +221 -0
- package/src/calendar/stories/date-range-calendar.story.tsx +230 -0
- package/src/calendar/style.scss +431 -0
- package/src/calendar/test/__utils__/index.ts +56 -0
- package/src/calendar/test/date-calendar.tsx +975 -0
- package/src/calendar/test/date-range-calendar.tsx +1701 -0
- package/src/calendar/types.ts +342 -0
- package/src/calendar/utils/constants.ts +62 -0
- package/src/calendar/utils/day-cell.tsx +133 -0
- package/src/calendar/utils/misc.ts +3 -0
- package/src/calendar/utils/use-controlled-value.ts +61 -0
- package/src/calendar/utils/use-localization-props.ts +169 -0
- package/src/circular-option-picker/stories/index.story.tsx +2 -2
- package/src/custom-gradient-picker/gradient-bar/control-points.tsx +1 -1
- package/src/custom-select-control-v2/custom-select.tsx +6 -3
- package/src/date-time/date/index.tsx +1 -1
- package/src/form-file-upload/index.tsx +6 -12
- package/src/form-token-field/index.tsx +12 -1
- package/src/form-token-field/token.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/mobile/bottom-sheet/cell.native.js +2 -2
- package/src/mobile/image/index.native.js +1 -1
- package/src/mobile/link-picker/index.native.js +1 -1
- package/src/navigation/menu/menu-title-search.tsx +1 -1
- package/src/palette-edit/index.tsx +4 -4
- package/src/select-control/style.scss +0 -6
- package/src/style.scss +1 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,1701 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen, within, renderHook } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
import {
|
|
7
|
+
startOfDay,
|
|
8
|
+
startOfWeek,
|
|
9
|
+
startOfMonth,
|
|
10
|
+
endOfWeek,
|
|
11
|
+
addDays,
|
|
12
|
+
subDays,
|
|
13
|
+
addWeeks,
|
|
14
|
+
addMonths,
|
|
15
|
+
subMonths,
|
|
16
|
+
subYears,
|
|
17
|
+
addHours,
|
|
18
|
+
} from 'date-fns';
|
|
19
|
+
import { ar } from 'date-fns/locale';
|
|
20
|
+
/**
|
|
21
|
+
* WordPress dependencies
|
|
22
|
+
*/
|
|
23
|
+
import { useState } from '@wordpress/element';
|
|
24
|
+
/**
|
|
25
|
+
* Internal dependencies
|
|
26
|
+
*/
|
|
27
|
+
import { usePreviewRange, DateRangeCalendar } from '../date-range-calendar';
|
|
28
|
+
import { TZDate } from '../';
|
|
29
|
+
import { getDateButton, getDateCell, monthNameFormatter } from './__utils__';
|
|
30
|
+
import type { DateRange, DateRangeCalendarProps } from '../types';
|
|
31
|
+
|
|
32
|
+
const UncontrolledDateRangeCalendar = (
|
|
33
|
+
props: DateRangeCalendarProps & {
|
|
34
|
+
initialSelected?: DateRange | undefined | null;
|
|
35
|
+
initialMonth?: Date | undefined;
|
|
36
|
+
}
|
|
37
|
+
) => {
|
|
38
|
+
return (
|
|
39
|
+
<DateRangeCalendar
|
|
40
|
+
{ ...props }
|
|
41
|
+
defaultSelected={ props.initialSelected ?? undefined }
|
|
42
|
+
defaultMonth={ props.initialMonth }
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const ControlledDateRangeCalendar = (
|
|
48
|
+
props: DateRangeCalendarProps & {
|
|
49
|
+
initialSelected?: DateRange | undefined | null;
|
|
50
|
+
initialMonth?: Date | undefined;
|
|
51
|
+
}
|
|
52
|
+
) => {
|
|
53
|
+
const [ selected, setSelected ] = useState< DateRange | undefined | null >(
|
|
54
|
+
props.initialSelected
|
|
55
|
+
);
|
|
56
|
+
const [ month, setMonth ] = useState< Date | undefined >(
|
|
57
|
+
props.initialMonth
|
|
58
|
+
);
|
|
59
|
+
return (
|
|
60
|
+
<DateRangeCalendar
|
|
61
|
+
{ ...props }
|
|
62
|
+
selected={ selected ?? null }
|
|
63
|
+
onSelect={ ( ...args ) => {
|
|
64
|
+
setSelected( args[ 0 ] );
|
|
65
|
+
props.onSelect?.( ...args );
|
|
66
|
+
} }
|
|
67
|
+
month={ month }
|
|
68
|
+
onMonthChange={ ( newMonth ) => {
|
|
69
|
+
setMonth( newMonth );
|
|
70
|
+
props.onMonthChange?.( newMonth );
|
|
71
|
+
} }
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function setupUserEvent() {
|
|
77
|
+
// The `advanceTimersByTime` is needed since we're using jest
|
|
78
|
+
// fake timers to simulate a fixed date for tests.
|
|
79
|
+
const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime } );
|
|
80
|
+
return user;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe( 'DateRangeCalendar', () => {
|
|
84
|
+
let today: Date;
|
|
85
|
+
let tomorrow: Date;
|
|
86
|
+
let yesterday: Date;
|
|
87
|
+
let currentMonth: Date;
|
|
88
|
+
let nextMonth: Date;
|
|
89
|
+
let nextNextMonth: Date;
|
|
90
|
+
let prevMonth: Date;
|
|
91
|
+
let prevPrevMonth: Date;
|
|
92
|
+
|
|
93
|
+
beforeAll( () => {
|
|
94
|
+
jest.useFakeTimers();
|
|
95
|
+
// For consistent tests, set the system time to a fixed date:
|
|
96
|
+
// Thursday, May 15, 2025, 20:00 UTC
|
|
97
|
+
jest.setSystemTime( 1747339200000 );
|
|
98
|
+
|
|
99
|
+
today = startOfDay( new Date() );
|
|
100
|
+
tomorrow = addDays( today, 1 );
|
|
101
|
+
yesterday = subDays( today, 1 );
|
|
102
|
+
currentMonth = startOfMonth( today );
|
|
103
|
+
nextMonth = startOfMonth( addMonths( today, 1 ) );
|
|
104
|
+
nextNextMonth = startOfMonth( addMonths( today, 2 ) );
|
|
105
|
+
prevMonth = startOfMonth( subMonths( today, 1 ) );
|
|
106
|
+
prevPrevMonth = startOfMonth( subMonths( today, 2 ) );
|
|
107
|
+
} );
|
|
108
|
+
|
|
109
|
+
afterAll( () => {
|
|
110
|
+
jest.useRealTimers();
|
|
111
|
+
} );
|
|
112
|
+
|
|
113
|
+
describe( 'Semantics and basic behavior', () => {
|
|
114
|
+
it( 'should apply the correct roles, semantics and attributes', async () => {
|
|
115
|
+
render( <DateRangeCalendar /> );
|
|
116
|
+
|
|
117
|
+
expect(
|
|
118
|
+
screen.getByRole( 'application', {
|
|
119
|
+
name: 'Date range calendar',
|
|
120
|
+
} )
|
|
121
|
+
).toBeVisible();
|
|
122
|
+
|
|
123
|
+
const tableGrid = screen.getByRole( 'grid', {
|
|
124
|
+
name: monthNameFormatter( 'en-US' ).format( today ),
|
|
125
|
+
} );
|
|
126
|
+
expect( tableGrid ).toBeVisible();
|
|
127
|
+
expect( tableGrid ).toHaveAttribute(
|
|
128
|
+
'aria-multiselectable',
|
|
129
|
+
'true'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const todayButton = getDateButton( today );
|
|
133
|
+
expect( todayButton ).toBeVisible();
|
|
134
|
+
expect( todayButton ).toHaveAccessibleName( /today/i );
|
|
135
|
+
} );
|
|
136
|
+
|
|
137
|
+
it( 'should show multiple months at once via the `numberOfMonths` prop', () => {
|
|
138
|
+
render( <DateRangeCalendar numberOfMonths={ 2 } /> );
|
|
139
|
+
|
|
140
|
+
const grids = screen.getAllByRole( 'grid' );
|
|
141
|
+
expect( grids ).toHaveLength( 2 );
|
|
142
|
+
expect( grids[ 0 ] ).toHaveAccessibleName(
|
|
143
|
+
monthNameFormatter( 'en-US' ).format( today )
|
|
144
|
+
);
|
|
145
|
+
expect( grids[ 1 ] ).toHaveAccessibleName(
|
|
146
|
+
monthNameFormatter( 'en-US' ).format( nextMonth )
|
|
147
|
+
);
|
|
148
|
+
} );
|
|
149
|
+
} );
|
|
150
|
+
|
|
151
|
+
describe( 'Date selection', () => {
|
|
152
|
+
it( 'should select an initial date range in uncontrolled mode via the `defaultSelected` prop', () => {
|
|
153
|
+
const dateRange = { from: today, to: tomorrow };
|
|
154
|
+
render( <DateRangeCalendar defaultSelected={ dateRange } /> );
|
|
155
|
+
|
|
156
|
+
expect( getDateCell( today, { selected: true } ) ).toBeVisible();
|
|
157
|
+
expect( getDateCell( tomorrow, { selected: true } ) ).toBeVisible();
|
|
158
|
+
|
|
159
|
+
const todayButton = getDateButton( today );
|
|
160
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
161
|
+
expect( todayButton ).toBeVisible();
|
|
162
|
+
expect( todayButton ).toHaveAccessibleName( /selected/i );
|
|
163
|
+
expect( tomorrowButton ).toBeVisible();
|
|
164
|
+
expect( tomorrowButton ).toHaveAccessibleName( /selected/i );
|
|
165
|
+
} );
|
|
166
|
+
|
|
167
|
+
it( 'should select an initial date range in controlled mode via the `selected` prop', () => {
|
|
168
|
+
const defaultRange = { from: yesterday, to: today };
|
|
169
|
+
const controlledRange = { from: today, to: tomorrow };
|
|
170
|
+
|
|
171
|
+
// Note: the `defaultSelected` prop is ignored when the `selected` prop is set.
|
|
172
|
+
render(
|
|
173
|
+
<DateRangeCalendar
|
|
174
|
+
defaultSelected={ defaultRange }
|
|
175
|
+
selected={ controlledRange }
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect( getDateCell( today, { selected: true } ) ).toBeVisible();
|
|
180
|
+
expect( getDateCell( tomorrow, { selected: true } ) ).toBeVisible();
|
|
181
|
+
|
|
182
|
+
const todayButton = getDateButton( today );
|
|
183
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
184
|
+
expect( todayButton ).toBeVisible();
|
|
185
|
+
expect( todayButton ).toHaveAccessibleName( /selected/i );
|
|
186
|
+
expect( tomorrowButton ).toBeVisible();
|
|
187
|
+
expect( tomorrowButton ).toHaveAccessibleName( /selected/i );
|
|
188
|
+
} );
|
|
189
|
+
|
|
190
|
+
it( 'should have no date selected in uncontrolled mode when the `selected` prop is set to `undefined`', () => {
|
|
191
|
+
render( <DateRangeCalendar /> );
|
|
192
|
+
|
|
193
|
+
expect(
|
|
194
|
+
screen.queryByRole( 'gridcell', { selected: true } )
|
|
195
|
+
).not.toBeInTheDocument();
|
|
196
|
+
expect(
|
|
197
|
+
screen.queryByRole( 'button', { name: /selected/i } )
|
|
198
|
+
).not.toBeInTheDocument();
|
|
199
|
+
} );
|
|
200
|
+
|
|
201
|
+
it( 'should have no date selected in controlled mode when the `selected` prop is set to `null`', () => {
|
|
202
|
+
const defaultRange = { from: today, to: tomorrow };
|
|
203
|
+
|
|
204
|
+
// Note: the `defaultSelected` prop is ignored when the `selected` prop is set.
|
|
205
|
+
render(
|
|
206
|
+
<DateRangeCalendar
|
|
207
|
+
defaultSelected={ defaultRange }
|
|
208
|
+
selected={ null }
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
expect(
|
|
213
|
+
screen.queryByRole( 'gridcell', { selected: true } )
|
|
214
|
+
).not.toBeInTheDocument();
|
|
215
|
+
expect(
|
|
216
|
+
screen.queryByRole( 'button', { name: /selected/i } )
|
|
217
|
+
).not.toBeInTheDocument();
|
|
218
|
+
} );
|
|
219
|
+
|
|
220
|
+
it( 'should select a date in uncontrolled mode via the `defaultSelected` prop even if the date is disabled`', () => {
|
|
221
|
+
const defaultRange = { from: today, to: tomorrow };
|
|
222
|
+
|
|
223
|
+
render(
|
|
224
|
+
<DateRangeCalendar
|
|
225
|
+
defaultSelected={ defaultRange }
|
|
226
|
+
disabled={ defaultRange }
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
expect( getDateCell( today, { selected: true } ) ).toBeVisible();
|
|
231
|
+
expect( getDateCell( tomorrow, { selected: true } ) ).toBeVisible();
|
|
232
|
+
|
|
233
|
+
const todayButton = getDateButton( today );
|
|
234
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
235
|
+
expect( todayButton ).toBeVisible();
|
|
236
|
+
expect( todayButton ).toBeDisabled();
|
|
237
|
+
expect( todayButton ).toHaveAccessibleName( /selected/i );
|
|
238
|
+
expect( tomorrowButton ).toBeVisible();
|
|
239
|
+
expect( tomorrowButton ).toHaveAccessibleName( /selected/i );
|
|
240
|
+
expect( tomorrowButton ).toBeDisabled();
|
|
241
|
+
} );
|
|
242
|
+
|
|
243
|
+
it( 'should select a date in controlled mode via the `selected` prop even if the date is disabled`', () => {
|
|
244
|
+
const defaultRange = { from: today, to: tomorrow };
|
|
245
|
+
|
|
246
|
+
render(
|
|
247
|
+
<DateRangeCalendar
|
|
248
|
+
selected={ defaultRange }
|
|
249
|
+
disabled={ defaultRange }
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect( getDateCell( today, { selected: true } ) ).toBeVisible();
|
|
254
|
+
expect( getDateCell( tomorrow, { selected: true } ) ).toBeVisible();
|
|
255
|
+
|
|
256
|
+
const todayButton = getDateButton( today );
|
|
257
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
258
|
+
expect( todayButton ).toBeVisible();
|
|
259
|
+
expect( todayButton ).toBeDisabled();
|
|
260
|
+
expect( todayButton ).toHaveAccessibleName( /selected/i );
|
|
261
|
+
expect( tomorrowButton ).toBeVisible();
|
|
262
|
+
expect( tomorrowButton ).toHaveAccessibleName( /selected/i );
|
|
263
|
+
expect( tomorrowButton ).toBeDisabled();
|
|
264
|
+
} );
|
|
265
|
+
|
|
266
|
+
describe.each( [
|
|
267
|
+
[ 'Uncontrolled', UncontrolledDateRangeCalendar ],
|
|
268
|
+
[ 'Controlled', ControlledDateRangeCalendar ],
|
|
269
|
+
] )( '[`%s`]', ( _mode, Component ) => {
|
|
270
|
+
it( 'should start selecting a range when a date button is clicked', async () => {
|
|
271
|
+
const user = setupUserEvent();
|
|
272
|
+
const onSelect = jest.fn();
|
|
273
|
+
|
|
274
|
+
render( <Component onSelect={ onSelect } /> );
|
|
275
|
+
|
|
276
|
+
const todayButton = getDateButton( today );
|
|
277
|
+
await user.click( todayButton );
|
|
278
|
+
|
|
279
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
280
|
+
expect( onSelect ).toHaveBeenCalledWith(
|
|
281
|
+
{ from: today, to: today },
|
|
282
|
+
today,
|
|
283
|
+
expect.objectContaining( { today: true } ),
|
|
284
|
+
expect.objectContaining( {
|
|
285
|
+
type: 'click',
|
|
286
|
+
target: todayButton,
|
|
287
|
+
} )
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
expect(
|
|
291
|
+
getDateCell( today, { selected: true } )
|
|
292
|
+
).toBeVisible();
|
|
293
|
+
} );
|
|
294
|
+
|
|
295
|
+
it( 'should complete a range selection when a second date button is clicked', async () => {
|
|
296
|
+
const user = setupUserEvent();
|
|
297
|
+
const onSelect = jest.fn();
|
|
298
|
+
|
|
299
|
+
render( <Component onSelect={ onSelect } /> );
|
|
300
|
+
|
|
301
|
+
const todayButton = getDateButton( today );
|
|
302
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
303
|
+
|
|
304
|
+
// First click - start range
|
|
305
|
+
await user.click( todayButton );
|
|
306
|
+
|
|
307
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
308
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
309
|
+
{ from: today, to: today },
|
|
310
|
+
today,
|
|
311
|
+
expect.objectContaining( { today: true } ),
|
|
312
|
+
expect.objectContaining( {
|
|
313
|
+
type: 'click',
|
|
314
|
+
target: todayButton,
|
|
315
|
+
} )
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Second click - complete range
|
|
319
|
+
await user.click( tomorrowButton );
|
|
320
|
+
|
|
321
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
322
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
323
|
+
{ from: today, to: tomorrow },
|
|
324
|
+
tomorrow,
|
|
325
|
+
expect.objectContaining( { today: false } ),
|
|
326
|
+
expect.objectContaining( {
|
|
327
|
+
type: 'click',
|
|
328
|
+
target: tomorrowButton,
|
|
329
|
+
} )
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
expect(
|
|
333
|
+
getDateCell( today, { selected: true } )
|
|
334
|
+
).toBeVisible();
|
|
335
|
+
expect(
|
|
336
|
+
getDateCell( tomorrow, { selected: true } )
|
|
337
|
+
).toBeVisible();
|
|
338
|
+
} );
|
|
339
|
+
|
|
340
|
+
it( 'should handle selecting dates in reverse order (end date first)', async () => {
|
|
341
|
+
const user = setupUserEvent();
|
|
342
|
+
const onSelect = jest.fn();
|
|
343
|
+
|
|
344
|
+
render( <Component onSelect={ onSelect } /> );
|
|
345
|
+
|
|
346
|
+
// First click on tomorrow
|
|
347
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
348
|
+
await user.click( tomorrowButton );
|
|
349
|
+
|
|
350
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
351
|
+
expect( onSelect ).toHaveBeenCalledWith(
|
|
352
|
+
{ from: tomorrow, to: tomorrow },
|
|
353
|
+
tomorrow,
|
|
354
|
+
expect.objectContaining( { today: false } ),
|
|
355
|
+
expect.objectContaining( {
|
|
356
|
+
type: 'click',
|
|
357
|
+
target: tomorrowButton,
|
|
358
|
+
} )
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
// Second click on today (earlier date)
|
|
362
|
+
const todayButton = getDateButton( today );
|
|
363
|
+
await user.click( todayButton );
|
|
364
|
+
|
|
365
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
366
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
367
|
+
2,
|
|
368
|
+
{ from: today, to: tomorrow },
|
|
369
|
+
today,
|
|
370
|
+
expect.objectContaining( { today: true } ),
|
|
371
|
+
expect.objectContaining( {
|
|
372
|
+
type: 'click',
|
|
373
|
+
target: todayButton,
|
|
374
|
+
} )
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
expect(
|
|
378
|
+
getDateCell( today, { selected: true } )
|
|
379
|
+
).toBeVisible();
|
|
380
|
+
expect(
|
|
381
|
+
getDateCell( tomorrow, { selected: true } )
|
|
382
|
+
).toBeVisible();
|
|
383
|
+
} );
|
|
384
|
+
|
|
385
|
+
it( 'should expand the current range when clicking a third date after the existing range end', async () => {
|
|
386
|
+
const user = setupUserEvent();
|
|
387
|
+
const onSelect = jest.fn();
|
|
388
|
+
|
|
389
|
+
render( <Component onSelect={ onSelect } /> );
|
|
390
|
+
|
|
391
|
+
// First click - start range
|
|
392
|
+
const todayButton = getDateButton( today );
|
|
393
|
+
await user.click( todayButton );
|
|
394
|
+
|
|
395
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
396
|
+
expect( onSelect ).toHaveBeenCalledWith(
|
|
397
|
+
{ from: today, to: today },
|
|
398
|
+
today,
|
|
399
|
+
expect.objectContaining( { today: true } ),
|
|
400
|
+
expect.objectContaining( {
|
|
401
|
+
type: 'click',
|
|
402
|
+
target: todayButton,
|
|
403
|
+
} )
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
// Second click - complete range
|
|
407
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
408
|
+
await user.click( tomorrowButton );
|
|
409
|
+
|
|
410
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
411
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
412
|
+
2,
|
|
413
|
+
{ from: today, to: tomorrow },
|
|
414
|
+
tomorrow,
|
|
415
|
+
expect.objectContaining( { today: false } ),
|
|
416
|
+
expect.objectContaining( {
|
|
417
|
+
type: 'click',
|
|
418
|
+
target: tomorrowButton,
|
|
419
|
+
} )
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// Third click - expand range end
|
|
423
|
+
const dayAfterTomorrow = addDays( today, 2 );
|
|
424
|
+
const dayAfterTomorrowButton =
|
|
425
|
+
getDateButton( dayAfterTomorrow );
|
|
426
|
+
await user.click( dayAfterTomorrowButton );
|
|
427
|
+
|
|
428
|
+
expect( onSelect ).toHaveBeenCalledTimes( 3 );
|
|
429
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
430
|
+
3,
|
|
431
|
+
{ from: today, to: dayAfterTomorrow },
|
|
432
|
+
dayAfterTomorrow,
|
|
433
|
+
expect.objectContaining( { today: false } ),
|
|
434
|
+
expect.objectContaining( {
|
|
435
|
+
type: 'click',
|
|
436
|
+
target: dayAfterTomorrowButton,
|
|
437
|
+
} )
|
|
438
|
+
);
|
|
439
|
+
} );
|
|
440
|
+
|
|
441
|
+
it( 'should update the current range when clicking a third date in between the existing range start and end', async () => {
|
|
442
|
+
const user = setupUserEvent();
|
|
443
|
+
const onSelect = jest.fn();
|
|
444
|
+
|
|
445
|
+
render( <Component onSelect={ onSelect } /> );
|
|
446
|
+
|
|
447
|
+
// First click - start range
|
|
448
|
+
const yesterdayButton = getDateButton( yesterday );
|
|
449
|
+
await user.click( yesterdayButton );
|
|
450
|
+
|
|
451
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
452
|
+
expect( onSelect ).toHaveBeenCalledWith(
|
|
453
|
+
{ from: yesterday, to: yesterday },
|
|
454
|
+
yesterday,
|
|
455
|
+
expect.objectContaining( { today: false } ),
|
|
456
|
+
expect.objectContaining( {
|
|
457
|
+
type: 'click',
|
|
458
|
+
target: yesterdayButton,
|
|
459
|
+
} )
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// Second click - complete range
|
|
463
|
+
const dayAfterTomorrow = addDays( today, 2 );
|
|
464
|
+
const dayAfterTomorrowButton =
|
|
465
|
+
getDateButton( dayAfterTomorrow );
|
|
466
|
+
await user.click( dayAfterTomorrowButton );
|
|
467
|
+
|
|
468
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
469
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
470
|
+
2,
|
|
471
|
+
{ from: yesterday, to: dayAfterTomorrow },
|
|
472
|
+
dayAfterTomorrow,
|
|
473
|
+
expect.objectContaining( { today: false } ),
|
|
474
|
+
expect.objectContaining( {
|
|
475
|
+
type: 'click',
|
|
476
|
+
target: dayAfterTomorrowButton,
|
|
477
|
+
} )
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
// Third click - change range end
|
|
481
|
+
const todayButton = getDateButton( today );
|
|
482
|
+
await user.click( todayButton );
|
|
483
|
+
|
|
484
|
+
expect( onSelect ).toHaveBeenCalledTimes( 3 );
|
|
485
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
486
|
+
3,
|
|
487
|
+
{ from: yesterday, to: today },
|
|
488
|
+
today,
|
|
489
|
+
expect.objectContaining( { today: true } ),
|
|
490
|
+
expect.objectContaining( {
|
|
491
|
+
type: 'click',
|
|
492
|
+
target: todayButton,
|
|
493
|
+
} )
|
|
494
|
+
);
|
|
495
|
+
} );
|
|
496
|
+
|
|
497
|
+
it( 'should expand the current range when clicking a third date before the existing range start', async () => {
|
|
498
|
+
const user = setupUserEvent();
|
|
499
|
+
const onSelect = jest.fn();
|
|
500
|
+
|
|
501
|
+
render( <Component onSelect={ onSelect } /> );
|
|
502
|
+
|
|
503
|
+
// First click - start range
|
|
504
|
+
const todayButton = getDateButton( today );
|
|
505
|
+
await user.click( todayButton );
|
|
506
|
+
|
|
507
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
508
|
+
expect( onSelect ).toHaveBeenCalledWith(
|
|
509
|
+
{ from: today, to: today },
|
|
510
|
+
today,
|
|
511
|
+
expect.objectContaining( { today: true } ),
|
|
512
|
+
expect.objectContaining( {
|
|
513
|
+
type: 'click',
|
|
514
|
+
target: todayButton,
|
|
515
|
+
} )
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
// Second click - complete range
|
|
519
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
520
|
+
await user.click( tomorrowButton );
|
|
521
|
+
|
|
522
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
523
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
524
|
+
2,
|
|
525
|
+
{ from: today, to: tomorrow },
|
|
526
|
+
tomorrow,
|
|
527
|
+
expect.objectContaining( { today: false } ),
|
|
528
|
+
expect.objectContaining( {
|
|
529
|
+
type: 'click',
|
|
530
|
+
target: tomorrowButton,
|
|
531
|
+
} )
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
// Third click - expand range start
|
|
535
|
+
const yesterdayButton = getDateButton( yesterday );
|
|
536
|
+
await user.click( yesterdayButton );
|
|
537
|
+
|
|
538
|
+
expect( onSelect ).toHaveBeenCalledTimes( 3 );
|
|
539
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
540
|
+
3,
|
|
541
|
+
{ from: yesterday, to: tomorrow },
|
|
542
|
+
yesterday,
|
|
543
|
+
expect.objectContaining( { today: false } ),
|
|
544
|
+
expect.objectContaining( {
|
|
545
|
+
type: 'click',
|
|
546
|
+
target: yesterdayButton,
|
|
547
|
+
} )
|
|
548
|
+
);
|
|
549
|
+
} );
|
|
550
|
+
|
|
551
|
+
it( 'should not select a disabled date when a date button is clicked', async () => {
|
|
552
|
+
const user = setupUserEvent();
|
|
553
|
+
const onSelect = jest.fn();
|
|
554
|
+
|
|
555
|
+
render(
|
|
556
|
+
<Component onSelect={ onSelect } disabled={ tomorrow } />
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
560
|
+
await user.click( tomorrowButton );
|
|
561
|
+
|
|
562
|
+
expect( onSelect ).not.toHaveBeenCalled();
|
|
563
|
+
expect(
|
|
564
|
+
screen.queryByRole( 'button', { name: /selected/i } )
|
|
565
|
+
).not.toBeInTheDocument();
|
|
566
|
+
} );
|
|
567
|
+
|
|
568
|
+
it( 'should clear the range when defining a one-day range and clicking on the same date again', async () => {
|
|
569
|
+
const user = setupUserEvent();
|
|
570
|
+
const onSelect = jest.fn();
|
|
571
|
+
|
|
572
|
+
const dayAfterTomorrow = addDays( today, 2 );
|
|
573
|
+
|
|
574
|
+
render(
|
|
575
|
+
<Component
|
|
576
|
+
onSelect={ onSelect }
|
|
577
|
+
initialSelected={ {
|
|
578
|
+
from: yesterday,
|
|
579
|
+
to: dayAfterTomorrow,
|
|
580
|
+
} }
|
|
581
|
+
/>
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// Third click - change range to have start and end on the same date
|
|
585
|
+
const dayAfterTomorrowButton =
|
|
586
|
+
getDateButton( dayAfterTomorrow );
|
|
587
|
+
await user.click( dayAfterTomorrowButton );
|
|
588
|
+
|
|
589
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
590
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
591
|
+
1,
|
|
592
|
+
{ from: dayAfterTomorrow, to: dayAfterTomorrow },
|
|
593
|
+
dayAfterTomorrow,
|
|
594
|
+
expect.objectContaining( { today: false } ),
|
|
595
|
+
expect.objectContaining( {
|
|
596
|
+
type: 'click',
|
|
597
|
+
target: dayAfterTomorrowButton,
|
|
598
|
+
} )
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
// Fourth click - remove date range
|
|
602
|
+
await user.click( dayAfterTomorrowButton );
|
|
603
|
+
|
|
604
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
605
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
606
|
+
2,
|
|
607
|
+
undefined,
|
|
608
|
+
dayAfterTomorrow,
|
|
609
|
+
expect.objectContaining( { today: false } ),
|
|
610
|
+
expect.objectContaining( {
|
|
611
|
+
type: 'click',
|
|
612
|
+
target: dayAfterTomorrowButton,
|
|
613
|
+
} )
|
|
614
|
+
);
|
|
615
|
+
} );
|
|
616
|
+
|
|
617
|
+
it( 'should not clear the range when clicking a selected date if the `required` prop is set to `true`', async () => {
|
|
618
|
+
const user = setupUserEvent();
|
|
619
|
+
const onSelect = jest.fn();
|
|
620
|
+
|
|
621
|
+
const dayAfterTomorrow = addDays( today, 2 );
|
|
622
|
+
|
|
623
|
+
render(
|
|
624
|
+
<Component
|
|
625
|
+
onSelect={ onSelect }
|
|
626
|
+
initialSelected={ {
|
|
627
|
+
from: yesterday,
|
|
628
|
+
to: dayAfterTomorrow,
|
|
629
|
+
} }
|
|
630
|
+
required
|
|
631
|
+
/>
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
// Third click - change range to have start and end on the same date
|
|
635
|
+
const dayAfterTomorrowButton =
|
|
636
|
+
getDateButton( dayAfterTomorrow );
|
|
637
|
+
await user.click( dayAfterTomorrowButton );
|
|
638
|
+
|
|
639
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
640
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
641
|
+
1,
|
|
642
|
+
{ from: dayAfterTomorrow, to: dayAfterTomorrow },
|
|
643
|
+
dayAfterTomorrow,
|
|
644
|
+
expect.objectContaining( { today: false } ),
|
|
645
|
+
expect.objectContaining( {
|
|
646
|
+
type: 'click',
|
|
647
|
+
target: dayAfterTomorrowButton,
|
|
648
|
+
} )
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
// Fourth click - doesn't remove date range
|
|
652
|
+
await user.click( dayAfterTomorrowButton );
|
|
653
|
+
|
|
654
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
655
|
+
expect( onSelect ).toHaveBeenNthCalledWith(
|
|
656
|
+
2,
|
|
657
|
+
{ from: dayAfterTomorrow, to: dayAfterTomorrow },
|
|
658
|
+
dayAfterTomorrow,
|
|
659
|
+
expect.objectContaining( { today: false } ),
|
|
660
|
+
expect.objectContaining( {
|
|
661
|
+
type: 'click',
|
|
662
|
+
target: dayAfterTomorrowButton,
|
|
663
|
+
} )
|
|
664
|
+
);
|
|
665
|
+
} );
|
|
666
|
+
|
|
667
|
+
it( 'should complete a range selection even if there are disabled dates in the range', async () => {
|
|
668
|
+
const user = setupUserEvent();
|
|
669
|
+
const onSelect = jest.fn();
|
|
670
|
+
|
|
671
|
+
render(
|
|
672
|
+
<Component onSelect={ onSelect } disabled={ tomorrow } />
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
const todayButton = getDateButton( today );
|
|
676
|
+
|
|
677
|
+
// First click - start range
|
|
678
|
+
await user.click( todayButton );
|
|
679
|
+
|
|
680
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
681
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
682
|
+
{ from: today, to: today },
|
|
683
|
+
today,
|
|
684
|
+
expect.objectContaining( { today: true } ),
|
|
685
|
+
expect.objectContaining( {
|
|
686
|
+
type: 'click',
|
|
687
|
+
target: todayButton,
|
|
688
|
+
} )
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
// Second click - range "restarts" from newly clicked date, since
|
|
692
|
+
// there was a disabled date in between
|
|
693
|
+
const dayAfterTomorrow = addDays( today, 2 );
|
|
694
|
+
const dayAfterTomorrowButton =
|
|
695
|
+
getDateButton( dayAfterTomorrow );
|
|
696
|
+
await user.click( dayAfterTomorrowButton );
|
|
697
|
+
|
|
698
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
699
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
700
|
+
{ from: today, to: dayAfterTomorrow },
|
|
701
|
+
dayAfterTomorrow,
|
|
702
|
+
expect.objectContaining( { today: false } ),
|
|
703
|
+
expect.objectContaining( {
|
|
704
|
+
type: 'click',
|
|
705
|
+
target: dayAfterTomorrowButton,
|
|
706
|
+
} )
|
|
707
|
+
);
|
|
708
|
+
} );
|
|
709
|
+
|
|
710
|
+
it( 'should not complete a range selection if the `excludeDisabled` prop is set to `true` and there is at least one disabled date in the range', async () => {
|
|
711
|
+
const user = setupUserEvent();
|
|
712
|
+
const onSelect = jest.fn();
|
|
713
|
+
|
|
714
|
+
render(
|
|
715
|
+
<Component
|
|
716
|
+
onSelect={ onSelect }
|
|
717
|
+
disabled={ tomorrow }
|
|
718
|
+
excludeDisabled
|
|
719
|
+
/>
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
const todayButton = getDateButton( today );
|
|
723
|
+
|
|
724
|
+
// First click - start range
|
|
725
|
+
await user.click( todayButton );
|
|
726
|
+
|
|
727
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
728
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
729
|
+
{ from: today, to: today },
|
|
730
|
+
today,
|
|
731
|
+
expect.objectContaining( { today: true } ),
|
|
732
|
+
expect.objectContaining( {
|
|
733
|
+
type: 'click',
|
|
734
|
+
target: todayButton,
|
|
735
|
+
} )
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
// Second click - range "restarts" from newly clicked date, since
|
|
739
|
+
// there was a disabled date in between
|
|
740
|
+
const dayAfterTomorrow = addDays( today, 2 );
|
|
741
|
+
const dayAfterTomorrowButton =
|
|
742
|
+
getDateButton( dayAfterTomorrow );
|
|
743
|
+
await user.click( dayAfterTomorrowButton );
|
|
744
|
+
|
|
745
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
746
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
747
|
+
{ from: dayAfterTomorrow, to: undefined },
|
|
748
|
+
dayAfterTomorrow,
|
|
749
|
+
expect.objectContaining( { today: false } ),
|
|
750
|
+
expect.objectContaining( {
|
|
751
|
+
type: 'click',
|
|
752
|
+
target: dayAfterTomorrowButton,
|
|
753
|
+
} )
|
|
754
|
+
);
|
|
755
|
+
} );
|
|
756
|
+
|
|
757
|
+
it( 'should not complete a range selection if the range has a duration of less than the value of the `min` prop', async () => {
|
|
758
|
+
const user = setupUserEvent();
|
|
759
|
+
const onSelect = jest.fn();
|
|
760
|
+
|
|
761
|
+
render( <Component onSelect={ onSelect } min={ 3 } /> );
|
|
762
|
+
|
|
763
|
+
const todayButton = getDateButton( today );
|
|
764
|
+
|
|
765
|
+
// First click - start range
|
|
766
|
+
await user.click( todayButton );
|
|
767
|
+
|
|
768
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
769
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
770
|
+
{ from: today, to: undefined },
|
|
771
|
+
today,
|
|
772
|
+
expect.objectContaining( { today: true } ),
|
|
773
|
+
expect.objectContaining( {
|
|
774
|
+
type: 'click',
|
|
775
|
+
target: todayButton,
|
|
776
|
+
} )
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
// Second click - range "restarts" from newly clicked date, since
|
|
780
|
+
// it was not long enough compared to the `min` prop
|
|
781
|
+
const dayAfterTomorrow = addDays( today, 2 );
|
|
782
|
+
const dayAfterTomorrowButton =
|
|
783
|
+
getDateButton( dayAfterTomorrow );
|
|
784
|
+
await user.click( dayAfterTomorrowButton );
|
|
785
|
+
|
|
786
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
787
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
788
|
+
{ from: dayAfterTomorrow, to: undefined },
|
|
789
|
+
dayAfterTomorrow,
|
|
790
|
+
expect.objectContaining( { today: false } ),
|
|
791
|
+
expect.objectContaining( {
|
|
792
|
+
type: 'click',
|
|
793
|
+
target: dayAfterTomorrowButton,
|
|
794
|
+
} )
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
// Third click - range is correctly set, since it includes
|
|
798
|
+
// at least 3 days
|
|
799
|
+
const yesterdayButton = getDateButton( yesterday );
|
|
800
|
+
await user.click( yesterdayButton );
|
|
801
|
+
|
|
802
|
+
expect( onSelect ).toHaveBeenCalledTimes( 3 );
|
|
803
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
804
|
+
{ from: yesterday, to: dayAfterTomorrow },
|
|
805
|
+
yesterday,
|
|
806
|
+
expect.objectContaining( { today: false } ),
|
|
807
|
+
expect.objectContaining( {
|
|
808
|
+
type: 'click',
|
|
809
|
+
target: yesterdayButton,
|
|
810
|
+
} )
|
|
811
|
+
);
|
|
812
|
+
} );
|
|
813
|
+
|
|
814
|
+
it( 'should not complete a range selection if the range has a duration of more than the value of the `max` prop', async () => {
|
|
815
|
+
const user = setupUserEvent();
|
|
816
|
+
const onSelect = jest.fn();
|
|
817
|
+
|
|
818
|
+
render( <Component onSelect={ onSelect } max={ 2 } /> );
|
|
819
|
+
|
|
820
|
+
// First click - start range
|
|
821
|
+
const yesterdayButton = getDateButton( yesterday );
|
|
822
|
+
await user.click( yesterdayButton );
|
|
823
|
+
|
|
824
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
825
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
826
|
+
{ from: yesterday, to: yesterday },
|
|
827
|
+
yesterday,
|
|
828
|
+
expect.objectContaining( { today: false } ),
|
|
829
|
+
expect.objectContaining( {
|
|
830
|
+
type: 'click',
|
|
831
|
+
target: yesterdayButton,
|
|
832
|
+
} )
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
// Second click - range "restarts" from newly clicked date, since
|
|
836
|
+
// it was too long compared to the `max` prop
|
|
837
|
+
const dayAfterTomorrow = addDays( today, 2 );
|
|
838
|
+
const dayAfterTomorrowButton =
|
|
839
|
+
getDateButton( dayAfterTomorrow );
|
|
840
|
+
await user.click( dayAfterTomorrowButton );
|
|
841
|
+
|
|
842
|
+
expect( onSelect ).toHaveBeenCalledTimes( 2 );
|
|
843
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
844
|
+
{ from: dayAfterTomorrow, to: undefined },
|
|
845
|
+
dayAfterTomorrow,
|
|
846
|
+
expect.objectContaining( { today: false } ),
|
|
847
|
+
expect.objectContaining( {
|
|
848
|
+
type: 'click',
|
|
849
|
+
target: dayAfterTomorrowButton,
|
|
850
|
+
} )
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
// Third click - range is correctly set, since it includes
|
|
854
|
+
// at most 2 days
|
|
855
|
+
const todayButton = getDateButton( today );
|
|
856
|
+
await user.click( todayButton );
|
|
857
|
+
|
|
858
|
+
expect( onSelect ).toHaveBeenCalledTimes( 3 );
|
|
859
|
+
expect( onSelect ).toHaveBeenLastCalledWith(
|
|
860
|
+
{ from: today, to: dayAfterTomorrow },
|
|
861
|
+
today,
|
|
862
|
+
expect.objectContaining( { today: true } ),
|
|
863
|
+
expect.objectContaining( {
|
|
864
|
+
type: 'click',
|
|
865
|
+
target: todayButton,
|
|
866
|
+
} )
|
|
867
|
+
);
|
|
868
|
+
} );
|
|
869
|
+
} );
|
|
870
|
+
} );
|
|
871
|
+
|
|
872
|
+
describe( 'Month navigation', () => {
|
|
873
|
+
it( 'should select an initial month in uncontrolled mode via the `defaultMonth` prop', () => {
|
|
874
|
+
render( <DateRangeCalendar defaultMonth={ nextMonth } /> );
|
|
875
|
+
|
|
876
|
+
expect(
|
|
877
|
+
screen.getByRole( 'grid', {
|
|
878
|
+
name: monthNameFormatter( 'en-US' ).format( nextMonth ),
|
|
879
|
+
} )
|
|
880
|
+
).toBeVisible();
|
|
881
|
+
expect( getDateCell( nextMonth ) ).toBeVisible();
|
|
882
|
+
expect( getDateButton( nextMonth ) ).toBeVisible();
|
|
883
|
+
} );
|
|
884
|
+
|
|
885
|
+
it( 'should select an initial month in controlled mode via the `month` prop', () => {
|
|
886
|
+
render( <DateRangeCalendar month={ nextMonth } /> );
|
|
887
|
+
|
|
888
|
+
expect(
|
|
889
|
+
screen.getByRole( 'grid', {
|
|
890
|
+
name: monthNameFormatter( 'en-US' ).format( nextMonth ),
|
|
891
|
+
} )
|
|
892
|
+
).toBeVisible();
|
|
893
|
+
expect( getDateCell( nextMonth ) ).toBeVisible();
|
|
894
|
+
expect( getDateButton( nextMonth ) ).toBeVisible();
|
|
895
|
+
} );
|
|
896
|
+
|
|
897
|
+
describe.each( [
|
|
898
|
+
[ 'Uncontrolled', UncontrolledDateRangeCalendar ],
|
|
899
|
+
[ 'Controlled', ControlledDateRangeCalendar ],
|
|
900
|
+
] )( '[`%s`]', ( _mode, Component ) => {
|
|
901
|
+
it( 'should navigate to the previous and next months when the previous and next month buttons are clicked', async () => {
|
|
902
|
+
const user = setupUserEvent();
|
|
903
|
+
const onMonthChange = jest.fn();
|
|
904
|
+
|
|
905
|
+
render( <Component onMonthChange={ onMonthChange } /> );
|
|
906
|
+
|
|
907
|
+
const prevButton = screen.getByRole( 'button', {
|
|
908
|
+
name: /previous month/i,
|
|
909
|
+
} );
|
|
910
|
+
const nextButton = screen.getByRole( 'button', {
|
|
911
|
+
name: /next month/i,
|
|
912
|
+
} );
|
|
913
|
+
await user.click( prevButton );
|
|
914
|
+
|
|
915
|
+
expect( onMonthChange ).toHaveBeenCalledTimes( 1 );
|
|
916
|
+
expect( onMonthChange ).toHaveBeenCalledWith( prevMonth );
|
|
917
|
+
|
|
918
|
+
expect(
|
|
919
|
+
screen.getByRole( 'grid', {
|
|
920
|
+
name: monthNameFormatter( 'en-US' ).format( prevMonth ),
|
|
921
|
+
} )
|
|
922
|
+
).toBeVisible();
|
|
923
|
+
expect( getDateCell( prevMonth ) ).toBeVisible();
|
|
924
|
+
expect( getDateButton( prevMonth ) ).toBeVisible();
|
|
925
|
+
|
|
926
|
+
await user.click( nextButton );
|
|
927
|
+
|
|
928
|
+
expect( onMonthChange ).toHaveBeenCalledTimes( 2 );
|
|
929
|
+
expect( onMonthChange ).toHaveBeenCalledWith( currentMonth );
|
|
930
|
+
|
|
931
|
+
expect(
|
|
932
|
+
screen.getByRole( 'grid', {
|
|
933
|
+
name: monthNameFormatter( 'en-US' ).format(
|
|
934
|
+
currentMonth
|
|
935
|
+
),
|
|
936
|
+
} )
|
|
937
|
+
).toBeVisible();
|
|
938
|
+
expect( getDateCell( currentMonth ) ).toBeVisible();
|
|
939
|
+
expect( getDateButton( currentMonth ) ).toBeVisible();
|
|
940
|
+
|
|
941
|
+
await user.click( nextButton );
|
|
942
|
+
|
|
943
|
+
expect( onMonthChange ).toHaveBeenCalledTimes( 3 );
|
|
944
|
+
expect( onMonthChange ).toHaveBeenCalledWith( nextMonth );
|
|
945
|
+
|
|
946
|
+
expect(
|
|
947
|
+
screen.getByRole( 'grid', {
|
|
948
|
+
name: monthNameFormatter( 'en-US' ).format( nextMonth ),
|
|
949
|
+
} )
|
|
950
|
+
).toBeVisible();
|
|
951
|
+
expect( getDateCell( nextMonth ) ).toBeVisible();
|
|
952
|
+
expect( getDateButton( nextMonth ) ).toBeVisible();
|
|
953
|
+
} );
|
|
954
|
+
|
|
955
|
+
it( 'should not navigate to a month that is before the `startMonth` prop', async () => {
|
|
956
|
+
const user = setupUserEvent();
|
|
957
|
+
const onMonthChange = jest.fn();
|
|
958
|
+
|
|
959
|
+
render(
|
|
960
|
+
<Component
|
|
961
|
+
startMonth={ nextMonth }
|
|
962
|
+
onMonthChange={ onMonthChange }
|
|
963
|
+
/>
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
const prevButton = screen.getByRole( 'button', {
|
|
967
|
+
name: /previous month/i,
|
|
968
|
+
} );
|
|
969
|
+
const nextButton = screen.getByRole( 'button', {
|
|
970
|
+
name: /next month/i,
|
|
971
|
+
} );
|
|
972
|
+
|
|
973
|
+
expect(
|
|
974
|
+
screen.getByRole( 'grid', {
|
|
975
|
+
name: monthNameFormatter( 'en-US' ).format( nextMonth ),
|
|
976
|
+
} )
|
|
977
|
+
).toBeVisible();
|
|
978
|
+
expect( getDateCell( nextMonth ) ).toBeVisible();
|
|
979
|
+
expect( getDateButton( nextMonth ) ).toBeVisible();
|
|
980
|
+
|
|
981
|
+
expect( prevButton ).toHaveAttribute( 'aria-disabled', 'true' );
|
|
982
|
+
|
|
983
|
+
await user.click( prevButton );
|
|
984
|
+
|
|
985
|
+
expect( onMonthChange ).not.toHaveBeenCalled();
|
|
986
|
+
|
|
987
|
+
await user.click( nextButton );
|
|
988
|
+
|
|
989
|
+
expect( onMonthChange ).toHaveBeenCalledTimes( 1 );
|
|
990
|
+
expect( onMonthChange ).toHaveBeenCalledWith( nextNextMonth );
|
|
991
|
+
|
|
992
|
+
expect(
|
|
993
|
+
screen.getByRole( 'grid', {
|
|
994
|
+
name: monthNameFormatter( 'en-US' ).format(
|
|
995
|
+
nextNextMonth
|
|
996
|
+
),
|
|
997
|
+
} )
|
|
998
|
+
).toBeVisible();
|
|
999
|
+
expect( getDateCell( nextNextMonth ) ).toBeVisible();
|
|
1000
|
+
expect( getDateButton( nextNextMonth ) ).toBeVisible();
|
|
1001
|
+
|
|
1002
|
+
expect( prevButton ).not.toHaveAttribute( 'aria-disabled' );
|
|
1003
|
+
} );
|
|
1004
|
+
|
|
1005
|
+
it( 'should not navigate to a month that is after the `endMonth` prop', async () => {
|
|
1006
|
+
const user = setupUserEvent();
|
|
1007
|
+
const onMonthChange = jest.fn();
|
|
1008
|
+
|
|
1009
|
+
render(
|
|
1010
|
+
<Component
|
|
1011
|
+
endMonth={ prevMonth }
|
|
1012
|
+
onMonthChange={ onMonthChange }
|
|
1013
|
+
/>
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
const prevButton = screen.getByRole( 'button', {
|
|
1017
|
+
name: /previous month/i,
|
|
1018
|
+
} );
|
|
1019
|
+
const nextButton = screen.getByRole( 'button', {
|
|
1020
|
+
name: /next month/i,
|
|
1021
|
+
} );
|
|
1022
|
+
|
|
1023
|
+
expect(
|
|
1024
|
+
screen.getByRole( 'grid', {
|
|
1025
|
+
name: monthNameFormatter( 'en-US' ).format( prevMonth ),
|
|
1026
|
+
} )
|
|
1027
|
+
).toBeVisible();
|
|
1028
|
+
expect( getDateCell( prevMonth ) ).toBeVisible();
|
|
1029
|
+
expect( getDateButton( prevMonth ) ).toBeVisible();
|
|
1030
|
+
|
|
1031
|
+
expect( nextButton ).toHaveAttribute( 'aria-disabled', 'true' );
|
|
1032
|
+
|
|
1033
|
+
await user.click( nextButton );
|
|
1034
|
+
|
|
1035
|
+
expect( onMonthChange ).not.toHaveBeenCalled();
|
|
1036
|
+
|
|
1037
|
+
await user.click( prevButton );
|
|
1038
|
+
|
|
1039
|
+
expect( onMonthChange ).toHaveBeenCalledTimes( 1 );
|
|
1040
|
+
expect( onMonthChange ).toHaveBeenCalledWith( prevPrevMonth );
|
|
1041
|
+
|
|
1042
|
+
expect(
|
|
1043
|
+
screen.getByRole( 'grid', {
|
|
1044
|
+
name: monthNameFormatter( 'en-US' ).format(
|
|
1045
|
+
prevPrevMonth
|
|
1046
|
+
),
|
|
1047
|
+
} )
|
|
1048
|
+
).toBeVisible();
|
|
1049
|
+
expect( getDateCell( prevPrevMonth ) ).toBeVisible();
|
|
1050
|
+
expect( getDateButton( prevPrevMonth ) ).toBeVisible();
|
|
1051
|
+
|
|
1052
|
+
expect( nextButton ).not.toHaveAttribute( 'aria-disabled' );
|
|
1053
|
+
} );
|
|
1054
|
+
} );
|
|
1055
|
+
} );
|
|
1056
|
+
|
|
1057
|
+
describe( 'Keyboard focus and navigation', () => {
|
|
1058
|
+
it( 'should auto-focus the selected day when the `autoFocus` prop is set to `true`', async () => {
|
|
1059
|
+
render(
|
|
1060
|
+
<DateRangeCalendar
|
|
1061
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
1062
|
+
autoFocus
|
|
1063
|
+
defaultSelected={ { from: today, to: tomorrow } }
|
|
1064
|
+
/>
|
|
1065
|
+
);
|
|
1066
|
+
expect( getDateButton( today ) ).toHaveFocus();
|
|
1067
|
+
} );
|
|
1068
|
+
|
|
1069
|
+
it( "should auto-focus today's date if there is not selected date when the `autoFocus` prop is set to `true`", async () => {
|
|
1070
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
1071
|
+
render( <DateRangeCalendar autoFocus /> );
|
|
1072
|
+
expect( getDateButton( today ) ).toHaveFocus();
|
|
1073
|
+
} );
|
|
1074
|
+
|
|
1075
|
+
it( 'should focus each arrow as a tab stop, but treat the grid as a 2d composite widget', async () => {
|
|
1076
|
+
const user = setupUserEvent();
|
|
1077
|
+
render( <DateRangeCalendar /> );
|
|
1078
|
+
|
|
1079
|
+
// Focus previous month button
|
|
1080
|
+
await user.tab();
|
|
1081
|
+
expect(
|
|
1082
|
+
screen.getByRole( 'button', { name: /previous month/i } )
|
|
1083
|
+
).toHaveFocus();
|
|
1084
|
+
|
|
1085
|
+
// Focus next month button
|
|
1086
|
+
await user.tab();
|
|
1087
|
+
expect(
|
|
1088
|
+
screen.getByRole( 'button', { name: /next month/i } )
|
|
1089
|
+
).toHaveFocus();
|
|
1090
|
+
|
|
1091
|
+
// Focus today button
|
|
1092
|
+
await user.tab();
|
|
1093
|
+
expect( getDateButton( today ) ).toHaveFocus();
|
|
1094
|
+
|
|
1095
|
+
// Focus next day
|
|
1096
|
+
await user.keyboard( '{ArrowRight}' );
|
|
1097
|
+
expect( getDateButton( addDays( today, 1 ) ) ).toHaveFocus();
|
|
1098
|
+
|
|
1099
|
+
// Focus to next week
|
|
1100
|
+
await user.keyboard( '{ArrowDown}' );
|
|
1101
|
+
expect( getDateButton( addDays( today, 8 ) ) ).toHaveFocus();
|
|
1102
|
+
|
|
1103
|
+
// Focus previous day
|
|
1104
|
+
await user.keyboard( '{ArrowLeft}' );
|
|
1105
|
+
expect( getDateButton( addDays( today, 7 ) ) ).toHaveFocus();
|
|
1106
|
+
|
|
1107
|
+
// Focus previous week
|
|
1108
|
+
await user.keyboard( '{ArrowUp}' );
|
|
1109
|
+
expect( getDateButton( today ) ).toHaveFocus();
|
|
1110
|
+
|
|
1111
|
+
// Focus first day of week
|
|
1112
|
+
await user.keyboard( '{Home}' );
|
|
1113
|
+
expect( getDateButton( startOfWeek( today ) ) ).toHaveFocus();
|
|
1114
|
+
|
|
1115
|
+
// Focus last day of week
|
|
1116
|
+
await user.keyboard( '{End}' );
|
|
1117
|
+
expect( getDateButton( endOfWeek( today ) ) ).toHaveFocus();
|
|
1118
|
+
|
|
1119
|
+
// Focus previous month
|
|
1120
|
+
await user.keyboard( '{PageUp}' );
|
|
1121
|
+
expect(
|
|
1122
|
+
getDateButton( subMonths( endOfWeek( today ), 1 ) )
|
|
1123
|
+
).toHaveFocus();
|
|
1124
|
+
|
|
1125
|
+
expect(
|
|
1126
|
+
screen.getByRole( 'grid', {
|
|
1127
|
+
name: monthNameFormatter( 'en-US' ).format(
|
|
1128
|
+
subMonths( endOfWeek( today ), 1 )
|
|
1129
|
+
),
|
|
1130
|
+
} )
|
|
1131
|
+
).toBeVisible();
|
|
1132
|
+
|
|
1133
|
+
// Navigate to next month
|
|
1134
|
+
await user.keyboard( '{PageDown}' );
|
|
1135
|
+
expect( getDateButton( endOfWeek( today ) ) ).toHaveFocus();
|
|
1136
|
+
expect(
|
|
1137
|
+
screen.getByRole( 'grid', {
|
|
1138
|
+
name: monthNameFormatter( 'en-US' ).format(
|
|
1139
|
+
endOfWeek( today )
|
|
1140
|
+
),
|
|
1141
|
+
} )
|
|
1142
|
+
).toBeVisible();
|
|
1143
|
+
|
|
1144
|
+
// Focus previous year
|
|
1145
|
+
await user.keyboard( '{Shift>}{PageUp}{/Shift}' );
|
|
1146
|
+
expect(
|
|
1147
|
+
getDateButton( subYears( endOfWeek( today ), 1 ) )
|
|
1148
|
+
).toHaveFocus();
|
|
1149
|
+
|
|
1150
|
+
expect(
|
|
1151
|
+
screen.getByRole( 'grid', {
|
|
1152
|
+
name: monthNameFormatter( 'en-US' ).format(
|
|
1153
|
+
subYears( endOfWeek( today ), 1 )
|
|
1154
|
+
),
|
|
1155
|
+
} )
|
|
1156
|
+
).toBeVisible();
|
|
1157
|
+
|
|
1158
|
+
// Focus next year
|
|
1159
|
+
await user.keyboard( '{Shift>}{PageDown}{/Shift}' );
|
|
1160
|
+
expect( getDateButton( endOfWeek( today ) ) ).toHaveFocus();
|
|
1161
|
+
|
|
1162
|
+
expect(
|
|
1163
|
+
screen.getByRole( 'grid', {
|
|
1164
|
+
name: monthNameFormatter( 'en-US' ).format(
|
|
1165
|
+
endOfWeek( today )
|
|
1166
|
+
),
|
|
1167
|
+
} )
|
|
1168
|
+
).toBeVisible();
|
|
1169
|
+
} );
|
|
1170
|
+
|
|
1171
|
+
// Note: the following test is not testing advanced keyboard interactions
|
|
1172
|
+
// (pageUp, pageDown, shift+pageUp, shift+pageDown, home, end)
|
|
1173
|
+
it( 'should not focus disabled dates and skip over them when navigating using arrow keys', async () => {
|
|
1174
|
+
const user = setupUserEvent();
|
|
1175
|
+
|
|
1176
|
+
render(
|
|
1177
|
+
<DateRangeCalendar
|
|
1178
|
+
disabled={ [
|
|
1179
|
+
tomorrow,
|
|
1180
|
+
addWeeks( addDays( tomorrow, 1 ), 1 ),
|
|
1181
|
+
addWeeks( today, 2 ),
|
|
1182
|
+
addWeeks( tomorrow, 2 ),
|
|
1183
|
+
] }
|
|
1184
|
+
/>
|
|
1185
|
+
);
|
|
1186
|
+
|
|
1187
|
+
await user.tab();
|
|
1188
|
+
await user.tab();
|
|
1189
|
+
await user.tab();
|
|
1190
|
+
expect( getDateButton( today ) ).toHaveFocus();
|
|
1191
|
+
|
|
1192
|
+
await user.keyboard( '{ArrowRight}' );
|
|
1193
|
+
expect( getDateButton( addDays( tomorrow, 1 ) ) ).toHaveFocus();
|
|
1194
|
+
|
|
1195
|
+
await user.keyboard( '{ArrowDown}' );
|
|
1196
|
+
expect(
|
|
1197
|
+
getDateButton( addWeeks( addDays( tomorrow, 1 ), 2 ) )
|
|
1198
|
+
).toHaveFocus();
|
|
1199
|
+
|
|
1200
|
+
await user.keyboard( '{ArrowLeft}' );
|
|
1201
|
+
expect( getDateButton( addWeeks( yesterday, 2 ) ) ).toHaveFocus();
|
|
1202
|
+
|
|
1203
|
+
await user.keyboard( '{ArrowUp}' );
|
|
1204
|
+
expect( getDateButton( addWeeks( yesterday, 1 ) ) ).toHaveFocus();
|
|
1205
|
+
} );
|
|
1206
|
+
|
|
1207
|
+
it( 'should focus the selected date when tabbing into the calendar', async () => {
|
|
1208
|
+
const user = setupUserEvent();
|
|
1209
|
+
render(
|
|
1210
|
+
<DateRangeCalendar selected={ { from: today, to: tomorrow } } />
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
// Tab to the calendar grid
|
|
1214
|
+
await user.tab();
|
|
1215
|
+
await user.tab();
|
|
1216
|
+
await user.tab();
|
|
1217
|
+
|
|
1218
|
+
expect( getDateButton( today ) ).toHaveFocus();
|
|
1219
|
+
} );
|
|
1220
|
+
} );
|
|
1221
|
+
|
|
1222
|
+
describe( 'Disabled states', () => {
|
|
1223
|
+
it( 'should support disabling all dates via the `disabled` prop', async () => {
|
|
1224
|
+
const user = setupUserEvent();
|
|
1225
|
+
|
|
1226
|
+
render( <DateRangeCalendar disabled /> );
|
|
1227
|
+
|
|
1228
|
+
within( screen.getByRole( 'grid' ) )
|
|
1229
|
+
.getAllByRole( 'button' )
|
|
1230
|
+
.forEach( ( button ) => {
|
|
1231
|
+
expect( button ).toBeDisabled();
|
|
1232
|
+
} );
|
|
1233
|
+
|
|
1234
|
+
await user.click(
|
|
1235
|
+
screen.getByRole( 'button', { name: /previous/i } )
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
within( screen.getByRole( 'grid' ) )
|
|
1239
|
+
.getAllByRole( 'button' )
|
|
1240
|
+
.forEach( ( button ) => {
|
|
1241
|
+
expect( button ).toBeDisabled();
|
|
1242
|
+
} );
|
|
1243
|
+
|
|
1244
|
+
await user.click( screen.getByRole( 'button', { name: /next/i } ) );
|
|
1245
|
+
await user.click( screen.getByRole( 'button', { name: /next/i } ) );
|
|
1246
|
+
|
|
1247
|
+
within( screen.getByRole( 'grid' ) )
|
|
1248
|
+
.getAllByRole( 'button' )
|
|
1249
|
+
.forEach( ( button ) => {
|
|
1250
|
+
expect( button ).toBeDisabled();
|
|
1251
|
+
} );
|
|
1252
|
+
} );
|
|
1253
|
+
|
|
1254
|
+
it( 'should support disabling single dates via the `disabled` prop', async () => {
|
|
1255
|
+
render( <DateRangeCalendar disabled={ tomorrow } /> );
|
|
1256
|
+
|
|
1257
|
+
expect( getDateButton( tomorrow ) ).toBeDisabled();
|
|
1258
|
+
} );
|
|
1259
|
+
|
|
1260
|
+
it( 'should support passing a custom function via the `disabled` prop', async () => {
|
|
1261
|
+
const primeNumbers = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 ];
|
|
1262
|
+
render(
|
|
1263
|
+
<DateRangeCalendar
|
|
1264
|
+
disabled={ ( date ) =>
|
|
1265
|
+
primeNumbers.includes( date.getDate() )
|
|
1266
|
+
}
|
|
1267
|
+
/>
|
|
1268
|
+
);
|
|
1269
|
+
|
|
1270
|
+
for ( const date of primeNumbers ) {
|
|
1271
|
+
expect(
|
|
1272
|
+
getDateButton(
|
|
1273
|
+
new Date( today.getFullYear(), today.getMonth(), date )
|
|
1274
|
+
)
|
|
1275
|
+
).toBeDisabled();
|
|
1276
|
+
}
|
|
1277
|
+
} );
|
|
1278
|
+
|
|
1279
|
+
it( 'should support disabling all dates before a certain date via the `disabled` prop', async () => {
|
|
1280
|
+
render( <DateRangeCalendar disabled={ { before: today } } /> );
|
|
1281
|
+
|
|
1282
|
+
for ( let date = 1; date < today.getDate(); date++ ) {
|
|
1283
|
+
expect(
|
|
1284
|
+
getDateButton(
|
|
1285
|
+
new Date( today.getFullYear(), today.getMonth(), date )
|
|
1286
|
+
)
|
|
1287
|
+
).toBeDisabled();
|
|
1288
|
+
}
|
|
1289
|
+
expect( getDateButton( today ) ).toBeEnabled();
|
|
1290
|
+
} );
|
|
1291
|
+
|
|
1292
|
+
it( 'should support disabling all dates after a certain date via the `disabled` prop', async () => {
|
|
1293
|
+
render( <DateRangeCalendar disabled={ { after: today } } /> );
|
|
1294
|
+
|
|
1295
|
+
for ( let date = today.getDate() + 1; date < 32; date++ ) {
|
|
1296
|
+
expect(
|
|
1297
|
+
getDateButton(
|
|
1298
|
+
new Date( today.getFullYear(), today.getMonth(), date )
|
|
1299
|
+
)
|
|
1300
|
+
).toBeDisabled();
|
|
1301
|
+
}
|
|
1302
|
+
expect( getDateButton( today ) ).toBeEnabled();
|
|
1303
|
+
} );
|
|
1304
|
+
|
|
1305
|
+
it( 'should support disabling all dates before a certain date and after a certain date via the `disabled` prop', async () => {
|
|
1306
|
+
render(
|
|
1307
|
+
<DateRangeCalendar
|
|
1308
|
+
disabled={ {
|
|
1309
|
+
before: yesterday,
|
|
1310
|
+
after: addDays( today, 1 ),
|
|
1311
|
+
} }
|
|
1312
|
+
/>
|
|
1313
|
+
);
|
|
1314
|
+
|
|
1315
|
+
let date;
|
|
1316
|
+
|
|
1317
|
+
for ( date = 1; date < today.getDate() - 1; date++ ) {
|
|
1318
|
+
expect(
|
|
1319
|
+
getDateButton(
|
|
1320
|
+
new Date( today.getFullYear(), today.getMonth(), date )
|
|
1321
|
+
)
|
|
1322
|
+
).toBeDisabled();
|
|
1323
|
+
}
|
|
1324
|
+
expect( getDateButton( yesterday ) ).toBeEnabled();
|
|
1325
|
+
expect( getDateButton( today ) ).toBeEnabled();
|
|
1326
|
+
expect( getDateButton( addDays( today, 1 ) ) ).toBeEnabled();
|
|
1327
|
+
|
|
1328
|
+
for ( date = today.getDate() + 2; date < 32; date++ ) {
|
|
1329
|
+
expect(
|
|
1330
|
+
getDateButton(
|
|
1331
|
+
new Date( today.getFullYear(), today.getMonth(), date )
|
|
1332
|
+
)
|
|
1333
|
+
).toBeDisabled();
|
|
1334
|
+
}
|
|
1335
|
+
} );
|
|
1336
|
+
|
|
1337
|
+
it( 'should support disabling all dates within a certain date range via the `disabled` prop', async () => {
|
|
1338
|
+
render(
|
|
1339
|
+
<DateRangeCalendar
|
|
1340
|
+
disabled={ { from: yesterday, to: addDays( today, 1 ) } }
|
|
1341
|
+
/>
|
|
1342
|
+
);
|
|
1343
|
+
|
|
1344
|
+
let date;
|
|
1345
|
+
|
|
1346
|
+
for ( date = 1; date < today.getDate() - 1; date++ ) {
|
|
1347
|
+
expect(
|
|
1348
|
+
getDateButton(
|
|
1349
|
+
new Date( today.getFullYear(), today.getMonth(), date )
|
|
1350
|
+
)
|
|
1351
|
+
).toBeEnabled();
|
|
1352
|
+
}
|
|
1353
|
+
expect( getDateButton( yesterday ) ).toBeDisabled();
|
|
1354
|
+
expect( getDateButton( today ) ).toBeDisabled();
|
|
1355
|
+
expect( getDateButton( addDays( today, 1 ) ) ).toBeDisabled();
|
|
1356
|
+
|
|
1357
|
+
for ( date = today.getDate() + 2; date < 32; date++ ) {
|
|
1358
|
+
expect(
|
|
1359
|
+
getDateButton(
|
|
1360
|
+
new Date( today.getFullYear(), today.getMonth(), date )
|
|
1361
|
+
)
|
|
1362
|
+
).toBeEnabled();
|
|
1363
|
+
}
|
|
1364
|
+
} );
|
|
1365
|
+
|
|
1366
|
+
it( 'should support disabling specific days of the week via the `disabled` prop', async () => {
|
|
1367
|
+
const weekendsInMay = [ 3, 4, 10, 11, 17, 18, 24, 25, 31 ];
|
|
1368
|
+
render(
|
|
1369
|
+
<DateRangeCalendar disabled={ { dayOfWeek: [ 0, 6 ] } } />
|
|
1370
|
+
);
|
|
1371
|
+
|
|
1372
|
+
for ( const date of weekendsInMay ) {
|
|
1373
|
+
expect(
|
|
1374
|
+
getDateButton(
|
|
1375
|
+
new Date( today.getFullYear(), today.getMonth(), date )
|
|
1376
|
+
)
|
|
1377
|
+
).toBeDisabled();
|
|
1378
|
+
}
|
|
1379
|
+
} );
|
|
1380
|
+
|
|
1381
|
+
it( 'should disable the previous and next months buttons if the `disableNavigation` is set to `true`', async () => {
|
|
1382
|
+
const user = setupUserEvent();
|
|
1383
|
+
|
|
1384
|
+
render( <DateRangeCalendar disableNavigation /> );
|
|
1385
|
+
|
|
1386
|
+
expect(
|
|
1387
|
+
screen.getByRole( 'button', { name: /previous month/i } )
|
|
1388
|
+
).toHaveAttribute( 'aria-disabled', 'true' );
|
|
1389
|
+
expect(
|
|
1390
|
+
screen.getByRole( 'button', { name: /next month/i } )
|
|
1391
|
+
).toHaveAttribute( 'aria-disabled', 'true' );
|
|
1392
|
+
|
|
1393
|
+
await user.tab();
|
|
1394
|
+
expect(
|
|
1395
|
+
screen.getByRole( 'button', { name: /today/i } )
|
|
1396
|
+
).toHaveFocus();
|
|
1397
|
+
} );
|
|
1398
|
+
} );
|
|
1399
|
+
|
|
1400
|
+
// Note: we're not testing localization of strings. We're only testing
|
|
1401
|
+
// that the date formatting, computed dir, and calendar format are correct.
|
|
1402
|
+
describe( 'Localization', () => {
|
|
1403
|
+
it( 'should localize the calendar based on the `locale` prop', async () => {
|
|
1404
|
+
const user = setupUserEvent();
|
|
1405
|
+
|
|
1406
|
+
render( <DateRangeCalendar locale={ ar } /> );
|
|
1407
|
+
|
|
1408
|
+
// Check computed writing direction
|
|
1409
|
+
expect(
|
|
1410
|
+
screen.getByRole( 'application', {
|
|
1411
|
+
name: 'Date range calendar',
|
|
1412
|
+
} )
|
|
1413
|
+
).toHaveAttribute( 'dir', 'rtl' );
|
|
1414
|
+
|
|
1415
|
+
// Check month name
|
|
1416
|
+
const grid = screen.getByRole( 'grid', {
|
|
1417
|
+
name: monthNameFormatter( 'ar' ).format( today ),
|
|
1418
|
+
} );
|
|
1419
|
+
expect( grid ).toBeVisible();
|
|
1420
|
+
|
|
1421
|
+
// Check today button
|
|
1422
|
+
expect( getDateButton( today, {}, 'ar' ) ).toHaveAccessibleName(
|
|
1423
|
+
/today/i
|
|
1424
|
+
);
|
|
1425
|
+
|
|
1426
|
+
await user.tab();
|
|
1427
|
+
await user.tab();
|
|
1428
|
+
await user.tab();
|
|
1429
|
+
expect( getDateButton( today, {}, 'ar' ) ).toHaveFocus();
|
|
1430
|
+
|
|
1431
|
+
await user.keyboard( '{Home}' );
|
|
1432
|
+
expect(
|
|
1433
|
+
getDateButton( startOfWeek( today, { locale: ar } ), {}, 'ar' )
|
|
1434
|
+
).toHaveFocus();
|
|
1435
|
+
} );
|
|
1436
|
+
|
|
1437
|
+
it( 'should support timezones according to the `timeZone` prop', async () => {
|
|
1438
|
+
const user = setupUserEvent();
|
|
1439
|
+
const onSelect = jest.fn();
|
|
1440
|
+
|
|
1441
|
+
render(
|
|
1442
|
+
<DateRangeCalendar
|
|
1443
|
+
timeZone="Asia/Tokyo"
|
|
1444
|
+
onSelect={ onSelect }
|
|
1445
|
+
/>
|
|
1446
|
+
);
|
|
1447
|
+
|
|
1448
|
+
// For someone in Tokyo, the current time simulated in the test
|
|
1449
|
+
// (ie. 20:00 UTC) is the next day.
|
|
1450
|
+
expect( getDateButton( tomorrow ) ).toHaveAccessibleName(
|
|
1451
|
+
/today/i
|
|
1452
|
+
);
|
|
1453
|
+
|
|
1454
|
+
// Select tomorrow's button (which is today in Tokyo)
|
|
1455
|
+
const tomorrowButton = getDateButton( tomorrow );
|
|
1456
|
+
await user.click( tomorrowButton );
|
|
1457
|
+
|
|
1458
|
+
const tomorrowFromTokyoTimezone = addHours(
|
|
1459
|
+
tomorrow,
|
|
1460
|
+
new TZDate( tomorrow, 'Asia/Tokyo' ).getTimezoneOffset() / 60
|
|
1461
|
+
);
|
|
1462
|
+
|
|
1463
|
+
expect( onSelect ).toHaveBeenCalledTimes( 1 );
|
|
1464
|
+
expect( onSelect ).toHaveBeenCalledWith(
|
|
1465
|
+
{
|
|
1466
|
+
from: tomorrowFromTokyoTimezone,
|
|
1467
|
+
to: tomorrowFromTokyoTimezone,
|
|
1468
|
+
},
|
|
1469
|
+
tomorrowFromTokyoTimezone,
|
|
1470
|
+
expect.objectContaining( { today: true } ),
|
|
1471
|
+
expect.objectContaining( {
|
|
1472
|
+
type: 'click',
|
|
1473
|
+
target: tomorrowButton,
|
|
1474
|
+
} )
|
|
1475
|
+
);
|
|
1476
|
+
} );
|
|
1477
|
+
|
|
1478
|
+
it( 'should handle timezoned dates and convert them to the calendar timezone', async () => {
|
|
1479
|
+
// Still the same time from UTC's POV, just expressed in Tokyo time.
|
|
1480
|
+
const tomorrowAtMidnightInTokyoTZ = new TZDate(
|
|
1481
|
+
tomorrow,
|
|
1482
|
+
'Asia/Tokyo'
|
|
1483
|
+
);
|
|
1484
|
+
const dayAfterTomorrowInTokyoTZ = new TZDate(
|
|
1485
|
+
addDays( tomorrow, 1 ),
|
|
1486
|
+
'Asia/Tokyo'
|
|
1487
|
+
);
|
|
1488
|
+
const timezoneRange = {
|
|
1489
|
+
from: tomorrowAtMidnightInTokyoTZ,
|
|
1490
|
+
to: dayAfterTomorrowInTokyoTZ,
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
render(
|
|
1494
|
+
<DateRangeCalendar
|
|
1495
|
+
defaultSelected={ timezoneRange }
|
|
1496
|
+
// Note: using "Etc/GMT+2" instead of "-02:00" because support for raw offsets was introduced in Node v22 (while currently the repository still targets Node v20).
|
|
1497
|
+
timeZone="Etc/GMT+2"
|
|
1498
|
+
/>
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
// Changing the calendar timezone to UTC-2 makes the dates become
|
|
1502
|
+
// earlier by 1 day (from midnight to 10pm the previous day).
|
|
1503
|
+
expect( getDateCell( today, { selected: true } ) ).toBeVisible();
|
|
1504
|
+
expect( getDateCell( tomorrow, { selected: true } ) ).toBeVisible();
|
|
1505
|
+
} );
|
|
1506
|
+
} );
|
|
1507
|
+
|
|
1508
|
+
describe( 'usePreviewRange', () => {
|
|
1509
|
+
const previewToday = new Date( '2024-03-15' );
|
|
1510
|
+
const previewTomorrow = addDays( previewToday, 1 );
|
|
1511
|
+
const previewYesterday = subDays( previewToday, 1 );
|
|
1512
|
+
const previewNextWeek = addDays( previewToday, 7 );
|
|
1513
|
+
|
|
1514
|
+
it( 'should return undefined when there is no hovered date', () => {
|
|
1515
|
+
const { result } = renderHook( () =>
|
|
1516
|
+
usePreviewRange( {
|
|
1517
|
+
selected: { from: previewToday, to: previewTomorrow },
|
|
1518
|
+
hoveredDate: undefined,
|
|
1519
|
+
} )
|
|
1520
|
+
);
|
|
1521
|
+
|
|
1522
|
+
expect( result.current ).toBeUndefined();
|
|
1523
|
+
} );
|
|
1524
|
+
|
|
1525
|
+
it( 'should return undefined when there is no selected date', () => {
|
|
1526
|
+
const { result } = renderHook( () =>
|
|
1527
|
+
usePreviewRange( {
|
|
1528
|
+
selected: undefined,
|
|
1529
|
+
hoveredDate: previewToday,
|
|
1530
|
+
} )
|
|
1531
|
+
);
|
|
1532
|
+
|
|
1533
|
+
expect( result.current ).toBeUndefined();
|
|
1534
|
+
} );
|
|
1535
|
+
|
|
1536
|
+
it( 'should return undefined when there is no selected start date', () => {
|
|
1537
|
+
const { result } = renderHook( () =>
|
|
1538
|
+
usePreviewRange( {
|
|
1539
|
+
selected: { from: undefined, to: previewTomorrow },
|
|
1540
|
+
hoveredDate: previewToday,
|
|
1541
|
+
} )
|
|
1542
|
+
);
|
|
1543
|
+
|
|
1544
|
+
expect( result.current ).toBeUndefined();
|
|
1545
|
+
} );
|
|
1546
|
+
|
|
1547
|
+
it( 'should show preview when hovering before selected range', () => {
|
|
1548
|
+
const { result } = renderHook( () =>
|
|
1549
|
+
usePreviewRange( {
|
|
1550
|
+
selected: { from: previewToday, to: previewTomorrow },
|
|
1551
|
+
hoveredDate: previewYesterday,
|
|
1552
|
+
} )
|
|
1553
|
+
);
|
|
1554
|
+
|
|
1555
|
+
expect( result.current ).toEqual( {
|
|
1556
|
+
from: previewYesterday,
|
|
1557
|
+
to: previewToday,
|
|
1558
|
+
} );
|
|
1559
|
+
} );
|
|
1560
|
+
|
|
1561
|
+
it( 'should show preview when hovering between selected range dates', () => {
|
|
1562
|
+
const { result } = renderHook( () =>
|
|
1563
|
+
usePreviewRange( {
|
|
1564
|
+
selected: { from: previewYesterday, to: previewTomorrow },
|
|
1565
|
+
hoveredDate: previewToday,
|
|
1566
|
+
} )
|
|
1567
|
+
);
|
|
1568
|
+
|
|
1569
|
+
expect( result.current ).toEqual( {
|
|
1570
|
+
from: previewYesterday,
|
|
1571
|
+
to: previewToday,
|
|
1572
|
+
} );
|
|
1573
|
+
} );
|
|
1574
|
+
|
|
1575
|
+
it( 'should show preview when hovering after selected range', () => {
|
|
1576
|
+
const { result } = renderHook( () =>
|
|
1577
|
+
usePreviewRange( {
|
|
1578
|
+
selected: { from: previewYesterday, to: previewToday },
|
|
1579
|
+
hoveredDate: previewTomorrow,
|
|
1580
|
+
} )
|
|
1581
|
+
);
|
|
1582
|
+
|
|
1583
|
+
expect( result.current ).toEqual( {
|
|
1584
|
+
from: previewToday,
|
|
1585
|
+
to: previewTomorrow,
|
|
1586
|
+
} );
|
|
1587
|
+
} );
|
|
1588
|
+
|
|
1589
|
+
it( 'should show preview when hovering after selected range with no end date', () => {
|
|
1590
|
+
const { result } = renderHook( () =>
|
|
1591
|
+
usePreviewRange( {
|
|
1592
|
+
selected: { from: previewToday },
|
|
1593
|
+
hoveredDate: previewTomorrow,
|
|
1594
|
+
} )
|
|
1595
|
+
);
|
|
1596
|
+
|
|
1597
|
+
expect( result.current ).toEqual( {
|
|
1598
|
+
from: previewToday,
|
|
1599
|
+
to: previewTomorrow,
|
|
1600
|
+
} );
|
|
1601
|
+
} );
|
|
1602
|
+
|
|
1603
|
+
describe( 'min range constraint', () => {
|
|
1604
|
+
it( 'should collapse preview to single date when range is less than min', () => {
|
|
1605
|
+
const { result } = renderHook( () =>
|
|
1606
|
+
usePreviewRange( {
|
|
1607
|
+
selected: { from: previewToday },
|
|
1608
|
+
hoveredDate: previewTomorrow,
|
|
1609
|
+
min: 3,
|
|
1610
|
+
} )
|
|
1611
|
+
);
|
|
1612
|
+
|
|
1613
|
+
expect( result.current ).toEqual( {
|
|
1614
|
+
from: previewTomorrow,
|
|
1615
|
+
to: previewTomorrow,
|
|
1616
|
+
} );
|
|
1617
|
+
} );
|
|
1618
|
+
|
|
1619
|
+
it( 'should allow preview when range meets min requirement', () => {
|
|
1620
|
+
const { result } = renderHook( () =>
|
|
1621
|
+
usePreviewRange( {
|
|
1622
|
+
selected: { from: previewToday },
|
|
1623
|
+
hoveredDate: previewNextWeek,
|
|
1624
|
+
min: 3,
|
|
1625
|
+
} )
|
|
1626
|
+
);
|
|
1627
|
+
|
|
1628
|
+
expect( result.current ).toEqual( {
|
|
1629
|
+
from: previewToday,
|
|
1630
|
+
to: previewNextWeek,
|
|
1631
|
+
} );
|
|
1632
|
+
} );
|
|
1633
|
+
} );
|
|
1634
|
+
|
|
1635
|
+
describe( 'max range constraint', () => {
|
|
1636
|
+
it( 'should collapse preview to single date when range exceeds max', () => {
|
|
1637
|
+
const { result } = renderHook( () =>
|
|
1638
|
+
usePreviewRange( {
|
|
1639
|
+
selected: { from: previewToday },
|
|
1640
|
+
hoveredDate: previewNextWeek,
|
|
1641
|
+
max: 3,
|
|
1642
|
+
} )
|
|
1643
|
+
);
|
|
1644
|
+
|
|
1645
|
+
expect( result.current ).toEqual( {
|
|
1646
|
+
from: previewNextWeek,
|
|
1647
|
+
to: previewNextWeek,
|
|
1648
|
+
} );
|
|
1649
|
+
} );
|
|
1650
|
+
|
|
1651
|
+
it( 'should allow preview when range meets max requirement', () => {
|
|
1652
|
+
const { result } = renderHook( () =>
|
|
1653
|
+
usePreviewRange( {
|
|
1654
|
+
selected: { from: previewToday },
|
|
1655
|
+
hoveredDate: previewTomorrow,
|
|
1656
|
+
max: 3,
|
|
1657
|
+
} )
|
|
1658
|
+
);
|
|
1659
|
+
|
|
1660
|
+
expect( result.current ).toEqual( {
|
|
1661
|
+
from: previewToday,
|
|
1662
|
+
to: previewTomorrow,
|
|
1663
|
+
} );
|
|
1664
|
+
} );
|
|
1665
|
+
} );
|
|
1666
|
+
|
|
1667
|
+
describe( 'disabled dates', () => {
|
|
1668
|
+
it( 'should collapse preview to single date when range contains disabled dates and excludeDisabled is true', () => {
|
|
1669
|
+
const { result } = renderHook( () =>
|
|
1670
|
+
usePreviewRange( {
|
|
1671
|
+
selected: { from: previewToday },
|
|
1672
|
+
hoveredDate: previewNextWeek,
|
|
1673
|
+
disabled: [ previewTomorrow ],
|
|
1674
|
+
excludeDisabled: true,
|
|
1675
|
+
} )
|
|
1676
|
+
);
|
|
1677
|
+
|
|
1678
|
+
expect( result.current ).toEqual( {
|
|
1679
|
+
from: previewNextWeek,
|
|
1680
|
+
to: previewNextWeek,
|
|
1681
|
+
} );
|
|
1682
|
+
} );
|
|
1683
|
+
|
|
1684
|
+
it( 'should allow preview when range contains disabled dates but excludeDisabled is false', () => {
|
|
1685
|
+
const { result } = renderHook( () =>
|
|
1686
|
+
usePreviewRange( {
|
|
1687
|
+
selected: { from: previewToday },
|
|
1688
|
+
hoveredDate: previewNextWeek,
|
|
1689
|
+
disabled: [ previewTomorrow ],
|
|
1690
|
+
excludeDisabled: false,
|
|
1691
|
+
} )
|
|
1692
|
+
);
|
|
1693
|
+
|
|
1694
|
+
expect( result.current ).toEqual( {
|
|
1695
|
+
from: previewToday,
|
|
1696
|
+
to: previewNextWeek,
|
|
1697
|
+
} );
|
|
1698
|
+
} );
|
|
1699
|
+
} );
|
|
1700
|
+
} );
|
|
1701
|
+
} );
|