@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.
@@ -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
  },