@dhis2-ui/calendar 10.0.0-alpha.5 → 10.0.0-alpha.7
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/build/cjs/calendar/calendar.js +3 -8
- package/build/cjs/calendar-input/__tests__/calendar-input.test.js +220 -3
- package/build/cjs/calendar-input/calendar-input.js +36 -27
- package/build/cjs/stories/calendar-story-wrapper.js +3 -8
- package/build/es/calendar/calendar.js +3 -8
- package/build/es/calendar-input/__tests__/calendar-input.test.js +218 -3
- package/build/es/calendar-input/calendar-input.js +36 -27
- package/build/es/stories/calendar-story-wrapper.js +3 -8
- package/package.json +8 -8
@@ -19,10 +19,10 @@ const Calendar = _ref => {
|
|
19
19
|
dir,
|
20
20
|
locale,
|
21
21
|
numberingSystem,
|
22
|
-
weekDayFormat,
|
22
|
+
weekDayFormat = 'narrow',
|
23
23
|
timeZone,
|
24
|
-
width,
|
25
|
-
cellSize
|
24
|
+
width = '240px',
|
25
|
+
cellSize = '32px'
|
26
26
|
} = _ref;
|
27
27
|
const [selectedDateString, setSelectedDateString] = (0, _react.useState)(date);
|
28
28
|
const languageDirection = (0, _multiCalendarDates.useResolvedDirection)(dir, locale);
|
@@ -70,11 +70,6 @@ const Calendar = _ref => {
|
|
70
70
|
return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_calendarContainer.CalendarContainer, calendarProps));
|
71
71
|
};
|
72
72
|
exports.Calendar = Calendar;
|
73
|
-
Calendar.defaultProps = {
|
74
|
-
cellSize: '32px',
|
75
|
-
width: '240px',
|
76
|
-
weekDayFormat: 'narrow'
|
77
|
-
};
|
78
73
|
const CalendarProps = exports.CalendarProps = {
|
79
74
|
/** the calendar to use such gregory, ethiopic, nepali - full supported list here: https://github.com/dhis2/multi-calendar-dates/blob/main/src/constants/calendars.ts */
|
80
75
|
calendar: _propTypes.default.any.isRequired,
|
@@ -1,10 +1,21 @@
|
|
1
1
|
"use strict";
|
2
2
|
|
3
|
+
var _button = require("@dhis2-ui/button");
|
3
4
|
var _react = require("@testing-library/react");
|
4
|
-
var
|
5
|
+
var _userEvent = _interopRequireDefault(require("@testing-library/user-event"));
|
6
|
+
var _react2 = _interopRequireWildcard(require("react"));
|
7
|
+
var _reactFinalForm = require("react-final-form");
|
5
8
|
var _calendarInput = require("../calendar-input.js");
|
9
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
10
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
6
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
12
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
7
13
|
describe('Calendar Input', () => {
|
14
|
+
beforeEach(() => {
|
15
|
+
jest.useFakeTimers();
|
16
|
+
jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'));
|
17
|
+
});
|
18
|
+
afterEach(jest.useRealTimers);
|
8
19
|
it('allow selection of a date through the calendar widget', async () => {
|
9
20
|
const onDateSelectMock = jest.fn();
|
10
21
|
const screen = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(_calendarInput.CalendarInput, {
|
@@ -15,7 +26,7 @@ describe('Calendar Input', () => {
|
|
15
26
|
_react.fireEvent.focus(dateInput);
|
16
27
|
const calendar = await screen.findByTestId('calendar');
|
17
28
|
expect(calendar).toBeInTheDocument();
|
18
|
-
const todayString =
|
29
|
+
const todayString = '2024-10-22';
|
19
30
|
const today = (0, _react.within)(calendar).getByTestId(todayString);
|
20
31
|
_react.fireEvent.click(today);
|
21
32
|
await (0, _react.waitFor)(() => {
|
@@ -43,4 +54,210 @@ describe('Calendar Input', () => {
|
|
43
54
|
calendarDateString: dateInputString
|
44
55
|
}));
|
45
56
|
});
|
46
|
-
|
57
|
+
describe('validation', () => {
|
58
|
+
it('should validate minimum date', async () => {
|
59
|
+
const onDateSelectMock = jest.fn();
|
60
|
+
const screen = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(CalendarWithValidation, {
|
61
|
+
calendar: "gregory",
|
62
|
+
minDate: "2024-01-01",
|
63
|
+
onDateSelect: onDateSelectMock
|
64
|
+
}));
|
65
|
+
const dateInputString = '2023-10-12';
|
66
|
+
const dateInput = (0, _react.within)(screen.getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
67
|
+
_userEvent.default.clear(dateInput);
|
68
|
+
_userEvent.default.type(dateInput, dateInputString);
|
69
|
+
_userEvent.default.tab();
|
70
|
+
expect(await screen.findByText('Date 2023-10-12 is less than the minimum allowed date 2024-01-01.'));
|
71
|
+
expect(onDateSelectMock).toHaveBeenCalledTimes(1);
|
72
|
+
});
|
73
|
+
it('should validate maximum date', async () => {
|
74
|
+
const {
|
75
|
+
getByTestId,
|
76
|
+
findByText
|
77
|
+
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(CalendarWithValidation, {
|
78
|
+
calendar: "gregory",
|
79
|
+
maxDate: "2024-01-01"
|
80
|
+
}));
|
81
|
+
const dateInputString = '2024-10-12';
|
82
|
+
const dateInput = (0, _react.within)(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
83
|
+
_userEvent.default.clear(dateInput);
|
84
|
+
_userEvent.default.type(dateInput, dateInputString);
|
85
|
+
_userEvent.default.tab();
|
86
|
+
expect(await findByText('Date 2024-10-12 is greater than the maximum allowed date 2024-01-01.'));
|
87
|
+
});
|
88
|
+
it('should validate date in ethiopic calendar', async () => {
|
89
|
+
const onDateSelectMock = jest.fn();
|
90
|
+
const {
|
91
|
+
getByTestId,
|
92
|
+
findByText,
|
93
|
+
queryByText
|
94
|
+
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(CalendarWithValidation, {
|
95
|
+
calendar: "ethiopian",
|
96
|
+
minDate: "2018-13-04",
|
97
|
+
onDateSelect: onDateSelectMock
|
98
|
+
}));
|
99
|
+
let dateInputString = '2018-13-02';
|
100
|
+
const dateInput = (0, _react.within)(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
101
|
+
_userEvent.default.clear(dateInput);
|
102
|
+
_userEvent.default.type(dateInput, dateInputString);
|
103
|
+
_userEvent.default.tab();
|
104
|
+
expect(await findByText('Date 2018-13-02 is less than the minimum allowed date 2018-13-04.'));
|
105
|
+
dateInputString = '2018-13-05';
|
106
|
+
_userEvent.default.clear(dateInput);
|
107
|
+
_userEvent.default.type(dateInput, dateInputString);
|
108
|
+
_userEvent.default.tab();
|
109
|
+
expect(queryByText('Date 2018-13-04 is less than the minimum allowed date 2018-13-05.')).not.toBeInTheDocument();
|
110
|
+
dateInputString = '2018-13-07';
|
111
|
+
_userEvent.default.clear(dateInput);
|
112
|
+
_userEvent.default.type(dateInput, dateInputString);
|
113
|
+
_userEvent.default.tab();
|
114
|
+
expect(await findByText('Invalid date in specified calendar')).toBeInTheDocument();
|
115
|
+
});
|
116
|
+
it('should validate date in nepali calendar', async () => {
|
117
|
+
const onDateSelectMock = jest.fn();
|
118
|
+
const {
|
119
|
+
getByTestId,
|
120
|
+
findByText,
|
121
|
+
queryByText
|
122
|
+
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(CalendarWithValidation, {
|
123
|
+
calendar: "nepali",
|
124
|
+
maxDate: "2080-05-30",
|
125
|
+
onDateSelect: onDateSelectMock
|
126
|
+
}));
|
127
|
+
let dateInputString = '2080-06-01';
|
128
|
+
const dateInput = (0, _react.within)(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
129
|
+
_userEvent.default.clear(dateInput);
|
130
|
+
_userEvent.default.type(dateInput, dateInputString);
|
131
|
+
_userEvent.default.tab();
|
132
|
+
expect(await findByText('Date 2080-06-01 is greater than the maximum allowed date 2080-05-30.'));
|
133
|
+
dateInputString = '2080-04-32';
|
134
|
+
_userEvent.default.clear(dateInput);
|
135
|
+
_userEvent.default.type(dateInput, dateInputString);
|
136
|
+
_userEvent.default.tab();
|
137
|
+
expect(queryByText(/greater than the maximum allowed date/)).not.toBeInTheDocument();
|
138
|
+
dateInputString = '2080-01-32';
|
139
|
+
_userEvent.default.clear(dateInput);
|
140
|
+
_userEvent.default.type(dateInput, dateInputString);
|
141
|
+
_userEvent.default.tab();
|
142
|
+
expect(await findByText('Invalid date in specified calendar')).toBeInTheDocument();
|
143
|
+
});
|
144
|
+
it('should validate from date picker', async () => {
|
145
|
+
const onDateSelectMock = jest.fn();
|
146
|
+
const {
|
147
|
+
queryByText,
|
148
|
+
getByText,
|
149
|
+
getByTestId
|
150
|
+
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(CalendarWithValidation, {
|
151
|
+
calendar: "gregory",
|
152
|
+
minDate: "2024-02-16",
|
153
|
+
onDateSelect: onDateSelectMock
|
154
|
+
}));
|
155
|
+
const dateInput = (0, _react.within)(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
156
|
+
await _userEvent.default.click(dateInput);
|
157
|
+
await _userEvent.default.click(getByText('17'));
|
158
|
+
expect(queryByText('17')).not.toBeInTheDocument();
|
159
|
+
|
160
|
+
// Bug where callback used to be called first with undefined
|
161
|
+
expect(onDateSelectMock).toHaveBeenCalledTimes(1);
|
162
|
+
expect(onDateSelectMock).toHaveBeenCalledWith({
|
163
|
+
calendarDateString: '2024-10-17',
|
164
|
+
validation: {
|
165
|
+
error: false,
|
166
|
+
valid: true,
|
167
|
+
warning: false
|
168
|
+
}
|
169
|
+
});
|
170
|
+
});
|
171
|
+
it('should validate with Clear', async () => {
|
172
|
+
const onDateSelectMock = jest.fn();
|
173
|
+
const {
|
174
|
+
queryByText,
|
175
|
+
getByText,
|
176
|
+
getByTestId
|
177
|
+
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(CalendarWithValidation, {
|
178
|
+
calendar: "gregory",
|
179
|
+
minDate: "2024-02-16",
|
180
|
+
onDateSelect: onDateSelectMock,
|
181
|
+
clearable: true
|
182
|
+
}));
|
183
|
+
const dateInputString = '2023-10-12';
|
184
|
+
const dateInput = (0, _react.within)(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
185
|
+
_userEvent.default.clear(dateInput);
|
186
|
+
_userEvent.default.type(dateInput, dateInputString);
|
187
|
+
_userEvent.default.tab();
|
188
|
+
expect(getByTestId('dhis2-uiwidgets-calendar-inputfield-validation')).toBeInTheDocument();
|
189
|
+
await _userEvent.default.click(getByText('Clear'));
|
190
|
+
expect(queryByText('17')).not.toBeInTheDocument();
|
191
|
+
expect(onDateSelectMock).toHaveBeenLastCalledWith({
|
192
|
+
calendarDateString: null,
|
193
|
+
validation: {
|
194
|
+
valid: true
|
195
|
+
}
|
196
|
+
});
|
197
|
+
});
|
198
|
+
it('should validate when Clearing manually (i.e. deleting text not using clear button)', async () => {
|
199
|
+
const onDateSelectMock = jest.fn();
|
200
|
+
const {
|
201
|
+
getByTestId
|
202
|
+
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(CalendarWithValidation, {
|
203
|
+
calendar: "gregory",
|
204
|
+
minDate: "2024-02-16",
|
205
|
+
onDateSelect: onDateSelectMock,
|
206
|
+
clearable: true
|
207
|
+
}));
|
208
|
+
const dateInputString = '2023-10-12';
|
209
|
+
const dateInput = (0, _react.within)(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
210
|
+
_userEvent.default.clear(dateInput);
|
211
|
+
_userEvent.default.type(dateInput, dateInputString);
|
212
|
+
_userEvent.default.tab();
|
213
|
+
expect(getByTestId('dhis2-uiwidgets-calendar-inputfield-validation')).toBeInTheDocument();
|
214
|
+
_userEvent.default.clear(dateInput);
|
215
|
+
_userEvent.default.tab();
|
216
|
+
expect(onDateSelectMock).toHaveBeenCalledWith({
|
217
|
+
calendarDateString: null,
|
218
|
+
validation: {
|
219
|
+
valid: true
|
220
|
+
}
|
221
|
+
});
|
222
|
+
});
|
223
|
+
});
|
224
|
+
});
|
225
|
+
const CalendarWithValidation = propsFromParent => {
|
226
|
+
const [date, setDate] = (0, _react2.useState)();
|
227
|
+
const [validation, setValidation] = (0, _react2.useState)({});
|
228
|
+
const errored = () => {
|
229
|
+
if (validation !== null && validation !== void 0 && validation.error) {
|
230
|
+
return {
|
231
|
+
calendar: validation.validationText
|
232
|
+
};
|
233
|
+
}
|
234
|
+
};
|
235
|
+
return /*#__PURE__*/_react2.default.createElement(_reactFinalForm.Form, {
|
236
|
+
onSubmit: () => {},
|
237
|
+
validate: errored
|
238
|
+
}, _ref => {
|
239
|
+
let {
|
240
|
+
handleSubmit,
|
241
|
+
invalid
|
242
|
+
} = _ref;
|
243
|
+
return /*#__PURE__*/_react2.default.createElement("form", null, /*#__PURE__*/_react2.default.createElement(_reactFinalForm.Field, {
|
244
|
+
name: "calendar"
|
245
|
+
}, props => /*#__PURE__*/_react2.default.createElement(_calendarInput.CalendarInput, _extends({}, props, {
|
246
|
+
date: date,
|
247
|
+
label: "Enter a date",
|
248
|
+
editable: true,
|
249
|
+
calendar: "gregory"
|
250
|
+
}, validation, propsFromParent, {
|
251
|
+
onDateSelect: date => {
|
252
|
+
var _propsFromParent$onDa;
|
253
|
+
setDate(date === null || date === void 0 ? void 0 : date.calendarDateString);
|
254
|
+
setValidation(date === null || date === void 0 ? void 0 : date.validation);
|
255
|
+
(_propsFromParent$onDa = propsFromParent.onDateSelect) === null || _propsFromParent$onDa === void 0 ? void 0 : _propsFromParent$onDa.call(propsFromParent, date);
|
256
|
+
}
|
257
|
+
}))), /*#__PURE__*/_react2.default.createElement(_button.Button, {
|
258
|
+
type: "submit",
|
259
|
+
disabled: invalid,
|
260
|
+
onClick: handleSubmit
|
261
|
+
}, "Submit"));
|
262
|
+
});
|
263
|
+
};
|
@@ -43,6 +43,7 @@ const CalendarInput = function () {
|
|
43
43
|
format,
|
44
44
|
strictValidation,
|
45
45
|
inputWidth,
|
46
|
+
dataTest = 'dhis2-uiwidgets-calendar-inputfield',
|
46
47
|
...rest
|
47
48
|
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
48
49
|
const ref = (0, _react.useRef)();
|
@@ -56,26 +57,43 @@ const CalendarInput = function () {
|
|
56
57
|
numberingSystem,
|
57
58
|
weekDayFormat
|
58
59
|
}), [calendar, locale, numberingSystem, weekDayFormat]);
|
60
|
+
const onChooseDate = (date, validationOptions) => {
|
61
|
+
// Handling clearing (with clicking the Clear button, or deleting input)
|
62
|
+
if (clearable && (date === null || date === '')) {
|
63
|
+
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
64
|
+
calendarDateString: null,
|
65
|
+
validation: {
|
66
|
+
valid: true
|
67
|
+
}
|
68
|
+
});
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
|
72
|
+
// ToDo: This is now a workaround for handling choosing from the date picker
|
73
|
+
// where the blur event gets triggered causing a call with undefined first
|
74
|
+
if (date === undefined) {
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
const validation = (0, _multiCalendarDates.validateDateString)(date, validationOptions);
|
78
|
+
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
79
|
+
calendarDateString: date,
|
80
|
+
validation
|
81
|
+
});
|
82
|
+
};
|
83
|
+
const validationOptions = (0, _react.useMemo)(() => ({
|
84
|
+
calendar,
|
85
|
+
format,
|
86
|
+
minDateString: minDate,
|
87
|
+
maxDateString: maxDate,
|
88
|
+
strictValidation
|
89
|
+
}), [calendar, format, maxDate, minDate, strictValidation]);
|
59
90
|
const pickerResults = (0, _multiCalendarDates.useDatePicker)({
|
60
91
|
onDateSelect: result => {
|
61
|
-
|
62
|
-
calendar,
|
63
|
-
format,
|
64
|
-
minDateString: minDate,
|
65
|
-
maxDateString: maxDate,
|
66
|
-
strictValidation
|
67
|
-
});
|
92
|
+
onChooseDate(result.calendarDateString, validationOptions);
|
68
93
|
setOpen(false);
|
69
|
-
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
70
|
-
calendarDateString: result.calendarDateString,
|
71
|
-
validation
|
72
|
-
});
|
73
94
|
},
|
74
95
|
date,
|
75
|
-
|
76
|
-
maxDate: maxDate,
|
77
|
-
strictValidation: strictValidation,
|
78
|
-
format: format,
|
96
|
+
...validationOptions,
|
79
97
|
options: useDatePickerOptions
|
80
98
|
});
|
81
99
|
const handleChange = e => {
|
@@ -83,17 +101,7 @@ const CalendarInput = function () {
|
|
83
101
|
setPartialDate(e.value);
|
84
102
|
};
|
85
103
|
const handleBlur = (_, e) => {
|
86
|
-
|
87
|
-
calendar,
|
88
|
-
format,
|
89
|
-
minDateString: minDate,
|
90
|
-
maxDateString: maxDate,
|
91
|
-
strictValidation
|
92
|
-
});
|
93
|
-
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
94
|
-
calendarDateString: partialDate,
|
95
|
-
validation
|
96
|
-
});
|
104
|
+
onChooseDate(partialDate, validationOptions);
|
97
105
|
if (excludeRef.current && !excludeRef.current.contains(e.relatedTarget)) {
|
98
106
|
setOpen(false);
|
99
107
|
}
|
@@ -127,6 +135,7 @@ const CalendarInput = function () {
|
|
127
135
|
}, /*#__PURE__*/_react.default.createElement(_input.InputField, _extends({
|
128
136
|
label: _index.default.t('Pick a date')
|
129
137
|
}, rest, {
|
138
|
+
dataTest: dataTest,
|
130
139
|
type: "text",
|
131
140
|
onFocus: onFocus,
|
132
141
|
value: partialDate,
|
@@ -143,7 +152,7 @@ const CalendarInput = function () {
|
|
143
152
|
secondary: true,
|
144
153
|
small: true,
|
145
154
|
onClick: () => {
|
146
|
-
|
155
|
+
onChooseDate(null);
|
147
156
|
},
|
148
157
|
type: "button"
|
149
158
|
}, _index.default.t('Clear')))), open && /*#__PURE__*/_react.default.createElement(_layer.Layer, {
|
@@ -18,13 +18,13 @@ const {
|
|
18
18
|
} = _multiCalendarDates.constants;
|
19
19
|
const CalendarStoryWrapper = props => {
|
20
20
|
const {
|
21
|
-
calendar,
|
21
|
+
calendar = 'gregory',
|
22
22
|
locale,
|
23
23
|
timeZone,
|
24
24
|
dir,
|
25
|
-
component: Component,
|
25
|
+
component: Component = _calendar.Calendar,
|
26
26
|
date,
|
27
|
-
weekDayFormat
|
27
|
+
weekDayFormat = 'narrow'
|
28
28
|
} = props;
|
29
29
|
const [selectedCalendar, setSelectedCalendar] = (0, _react.useState)(calendar);
|
30
30
|
const [selectedNumberingSystem, setSelectedNumberingSystem] = (0, _react.useState)();
|
@@ -161,11 +161,6 @@ const CalendarStoryWrapper = props => {
|
|
161
161
|
}, selectedDate.calendarDateString)), /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("label", null, "callback:"), JSON.stringify(selectedDate, null, 2))))));
|
162
162
|
};
|
163
163
|
exports.CalendarStoryWrapper = CalendarStoryWrapper;
|
164
|
-
CalendarStoryWrapper.defaultProps = {
|
165
|
-
calendar: 'gregorian',
|
166
|
-
component: _calendar.Calendar,
|
167
|
-
weekDayFormat: 'narrow'
|
168
|
-
};
|
169
164
|
CalendarStoryWrapper.propTypes = {
|
170
165
|
calendar: _propTypes.default.string.isRequired,
|
171
166
|
component: _propTypes.default.elementType.isRequired,
|
@@ -10,10 +10,10 @@ export const Calendar = _ref => {
|
|
10
10
|
dir,
|
11
11
|
locale,
|
12
12
|
numberingSystem,
|
13
|
-
weekDayFormat,
|
13
|
+
weekDayFormat = 'narrow',
|
14
14
|
timeZone,
|
15
|
-
width,
|
16
|
-
cellSize
|
15
|
+
width = '240px',
|
16
|
+
cellSize = '32px'
|
17
17
|
} = _ref;
|
18
18
|
const [selectedDateString, setSelectedDateString] = useState(date);
|
19
19
|
const languageDirection = useResolvedDirection(dir, locale);
|
@@ -60,11 +60,6 @@ export const Calendar = _ref => {
|
|
60
60
|
}, [cellSize, date, dir, locale, pickerResults, width, languageDirection]);
|
61
61
|
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(CalendarContainer, calendarProps));
|
62
62
|
};
|
63
|
-
Calendar.defaultProps = {
|
64
|
-
cellSize: '32px',
|
65
|
-
width: '240px',
|
66
|
-
weekDayFormat: 'narrow'
|
67
|
-
};
|
68
63
|
export const CalendarProps = {
|
69
64
|
/** the calendar to use such gregory, ethiopic, nepali - full supported list here: https://github.com/dhis2/multi-calendar-dates/blob/main/src/constants/calendars.ts */
|
70
65
|
calendar: PropTypes.any.isRequired,
|
@@ -1,7 +1,16 @@
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
2
|
+
import { Button } from '@dhis2-ui/button';
|
1
3
|
import { fireEvent, render, waitFor, within } from '@testing-library/react';
|
2
|
-
import
|
4
|
+
import userEvent from '@testing-library/user-event';
|
5
|
+
import React, { useState } from 'react';
|
6
|
+
import { Field, Form } from 'react-final-form';
|
3
7
|
import { CalendarInput } from '../calendar-input.js';
|
4
8
|
describe('Calendar Input', () => {
|
9
|
+
beforeEach(() => {
|
10
|
+
jest.useFakeTimers();
|
11
|
+
jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'));
|
12
|
+
});
|
13
|
+
afterEach(jest.useRealTimers);
|
5
14
|
it('allow selection of a date through the calendar widget', async () => {
|
6
15
|
const onDateSelectMock = jest.fn();
|
7
16
|
const screen = render( /*#__PURE__*/React.createElement(CalendarInput, {
|
@@ -12,7 +21,7 @@ describe('Calendar Input', () => {
|
|
12
21
|
fireEvent.focus(dateInput);
|
13
22
|
const calendar = await screen.findByTestId('calendar');
|
14
23
|
expect(calendar).toBeInTheDocument();
|
15
|
-
const todayString =
|
24
|
+
const todayString = '2024-10-22';
|
16
25
|
const today = within(calendar).getByTestId(todayString);
|
17
26
|
fireEvent.click(today);
|
18
27
|
await waitFor(() => {
|
@@ -40,4 +49,210 @@ describe('Calendar Input', () => {
|
|
40
49
|
calendarDateString: dateInputString
|
41
50
|
}));
|
42
51
|
});
|
43
|
-
|
52
|
+
describe('validation', () => {
|
53
|
+
it('should validate minimum date', async () => {
|
54
|
+
const onDateSelectMock = jest.fn();
|
55
|
+
const screen = render( /*#__PURE__*/React.createElement(CalendarWithValidation, {
|
56
|
+
calendar: "gregory",
|
57
|
+
minDate: "2024-01-01",
|
58
|
+
onDateSelect: onDateSelectMock
|
59
|
+
}));
|
60
|
+
const dateInputString = '2023-10-12';
|
61
|
+
const dateInput = within(screen.getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
62
|
+
userEvent.clear(dateInput);
|
63
|
+
userEvent.type(dateInput, dateInputString);
|
64
|
+
userEvent.tab();
|
65
|
+
expect(await screen.findByText('Date 2023-10-12 is less than the minimum allowed date 2024-01-01.'));
|
66
|
+
expect(onDateSelectMock).toHaveBeenCalledTimes(1);
|
67
|
+
});
|
68
|
+
it('should validate maximum date', async () => {
|
69
|
+
const {
|
70
|
+
getByTestId,
|
71
|
+
findByText
|
72
|
+
} = render( /*#__PURE__*/React.createElement(CalendarWithValidation, {
|
73
|
+
calendar: "gregory",
|
74
|
+
maxDate: "2024-01-01"
|
75
|
+
}));
|
76
|
+
const dateInputString = '2024-10-12';
|
77
|
+
const dateInput = within(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
78
|
+
userEvent.clear(dateInput);
|
79
|
+
userEvent.type(dateInput, dateInputString);
|
80
|
+
userEvent.tab();
|
81
|
+
expect(await findByText('Date 2024-10-12 is greater than the maximum allowed date 2024-01-01.'));
|
82
|
+
});
|
83
|
+
it('should validate date in ethiopic calendar', async () => {
|
84
|
+
const onDateSelectMock = jest.fn();
|
85
|
+
const {
|
86
|
+
getByTestId,
|
87
|
+
findByText,
|
88
|
+
queryByText
|
89
|
+
} = render( /*#__PURE__*/React.createElement(CalendarWithValidation, {
|
90
|
+
calendar: "ethiopian",
|
91
|
+
minDate: "2018-13-04",
|
92
|
+
onDateSelect: onDateSelectMock
|
93
|
+
}));
|
94
|
+
let dateInputString = '2018-13-02';
|
95
|
+
const dateInput = within(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
96
|
+
userEvent.clear(dateInput);
|
97
|
+
userEvent.type(dateInput, dateInputString);
|
98
|
+
userEvent.tab();
|
99
|
+
expect(await findByText('Date 2018-13-02 is less than the minimum allowed date 2018-13-04.'));
|
100
|
+
dateInputString = '2018-13-05';
|
101
|
+
userEvent.clear(dateInput);
|
102
|
+
userEvent.type(dateInput, dateInputString);
|
103
|
+
userEvent.tab();
|
104
|
+
expect(queryByText('Date 2018-13-04 is less than the minimum allowed date 2018-13-05.')).not.toBeInTheDocument();
|
105
|
+
dateInputString = '2018-13-07';
|
106
|
+
userEvent.clear(dateInput);
|
107
|
+
userEvent.type(dateInput, dateInputString);
|
108
|
+
userEvent.tab();
|
109
|
+
expect(await findByText('Invalid date in specified calendar')).toBeInTheDocument();
|
110
|
+
});
|
111
|
+
it('should validate date in nepali calendar', async () => {
|
112
|
+
const onDateSelectMock = jest.fn();
|
113
|
+
const {
|
114
|
+
getByTestId,
|
115
|
+
findByText,
|
116
|
+
queryByText
|
117
|
+
} = render( /*#__PURE__*/React.createElement(CalendarWithValidation, {
|
118
|
+
calendar: "nepali",
|
119
|
+
maxDate: "2080-05-30",
|
120
|
+
onDateSelect: onDateSelectMock
|
121
|
+
}));
|
122
|
+
let dateInputString = '2080-06-01';
|
123
|
+
const dateInput = within(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
124
|
+
userEvent.clear(dateInput);
|
125
|
+
userEvent.type(dateInput, dateInputString);
|
126
|
+
userEvent.tab();
|
127
|
+
expect(await findByText('Date 2080-06-01 is greater than the maximum allowed date 2080-05-30.'));
|
128
|
+
dateInputString = '2080-04-32';
|
129
|
+
userEvent.clear(dateInput);
|
130
|
+
userEvent.type(dateInput, dateInputString);
|
131
|
+
userEvent.tab();
|
132
|
+
expect(queryByText(/greater than the maximum allowed date/)).not.toBeInTheDocument();
|
133
|
+
dateInputString = '2080-01-32';
|
134
|
+
userEvent.clear(dateInput);
|
135
|
+
userEvent.type(dateInput, dateInputString);
|
136
|
+
userEvent.tab();
|
137
|
+
expect(await findByText('Invalid date in specified calendar')).toBeInTheDocument();
|
138
|
+
});
|
139
|
+
it('should validate from date picker', async () => {
|
140
|
+
const onDateSelectMock = jest.fn();
|
141
|
+
const {
|
142
|
+
queryByText,
|
143
|
+
getByText,
|
144
|
+
getByTestId
|
145
|
+
} = render( /*#__PURE__*/React.createElement(CalendarWithValidation, {
|
146
|
+
calendar: "gregory",
|
147
|
+
minDate: "2024-02-16",
|
148
|
+
onDateSelect: onDateSelectMock
|
149
|
+
}));
|
150
|
+
const dateInput = within(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
151
|
+
await userEvent.click(dateInput);
|
152
|
+
await userEvent.click(getByText('17'));
|
153
|
+
expect(queryByText('17')).not.toBeInTheDocument();
|
154
|
+
|
155
|
+
// Bug where callback used to be called first with undefined
|
156
|
+
expect(onDateSelectMock).toHaveBeenCalledTimes(1);
|
157
|
+
expect(onDateSelectMock).toHaveBeenCalledWith({
|
158
|
+
calendarDateString: '2024-10-17',
|
159
|
+
validation: {
|
160
|
+
error: false,
|
161
|
+
valid: true,
|
162
|
+
warning: false
|
163
|
+
}
|
164
|
+
});
|
165
|
+
});
|
166
|
+
it('should validate with Clear', async () => {
|
167
|
+
const onDateSelectMock = jest.fn();
|
168
|
+
const {
|
169
|
+
queryByText,
|
170
|
+
getByText,
|
171
|
+
getByTestId
|
172
|
+
} = render( /*#__PURE__*/React.createElement(CalendarWithValidation, {
|
173
|
+
calendar: "gregory",
|
174
|
+
minDate: "2024-02-16",
|
175
|
+
onDateSelect: onDateSelectMock,
|
176
|
+
clearable: true
|
177
|
+
}));
|
178
|
+
const dateInputString = '2023-10-12';
|
179
|
+
const dateInput = within(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
180
|
+
userEvent.clear(dateInput);
|
181
|
+
userEvent.type(dateInput, dateInputString);
|
182
|
+
userEvent.tab();
|
183
|
+
expect(getByTestId('dhis2-uiwidgets-calendar-inputfield-validation')).toBeInTheDocument();
|
184
|
+
await userEvent.click(getByText('Clear'));
|
185
|
+
expect(queryByText('17')).not.toBeInTheDocument();
|
186
|
+
expect(onDateSelectMock).toHaveBeenLastCalledWith({
|
187
|
+
calendarDateString: null,
|
188
|
+
validation: {
|
189
|
+
valid: true
|
190
|
+
}
|
191
|
+
});
|
192
|
+
});
|
193
|
+
it('should validate when Clearing manually (i.e. deleting text not using clear button)', async () => {
|
194
|
+
const onDateSelectMock = jest.fn();
|
195
|
+
const {
|
196
|
+
getByTestId
|
197
|
+
} = render( /*#__PURE__*/React.createElement(CalendarWithValidation, {
|
198
|
+
calendar: "gregory",
|
199
|
+
minDate: "2024-02-16",
|
200
|
+
onDateSelect: onDateSelectMock,
|
201
|
+
clearable: true
|
202
|
+
}));
|
203
|
+
const dateInputString = '2023-10-12';
|
204
|
+
const dateInput = within(getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
205
|
+
userEvent.clear(dateInput);
|
206
|
+
userEvent.type(dateInput, dateInputString);
|
207
|
+
userEvent.tab();
|
208
|
+
expect(getByTestId('dhis2-uiwidgets-calendar-inputfield-validation')).toBeInTheDocument();
|
209
|
+
userEvent.clear(dateInput);
|
210
|
+
userEvent.tab();
|
211
|
+
expect(onDateSelectMock).toHaveBeenCalledWith({
|
212
|
+
calendarDateString: null,
|
213
|
+
validation: {
|
214
|
+
valid: true
|
215
|
+
}
|
216
|
+
});
|
217
|
+
});
|
218
|
+
});
|
219
|
+
});
|
220
|
+
const CalendarWithValidation = propsFromParent => {
|
221
|
+
const [date, setDate] = useState();
|
222
|
+
const [validation, setValidation] = useState({});
|
223
|
+
const errored = () => {
|
224
|
+
if (validation !== null && validation !== void 0 && validation.error) {
|
225
|
+
return {
|
226
|
+
calendar: validation.validationText
|
227
|
+
};
|
228
|
+
}
|
229
|
+
};
|
230
|
+
return /*#__PURE__*/React.createElement(Form, {
|
231
|
+
onSubmit: () => {},
|
232
|
+
validate: errored
|
233
|
+
}, _ref => {
|
234
|
+
let {
|
235
|
+
handleSubmit,
|
236
|
+
invalid
|
237
|
+
} = _ref;
|
238
|
+
return /*#__PURE__*/React.createElement("form", null, /*#__PURE__*/React.createElement(Field, {
|
239
|
+
name: "calendar"
|
240
|
+
}, props => /*#__PURE__*/React.createElement(CalendarInput, _extends({}, props, {
|
241
|
+
date: date,
|
242
|
+
label: "Enter a date",
|
243
|
+
editable: true,
|
244
|
+
calendar: "gregory"
|
245
|
+
}, validation, propsFromParent, {
|
246
|
+
onDateSelect: date => {
|
247
|
+
var _propsFromParent$onDa;
|
248
|
+
setDate(date === null || date === void 0 ? void 0 : date.calendarDateString);
|
249
|
+
setValidation(date === null || date === void 0 ? void 0 : date.validation);
|
250
|
+
(_propsFromParent$onDa = propsFromParent.onDateSelect) === null || _propsFromParent$onDa === void 0 ? void 0 : _propsFromParent$onDa.call(propsFromParent, date);
|
251
|
+
}
|
252
|
+
}))), /*#__PURE__*/React.createElement(Button, {
|
253
|
+
type: "submit",
|
254
|
+
disabled: invalid,
|
255
|
+
onClick: handleSubmit
|
256
|
+
}, "Submit"));
|
257
|
+
});
|
258
|
+
};
|
@@ -34,6 +34,7 @@ export const CalendarInput = function () {
|
|
34
34
|
format,
|
35
35
|
strictValidation,
|
36
36
|
inputWidth,
|
37
|
+
dataTest = 'dhis2-uiwidgets-calendar-inputfield',
|
37
38
|
...rest
|
38
39
|
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
39
40
|
const ref = useRef();
|
@@ -47,26 +48,43 @@ export const CalendarInput = function () {
|
|
47
48
|
numberingSystem,
|
48
49
|
weekDayFormat
|
49
50
|
}), [calendar, locale, numberingSystem, weekDayFormat]);
|
51
|
+
const onChooseDate = (date, validationOptions) => {
|
52
|
+
// Handling clearing (with clicking the Clear button, or deleting input)
|
53
|
+
if (clearable && (date === null || date === '')) {
|
54
|
+
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
55
|
+
calendarDateString: null,
|
56
|
+
validation: {
|
57
|
+
valid: true
|
58
|
+
}
|
59
|
+
});
|
60
|
+
return;
|
61
|
+
}
|
62
|
+
|
63
|
+
// ToDo: This is now a workaround for handling choosing from the date picker
|
64
|
+
// where the blur event gets triggered causing a call with undefined first
|
65
|
+
if (date === undefined) {
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
const validation = validateDateString(date, validationOptions);
|
69
|
+
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
70
|
+
calendarDateString: date,
|
71
|
+
validation
|
72
|
+
});
|
73
|
+
};
|
74
|
+
const validationOptions = useMemo(() => ({
|
75
|
+
calendar,
|
76
|
+
format,
|
77
|
+
minDateString: minDate,
|
78
|
+
maxDateString: maxDate,
|
79
|
+
strictValidation
|
80
|
+
}), [calendar, format, maxDate, minDate, strictValidation]);
|
50
81
|
const pickerResults = useDatePicker({
|
51
82
|
onDateSelect: result => {
|
52
|
-
|
53
|
-
calendar,
|
54
|
-
format,
|
55
|
-
minDateString: minDate,
|
56
|
-
maxDateString: maxDate,
|
57
|
-
strictValidation
|
58
|
-
});
|
83
|
+
onChooseDate(result.calendarDateString, validationOptions);
|
59
84
|
setOpen(false);
|
60
|
-
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
61
|
-
calendarDateString: result.calendarDateString,
|
62
|
-
validation
|
63
|
-
});
|
64
85
|
},
|
65
86
|
date,
|
66
|
-
|
67
|
-
maxDate: maxDate,
|
68
|
-
strictValidation: strictValidation,
|
69
|
-
format: format,
|
87
|
+
...validationOptions,
|
70
88
|
options: useDatePickerOptions
|
71
89
|
});
|
72
90
|
const handleChange = e => {
|
@@ -74,17 +92,7 @@ export const CalendarInput = function () {
|
|
74
92
|
setPartialDate(e.value);
|
75
93
|
};
|
76
94
|
const handleBlur = (_, e) => {
|
77
|
-
|
78
|
-
calendar,
|
79
|
-
format,
|
80
|
-
minDateString: minDate,
|
81
|
-
maxDateString: maxDate,
|
82
|
-
strictValidation
|
83
|
-
});
|
84
|
-
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
85
|
-
calendarDateString: partialDate,
|
86
|
-
validation
|
87
|
-
});
|
95
|
+
onChooseDate(partialDate, validationOptions);
|
88
96
|
if (excludeRef.current && !excludeRef.current.contains(e.relatedTarget)) {
|
89
97
|
setOpen(false);
|
90
98
|
}
|
@@ -118,6 +126,7 @@ export const CalendarInput = function () {
|
|
118
126
|
}, /*#__PURE__*/React.createElement(InputField, _extends({
|
119
127
|
label: i18n.t('Pick a date')
|
120
128
|
}, rest, {
|
129
|
+
dataTest: dataTest,
|
121
130
|
type: "text",
|
122
131
|
onFocus: onFocus,
|
123
132
|
value: partialDate,
|
@@ -134,7 +143,7 @@ export const CalendarInput = function () {
|
|
134
143
|
secondary: true,
|
135
144
|
small: true,
|
136
145
|
onClick: () => {
|
137
|
-
|
146
|
+
onChooseDate(null);
|
138
147
|
},
|
139
148
|
type: "button"
|
140
149
|
}, i18n.t('Clear')))), open && /*#__PURE__*/React.createElement(Layer, {
|
@@ -9,13 +9,13 @@ const {
|
|
9
9
|
} = constants;
|
10
10
|
export const CalendarStoryWrapper = props => {
|
11
11
|
const {
|
12
|
-
calendar,
|
12
|
+
calendar = 'gregory',
|
13
13
|
locale,
|
14
14
|
timeZone,
|
15
15
|
dir,
|
16
|
-
component: Component,
|
16
|
+
component: Component = Calendar,
|
17
17
|
date,
|
18
|
-
weekDayFormat
|
18
|
+
weekDayFormat = 'narrow'
|
19
19
|
} = props;
|
20
20
|
const [selectedCalendar, setSelectedCalendar] = useState(calendar);
|
21
21
|
const [selectedNumberingSystem, setSelectedNumberingSystem] = useState();
|
@@ -151,11 +151,6 @@ export const CalendarStoryWrapper = props => {
|
|
151
151
|
"data-test": "storybook-calendar-result"
|
152
152
|
}, selectedDate.calendarDateString)), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", null, "callback:"), JSON.stringify(selectedDate, null, 2))))));
|
153
153
|
};
|
154
|
-
CalendarStoryWrapper.defaultProps = {
|
155
|
-
calendar: 'gregorian',
|
156
|
-
component: Calendar,
|
157
|
-
weekDayFormat: 'narrow'
|
158
|
-
};
|
159
154
|
CalendarStoryWrapper.propTypes = {
|
160
155
|
calendar: PropTypes.string.isRequired,
|
161
156
|
component: PropTypes.elementType.isRequired,
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@dhis2-ui/calendar",
|
3
|
-
"version": "10.0.0-alpha.
|
3
|
+
"version": "10.0.0-alpha.7",
|
4
4
|
"description": "UI Calendar",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
@@ -33,15 +33,15 @@
|
|
33
33
|
"styled-jsx": "^4"
|
34
34
|
},
|
35
35
|
"dependencies": {
|
36
|
-
"@dhis2-ui/button": "10.0.0-alpha.
|
37
|
-
"@dhis2-ui/card": "10.0.0-alpha.
|
38
|
-
"@dhis2-ui/input": "10.0.0-alpha.
|
39
|
-
"@dhis2-ui/layer": "10.0.0-alpha.
|
40
|
-
"@dhis2-ui/popper": "10.0.0-alpha.
|
36
|
+
"@dhis2-ui/button": "10.0.0-alpha.7",
|
37
|
+
"@dhis2-ui/card": "10.0.0-alpha.7",
|
38
|
+
"@dhis2-ui/input": "10.0.0-alpha.7",
|
39
|
+
"@dhis2-ui/layer": "10.0.0-alpha.7",
|
40
|
+
"@dhis2-ui/popper": "10.0.0-alpha.7",
|
41
41
|
"@dhis2/multi-calendar-dates": "2.0.0-alpha.5",
|
42
42
|
"@dhis2/prop-types": "^3.1.2",
|
43
|
-
"@dhis2/ui-constants": "10.0.0-alpha.
|
44
|
-
"@dhis2/ui-icons": "10.0.0-alpha.
|
43
|
+
"@dhis2/ui-constants": "10.0.0-alpha.7",
|
44
|
+
"@dhis2/ui-icons": "10.0.0-alpha.7",
|
45
45
|
"classnames": "^2.3.1",
|
46
46
|
"prop-types": "^15.7.2"
|
47
47
|
},
|