@dhis2-ui/calendar 10.0.0-alpha.5 → 10.0.0-alpha.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 _react2 = _interopRequireDefault(require("react"));
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 = new Date().toISOString().slice(0, -14);
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
- const validation = (0, _multiCalendarDates.validateDateString)(result.calendarDateString, {
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
- minDate: minDate,
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
- const validation = (0, _multiCalendarDates.validateDateString)(partialDate, {
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
- parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect(null);
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 React from 'react';
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 = new Date().toISOString().slice(0, -14);
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
- const validation = validateDateString(result.calendarDateString, {
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
- minDate: minDate,
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
- const validation = validateDateString(partialDate, {
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
- parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect(null);
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.5",
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.5",
37
- "@dhis2-ui/card": "10.0.0-alpha.5",
38
- "@dhis2-ui/input": "10.0.0-alpha.5",
39
- "@dhis2-ui/layer": "10.0.0-alpha.5",
40
- "@dhis2-ui/popper": "10.0.0-alpha.5",
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.5",
44
- "@dhis2/ui-icons": "10.0.0-alpha.5",
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
  },