bright-components 10.1.1 → 10.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+
6
+ var _react = _interopRequireWildcard(require("react"));
7
+
8
+ var _dateFns = require("date-fns");
9
+
10
+ var _styledComponents = _interopRequireDefault(require("styled-components"));
11
+
12
+ var _Input = _interopRequireDefault(require("../../Input"));
13
+
14
+ var _ErrorMessage = _interopRequireDefault(require("../../ErrorMessage"));
15
+
16
+ var _propTypes = require("prop-types");
17
+
18
+ var _colors = _interopRequireDefault(require("../../../constants/colors"));
19
+
20
+ var _spacing = _interopRequireDefault(require("../../../constants/spacing"));
21
+
22
+ var _Calendar = _interopRequireDefault(require("../../Icons/Calendar"));
23
+
24
+ var _reactGa = require("react-ga");
25
+
26
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
27
+
28
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
29
+
30
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
31
+
32
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
33
+
34
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
35
+
36
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
37
+
38
+ const HiddenBox = (0, _styledComponents.default)(_Input.default.withComponent('div'))`
39
+ display: flex;
40
+ align-items: center;
41
+ ${props => props.error && `
42
+ margin-bottom: 0px !important;
43
+ border-bottom-left-radius: 0px;
44
+ border-bottom-right-radius: 0px;
45
+ border-color: ${_colors.default.borderError} !important;
46
+ `};
47
+ input {
48
+ height: 2.5rem !important;
49
+ min-height: 2.5rem !important;
50
+ border: 0 !important;
51
+ margin: 0 !important;
52
+ padding: 0 !important;
53
+ box-shadow: none !important;
54
+ }
55
+ `;
56
+ HiddenBox.displayName = 'HiddenBox';
57
+ const OpenCalendarIcon = (0, _styledComponents.default)(_Calendar.default)`
58
+ cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
59
+ margin-left: ${_spacing.default.s1};
60
+ color: ${_colors.default.primary};
61
+ `;
62
+ OpenCalendarIcon.displayName = 'OpenCalendarIcon';
63
+ const formats = {
64
+ CA: {
65
+ placeholderDate: 'YY/MM/DD',
66
+ dateFormat: 'yy/MM/dd'
67
+ },
68
+ default: {
69
+ placeholderDate: 'DD/MM/YY',
70
+ dateFormat: 'dd/MM/yy'
71
+ }
72
+ };
73
+
74
+ const makeInnerDate = (date, dateFormat) => {
75
+ if (date.from) {
76
+ return (0, _dateFns.format)(date.from, dateFormat);
77
+ }
78
+
79
+ return '';
80
+ };
81
+
82
+ const NewDisplay = _ref => {
83
+ let {
84
+ placeholder,
85
+ datesToDisplay,
86
+ range,
87
+ allowClear,
88
+ yearRange,
89
+ onSelectedDate,
90
+ locale,
91
+ clearValue,
92
+ disabled,
93
+ error,
94
+ toggleDayPicker,
95
+ GA: {
96
+ category,
97
+ actionId
98
+ },
99
+ recordValue,
100
+ dayPickerOpen
101
+ } = _ref;
102
+ const {
103
+ placeholderDate,
104
+ dateFormat
105
+ } = formats[locale] || formats.default;
106
+ const [hasFocus, setHasFocus] = (0, _react.useState)(false);
107
+ const [hasError, setHasError] = (0, _react.useState)(false);
108
+ const [innerDate, setInnerDate] = (0, _react.useState)(makeInnerDate(range, dateFormat));
109
+ return _react.default.createElement(_react.default.Fragment, null, _react.default.createElement(HiddenBox, {
110
+ error: error,
111
+ "data-testid": "input-selector"
112
+ }, _react.default.createElement(_Input.default, {
113
+ "data-testid": "new input",
114
+ placeholder: hasFocus ? placeholderDate : placeholder,
115
+ value: hasFocus ? innerDate : datesToDisplay,
116
+ fullWidth: true,
117
+ disabled: disabled,
118
+ onFocus: () => {
119
+ setInnerDate(makeInnerDate(range, dateFormat));
120
+ setHasFocus(true);
121
+ setHasError(false);
122
+ },
123
+ onBlur: _ref2 => {
124
+ let {
125
+ target: {
126
+ value
127
+ }
128
+ } = _ref2;
129
+ setHasFocus(false);
130
+ if (dayPickerOpen) toggleDayPicker();
131
+
132
+ if (!value && (allowClear || !range.from)) {
133
+ return range.from ? clearValue() : null;
134
+ }
135
+
136
+ const newDate = {
137
+ from: (0, _dateFns.parse)(value, dateFormat, new Date()),
138
+ to: (0, _dateFns.parse)(value, dateFormat, new Date())
139
+ };
140
+ const fromYear = (0, _dateFns.getYear)(newDate.from);
141
+ const toYear = (0, _dateFns.getYear)(newDate.to);
142
+
143
+ if (!(0, _dateFns.isValid)(newDate.from) || !(0, _dateFns.isValid)(newDate.to) || fromYear < yearRange.min || toYear > yearRange.max) {
144
+ setHasError(!((0, _dateFns.isValid)(newDate.from) && (0, _dateFns.isValid)(newDate.to)) ? 'invalid' : 'range');
145
+ return range.from ? onSelectedDate(allowClear ? '' : range) : null;
146
+ }
147
+
148
+ if (category) {
149
+ const label = `${(0, _dateFns.format)(newDate.from, 'EEE dd MMM')} ${(0, _dateFns.format)(newDate.from, 'yyyy')}`;
150
+ (0, _reactGa.event)(_objectSpread({
151
+ category,
152
+ action: `${actionId} - Set via Typing`
153
+ }, recordValue ? {
154
+ label
155
+ } : {}));
156
+ }
157
+
158
+ return onSelectedDate(newDate);
159
+ },
160
+ onChange: _ref3 => {
161
+ let {
162
+ target: {
163
+ value
164
+ }
165
+ } = _ref3;
166
+ setInnerDate(value.replace(/.*\/$/g, text => value.length < innerDate.length ? value.slice(0, -1) : text).replace(/(\d\d)(\d)/g, '$1/$2').replace(/^(\d\/)$/g, '0$1').replace(/\d\d$/g, text => value.length > innerDate.length && value.length < 7 ? `${text}/` : text).replace(/^(\d\d\/)(\d\/)$/g, '$10$2').replace(/[^\d/]/g, '').replace(/^(.{0,8})(.*)$/g, '$1').replace(/\/\/$/g, '/'));
167
+ }
168
+ }), _react.default.createElement(OpenCalendarIcon, {
169
+ "data-testid": "calendarIcon",
170
+ size: 22,
171
+ disabled: disabled,
172
+ onClick: () => {
173
+ setHasError(false);
174
+ toggleDayPicker();
175
+ }
176
+ })), hasError && _react.default.createElement(_ErrorMessage.default, {
177
+ showIcon: false
178
+ }, hasError === 'invalid' ? 'Date is invalid' : `Year is outside of range. Must be between ${yearRange.min} - ${yearRange.max}`));
179
+ };
180
+
181
+ NewDisplay.propTypes = {
182
+ placeholder: _propTypes.string.isRequired,
183
+ datesToDisplay: _propTypes.string.isRequired,
184
+ range: (0, _propTypes.shape)({
185
+ from: (0, _propTypes.instanceOf)(Date),
186
+ to: (0, _propTypes.instanceOf)(Date)
187
+ }).isRequired,
188
+ allowClear: _propTypes.bool.isRequired,
189
+ yearRange: (0, _propTypes.shape)({
190
+ min: _propTypes.number,
191
+ max: _propTypes.number
192
+ }).isRequired,
193
+ onSelectedDate: _propTypes.func.isRequired,
194
+ locale: _propTypes.string.isRequired,
195
+ clearValue: _propTypes.func.isRequired,
196
+ disabled: _propTypes.bool.isRequired,
197
+ error: _propTypes.bool.isRequired,
198
+ toggleDayPicker: _propTypes.func.isRequired,
199
+ GA: (0, _propTypes.shape)({
200
+ category: _propTypes.string,
201
+ actionId: _propTypes.string
202
+ }).isRequired,
203
+ recordValue: _propTypes.bool.isRequired,
204
+ dayPickerOpen: _propTypes.bool.isRequired
205
+ };
206
+ var _default = NewDisplay;
207
+ exports.default = _default;
@@ -33,6 +33,8 @@ var _breakpoints = _interopRequireDefault(require("../../constants/breakpoints")
33
33
 
34
34
  var _Calendar = _interopRequireDefault(require("../Icons/Calendar"));
35
35
 
36
+ var _NewDayPicker = _interopRequireDefault(require("./NewDayPicker"));
37
+
36
38
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
37
39
 
38
40
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
@@ -271,15 +273,17 @@ class DayPicker extends _react.default.Component {
271
273
  closeButton,
272
274
  panelAbsoluteOnDesktop,
273
275
  onSelectedDate,
274
- isDateSelect
276
+ isDateSelect,
277
+ typeable
275
278
  } = _this$props,
276
- rest = _objectWithoutProperties(_this$props, ["allowClear", "placeholder", "dateRange", "yearRange", "yearsToDisplay", "style", "error", "disabled", "shortcuts", "disabledDays", "GA", "recordValue", "legacyInputStyle", "closeButton", "panelAbsoluteOnDesktop", "onSelectedDate", "isDateSelect"]);
279
+ rest = _objectWithoutProperties(_this$props, ["allowClear", "placeholder", "dateRange", "yearRange", "yearsToDisplay", "style", "error", "disabled", "shortcuts", "disabledDays", "GA", "recordValue", "legacyInputStyle", "closeButton", "panelAbsoluteOnDesktop", "onSelectedDate", "isDateSelect", "typeable"]);
277
280
 
278
281
  const {
279
282
  dayPickerOpen
280
283
  } = this.state;
281
284
  const {
282
- range
285
+ range,
286
+ locale
283
287
  } = this.props;
284
288
  const fromDate = range.from ? (0, _dateFns.format)(range.from, this.getDisplayFormat()) : '';
285
289
  const toDate = range.to ? (0, _dateFns.format)(range.to, friendlyShortWithYear) : '';
@@ -290,7 +294,7 @@ class DayPicker extends _react.default.Component {
290
294
  } = style,
291
295
  otherStyles = _objectWithoutProperties(style, ["width"]);
292
296
 
293
- return _react.default.createElement(DayPickerInputContainer, rest, _react.default.createElement(InputBox, {
297
+ return _react.default.createElement(DayPickerInputContainer, rest, !typeable || dateRange ? _react.default.createElement(InputBox, {
294
298
  onClick: this.toggleDayPicker,
295
299
  error: error,
296
300
  disabled: disabled,
@@ -299,7 +303,22 @@ class DayPicker extends _react.default.Component {
299
303
  }, datesToDisplay ? _react.default.createElement("span", null, datesToDisplay) : _react.default.createElement(Placeholder, null, placeholder), _react.default.createElement(OpenCalendarIcon, {
300
304
  size: 22,
301
305
  disabled: disabled
302
- })), dayPickerOpen && _react.default.createElement(DayRangeContainer, {
306
+ })) : _react.default.createElement(_NewDayPicker.default, {
307
+ placeholder: placeholder,
308
+ datesToDisplay: datesToDisplay,
309
+ range: range,
310
+ allowClear: allowClear,
311
+ yearRange: yearRange,
312
+ onSelectedDate: onSelectedDate,
313
+ locale: locale,
314
+ clearValue: this.clearValue,
315
+ disabled: disabled,
316
+ error: error,
317
+ toggleDayPicker: this.toggleDayPicker,
318
+ GA: GA,
319
+ recordValue: recordValue,
320
+ dayPickerOpen: dayPickerOpen
321
+ }), dayPickerOpen && _react.default.createElement(DayRangeContainer, {
303
322
  panelAbsoluteOnDesktop: panelAbsoluteOnDesktop,
304
323
  style: otherStyles,
305
324
  width: width,
@@ -360,7 +379,9 @@ DayPicker.propTypes = {
360
379
  legacyInputStyle: _propTypes.bool,
361
380
  closeButton: _propTypes.bool,
362
381
  panelAbsoluteOnDesktop: _propTypes.bool,
363
- isDateSelect: _propTypes.bool
382
+ isDateSelect: _propTypes.bool,
383
+ typeable: _propTypes.bool,
384
+ locale: _propTypes.string
364
385
  };
365
386
  DayPicker.defaultProps = {
366
387
  recordValue: false,
@@ -385,7 +406,9 @@ DayPicker.defaultProps = {
385
406
  legacyInputStyle: false,
386
407
  closeButton: false,
387
408
  panelAbsoluteOnDesktop: true,
388
- isDateSelect: false
409
+ isDateSelect: false,
410
+ typeable: false,
411
+ locale: 'GB'
389
412
  };
390
413
 
391
414
  var _default = (0, _reactOnclickoutside.default)(DayPicker);
@@ -207,7 +207,7 @@ class DayPickerPanel extends _react.default.Component {
207
207
  to
208
208
  }
209
209
  } = this.state;
210
- const actionEvent = selectedDay ? 'Set' : 'Apply';
210
+ const actionEvent = selectedDay ? 'Set via Date Picker' : 'Apply';
211
211
 
212
212
  if (category) {
213
213
  const label = dateRange ? `${(0, _dateFns.format)(from, friendlyShort)} - ${(0, _dateFns.format)(to || from, friendlyShort)} ${(0, _dateFns.format)(to || from, 'yyyy')}` : `${(0, _dateFns.format)(from, friendlyShort)} ${(0, _dateFns.format)(from, 'yyyy')}`;
@@ -133,6 +133,7 @@ const TimePicker = _ref => {
133
133
  const valueAsString = (0, _durationToTimeString.default)(value);
134
134
  return _react.default.createElement(_react.default.Fragment, null, _react.default.createElement(MobileTimePicker, {
135
135
  type: "time",
136
+ "data-testid": "mobileTimePicker",
136
137
  onChange: e => {
137
138
  const timeString = e.target.value;
138
139
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  var _enzyme = require("enzyme");
4
4
 
5
+ var _react = require("@testing-library/react");
6
+
5
7
  var _enzymeAdapterReact = _interopRequireDefault(require("enzyme-adapter-react-16"));
6
8
 
7
9
  var _reactGa = _interopRequireDefault(require("react-ga"));
@@ -53,4 +55,7 @@ afterEach(() => {
53
55
  });
54
56
  (0, _enzyme.configure)({
55
57
  adapter: new _enzymeAdapterReact.default()
58
+ });
59
+ (0, _react.configure)({
60
+ defaultHidden: true
56
61
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bright-components",
3
- "version": "10.1.1",
3
+ "version": "10.2.0",
4
4
  "private": false,
5
5
  "main": "./dist",
6
6
  "repository": {
@@ -73,10 +73,11 @@
73
73
  "@babel/preset-env": "^7.16.5",
74
74
  "@babel/preset-react": "^7.7.4",
75
75
  "@svgr/webpack": "4.3.2",
76
+ "@testing-library/dom": "^8.11.2",
76
77
  "@testing-library/jest-dom": "^4.1.0",
77
78
  "@testing-library/react": "^9.1.4",
78
79
  "@testing-library/react-hooks": "^2.0.1",
79
- "@testing-library/user-event": "^7.0.1",
80
+ "@testing-library/user-event": "^12.8.3",
80
81
  "babel-eslint": "10.0.2",
81
82
  "babel-jest": "^24.9.0",
82
83
  "babel-loader": "8.0.6",
@@ -0,0 +1,208 @@
1
+ import React, { useState } from 'react';
2
+ import { format, isValid, parse, getYear } from 'date-fns';
3
+ import styled from 'styled-components';
4
+ import Input from 'components/Input';
5
+ import ErrorMessage from 'components/ErrorMessage';
6
+ import { string, func, bool, shape, instanceOf, number } from 'prop-types';
7
+ import colors from 'constants/colors';
8
+ import spacing from 'constants/spacing';
9
+ import CalendarIcon from 'components/Icons/Calendar/';
10
+ import { event } from 'react-ga';
11
+
12
+ const HiddenBox = styled(Input.withComponent('div'))`
13
+ display: flex;
14
+ align-items: center;
15
+ ${props =>
16
+ props.error &&
17
+ `
18
+ margin-bottom: 0px !important;
19
+ border-bottom-left-radius: 0px;
20
+ border-bottom-right-radius: 0px;
21
+ border-color: ${colors.borderError} !important;
22
+ `};
23
+ input {
24
+ height: 2.5rem !important;
25
+ min-height: 2.5rem !important;
26
+ border: 0 !important;
27
+ margin: 0 !important;
28
+ padding: 0 !important;
29
+ box-shadow: none !important;
30
+ }
31
+ `;
32
+ HiddenBox.displayName = 'HiddenBox';
33
+
34
+ const OpenCalendarIcon = styled(CalendarIcon)`
35
+ cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
36
+ margin-left: ${spacing.s1};
37
+ color: ${colors.primary};
38
+ `;
39
+ OpenCalendarIcon.displayName = 'OpenCalendarIcon';
40
+
41
+ const formats = {
42
+ CA: { placeholderDate: 'YY/MM/DD', dateFormat: 'yy/MM/dd' },
43
+ default: { placeholderDate: 'DD/MM/YY', dateFormat: 'dd/MM/yy' }
44
+ };
45
+
46
+ const makeInnerDate = (date, dateFormat) => {
47
+ if (date.from) {
48
+ return format(date.from, dateFormat);
49
+ }
50
+ return '';
51
+ };
52
+
53
+ const NewDisplay = ({
54
+ placeholder,
55
+ datesToDisplay,
56
+ range,
57
+ allowClear,
58
+ yearRange,
59
+ onSelectedDate,
60
+ locale,
61
+ clearValue,
62
+ disabled,
63
+ error,
64
+ toggleDayPicker,
65
+ GA: { category, actionId },
66
+ recordValue,
67
+ dayPickerOpen
68
+ }) => {
69
+ const { placeholderDate, dateFormat } = formats[locale] || formats.default;
70
+
71
+ const [hasFocus, setHasFocus] = useState(false);
72
+ const [hasError, setHasError] = useState(false);
73
+ const [innerDate, setInnerDate] = useState(
74
+ makeInnerDate(range, dateFormat)
75
+ );
76
+
77
+ return (
78
+ <>
79
+ <HiddenBox error={error} data-testid="input-selector">
80
+ <Input
81
+ data-testid="new input"
82
+ placeholder={hasFocus ? placeholderDate : placeholder}
83
+ value={hasFocus ? innerDate : datesToDisplay}
84
+ fullWidth
85
+ disabled={disabled}
86
+ onFocus={() => {
87
+ setInnerDate(makeInnerDate(range, dateFormat));
88
+ setHasFocus(true);
89
+ setHasError(false);
90
+ }}
91
+ onBlur={({ target: { value } }) => {
92
+ setHasFocus(false);
93
+ if (dayPickerOpen) toggleDayPicker();
94
+ if (!value && (allowClear || !range.from)) {
95
+ return range.from ? clearValue() : null;
96
+ }
97
+
98
+ const newDate = {
99
+ from: parse(value, dateFormat, new Date()),
100
+ to: parse(value, dateFormat, new Date())
101
+ };
102
+
103
+ const fromYear = getYear(newDate.from);
104
+ const toYear = getYear(newDate.to);
105
+
106
+ if (
107
+ !isValid(newDate.from) ||
108
+ !isValid(newDate.to) ||
109
+ fromYear < yearRange.min ||
110
+ toYear > yearRange.max
111
+ ) {
112
+ setHasError(
113
+ !(isValid(newDate.from) && isValid(newDate.to))
114
+ ? 'invalid'
115
+ : 'range'
116
+ );
117
+
118
+ return range.from
119
+ ? onSelectedDate(allowClear ? '' : range)
120
+ : null;
121
+ }
122
+
123
+ if (category) {
124
+ const label = `${format(
125
+ newDate.from,
126
+ 'EEE dd MMM'
127
+ )} ${format(newDate.from, 'yyyy')}`;
128
+
129
+ event({
130
+ category,
131
+ action: `${actionId} - Set via Typing`,
132
+ ...(recordValue ? { label } : {})
133
+ });
134
+ }
135
+
136
+ return onSelectedDate(newDate);
137
+ }}
138
+ onChange={({ target: { value } }) => {
139
+ setInnerDate(
140
+ value
141
+ .replace(/.*\/$/g, text =>
142
+ value.length < innerDate.length
143
+ ? value.slice(0, -1)
144
+ : text
145
+ )
146
+ .replace(/(\d\d)(\d)/g, '$1/$2')
147
+ .replace(/^(\d\/)$/g, '0$1')
148
+ .replace(/\d\d$/g, text =>
149
+ value.length > innerDate.length &&
150
+ value.length < 7
151
+ ? `${text}/`
152
+ : text
153
+ )
154
+ .replace(/^(\d\d\/)(\d\/)$/g, '$10$2')
155
+ .replace(/[^\d/]/g, '')
156
+ .replace(/^(.{0,8})(.*)$/g, '$1')
157
+ .replace(/\/\/$/g, '/')
158
+ );
159
+ }}
160
+ />
161
+ <OpenCalendarIcon
162
+ data-testid="calendarIcon"
163
+ size={22}
164
+ disabled={disabled}
165
+ onClick={() => {
166
+ setHasError(false);
167
+ toggleDayPicker();
168
+ }}
169
+ />
170
+ </HiddenBox>
171
+ {hasError && (
172
+ <ErrorMessage showIcon={false}>
173
+ {hasError === 'invalid'
174
+ ? 'Date is invalid'
175
+ : `Year is outside of range. Must be between ${yearRange.min} - ${yearRange.max}`}
176
+ </ErrorMessage>
177
+ )}
178
+ </>
179
+ );
180
+ };
181
+
182
+ NewDisplay.propTypes = {
183
+ placeholder: string.isRequired,
184
+ datesToDisplay: string.isRequired,
185
+ range: shape({
186
+ from: instanceOf(Date),
187
+ to: instanceOf(Date)
188
+ }).isRequired,
189
+ allowClear: bool.isRequired,
190
+ yearRange: shape({
191
+ min: number,
192
+ max: number
193
+ }).isRequired,
194
+ onSelectedDate: func.isRequired,
195
+ locale: string.isRequired,
196
+ clearValue: func.isRequired,
197
+ disabled: bool.isRequired,
198
+ error: bool.isRequired,
199
+ toggleDayPicker: func.isRequired,
200
+ GA: shape({
201
+ category: string,
202
+ actionId: string
203
+ }).isRequired,
204
+ recordValue: bool.isRequired,
205
+ dayPickerOpen: bool.isRequired
206
+ };
207
+
208
+ export default NewDisplay;
@@ -231,3 +231,20 @@ initialState={dates: {from: new Date(2018, 3, 1), to: undefined}};
231
231
  isDateSelect={true}
232
232
  />
233
233
  ```
234
+ Day Picker - typeable
235
+ ```js
236
+ initialState={dates: {from: new Date(2018, 3, 1), to: undefined}};
237
+ <DayPicker
238
+ range={state.dates}
239
+ yearRange={{ min: 2000, max: 2030}}
240
+ style={{
241
+ width: '70%'
242
+ }}
243
+ GA={{
244
+ category: 'Test',
245
+ actionId: 'Test'
246
+ }}
247
+ onSelectedDate={dates=>setState({dates})}
248
+ typeable
249
+ />
250
+ ```
@@ -26,6 +26,8 @@ import breakpoints from 'constants/breakpoints';
26
26
 
27
27
  import CalendarIcon from 'components/Icons/Calendar/';
28
28
 
29
+ import NewDayPicker from './NewDayPicker';
30
+
29
31
  const friendlyShort = 'EEE dd MMM';
30
32
  const friendlyShortWithYear = 'EEE dd MMM yyyy';
31
33
 
@@ -232,11 +234,12 @@ class DayPicker extends React.Component {
232
234
  panelAbsoluteOnDesktop,
233
235
  onSelectedDate,
234
236
  isDateSelect,
237
+ typeable,
235
238
  ...rest
236
239
  } = this.props;
237
240
 
238
241
  const { dayPickerOpen } = this.state;
239
- const { range } = this.props;
242
+ const { range, locale } = this.props;
240
243
 
241
244
  const fromDate = range.from
242
245
  ? format(range.from, this.getDisplayFormat())
@@ -250,20 +253,39 @@ class DayPicker extends React.Component {
250
253
 
251
254
  return (
252
255
  <DayPickerInputContainer {...rest}>
253
- <InputBox
254
- onClick={this.toggleDayPicker}
255
- error={error}
256
- disabled={disabled}
257
- legacyInputStyle={legacyInputStyle}
258
- data-testid="input-selector"
259
- >
260
- {datesToDisplay ? (
261
- <span>{datesToDisplay}</span>
262
- ) : (
263
- <Placeholder>{placeholder}</Placeholder>
264
- )}
265
- <OpenCalendarIcon size={22} disabled={disabled} />
266
- </InputBox>
256
+ {!typeable || dateRange ? (
257
+ <InputBox
258
+ onClick={this.toggleDayPicker}
259
+ error={error}
260
+ disabled={disabled}
261
+ legacyInputStyle={legacyInputStyle}
262
+ data-testid="input-selector"
263
+ >
264
+ {datesToDisplay ? (
265
+ <span>{datesToDisplay}</span>
266
+ ) : (
267
+ <Placeholder>{placeholder}</Placeholder>
268
+ )}
269
+ <OpenCalendarIcon size={22} disabled={disabled} />
270
+ </InputBox>
271
+ ) : (
272
+ <NewDayPicker
273
+ placeholder={placeholder}
274
+ datesToDisplay={datesToDisplay}
275
+ range={range}
276
+ allowClear={allowClear}
277
+ yearRange={yearRange}
278
+ onSelectedDate={onSelectedDate}
279
+ locale={locale}
280
+ clearValue={this.clearValue}
281
+ disabled={disabled}
282
+ error={error}
283
+ toggleDayPicker={this.toggleDayPicker}
284
+ GA={GA}
285
+ recordValue={recordValue}
286
+ dayPickerOpen={dayPickerOpen}
287
+ />
288
+ )}
267
289
 
268
290
  {dayPickerOpen && (
269
291
  <DayRangeContainer
@@ -330,7 +352,9 @@ DayPicker.propTypes = {
330
352
  legacyInputStyle: bool,
331
353
  closeButton: bool,
332
354
  panelAbsoluteOnDesktop: bool,
333
- isDateSelect: bool
355
+ isDateSelect: bool,
356
+ typeable: bool,
357
+ locale: string
334
358
  };
335
359
 
336
360
  DayPicker.defaultProps = {
@@ -356,7 +380,9 @@ DayPicker.defaultProps = {
356
380
  legacyInputStyle: false,
357
381
  closeButton: false,
358
382
  panelAbsoluteOnDesktop: true,
359
- isDateSelect: false
383
+ isDateSelect: false,
384
+ typeable: false,
385
+ locale: 'GB'
360
386
  };
361
387
 
362
388
  export { DayPicker as Unwrapped };
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import { render, fireEvent } from '@testing-library/react';
2
+ import { render, fireEvent, wait } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
3
4
  import ReactGA from 'react-ga';
4
5
  import DayPicker from '.';
5
6
 
@@ -406,4 +407,299 @@ describe('<DayPicker />', () => {
406
407
  expect(ReactGA.event).toHaveBeenCalledTimes(1);
407
408
  });
408
409
  });
410
+
411
+ describe('Typing date', () => {
412
+ it('should set the correct date when typed in', async () => {
413
+ wrapper.rerender(
414
+ <DayPicker
415
+ {...props}
416
+ typeable
417
+ dateRange={false}
418
+ yearRange={{ min: 2000, max: 2100 }}
419
+ GA={{
420
+ category: undefined,
421
+ actionId: undefined
422
+ }}
423
+ />
424
+ );
425
+ const inputSelect = wrapper.getByTestId('new input');
426
+
427
+ fireEvent.focus(inputSelect);
428
+ userEvent.type(inputSelect, `010203`);
429
+ await wait(() => expect(inputSelect.value).toEqual('01/02/03'));
430
+ fireEvent.blur(inputSelect);
431
+
432
+ expect(onMockSelectedDate).toHaveBeenLastCalledWith({
433
+ from: new Date('2003-02-01T00:00:00.000Z'),
434
+ to: new Date('2003-02-01T00:00:00.000Z')
435
+ });
436
+ });
437
+
438
+ it('should set the correct date when typed in - CA', () => {
439
+ wrapper.rerender(
440
+ <DayPicker
441
+ {...props}
442
+ typeable
443
+ dateRange={false}
444
+ yearRange={{ min: 2000, max: 2100 }}
445
+ locale="CA"
446
+ />
447
+ );
448
+ const inputSelect = wrapper.getByTestId('new input');
449
+
450
+ fireEvent.focus(inputSelect);
451
+ userEvent.type(inputSelect, '010203');
452
+ fireEvent.blur(inputSelect);
453
+
454
+ expect(onMockSelectedDate).toHaveBeenLastCalledWith({
455
+ from: new Date('2001-02-03T00:00:00.000Z'),
456
+ to: new Date('2001-02-03T00:00:00.000Z')
457
+ });
458
+ });
459
+
460
+ it('should show the correct date when date pre set', () => {
461
+ wrapper.rerender(
462
+ <DayPicker
463
+ {...props}
464
+ typeable
465
+ dateRange={false}
466
+ yearRange={{ min: 2000, max: 2100 }}
467
+ range={{
468
+ from: new Date('2019-08-01'),
469
+ to: undefined
470
+ }}
471
+ />
472
+ );
473
+ const inputSelect = wrapper.getByTestId('new input');
474
+
475
+ fireEvent.focus(inputSelect);
476
+
477
+ expect(inputSelect.value).toEqual('01/08/19');
478
+ });
479
+
480
+ it('should show the correct date when date pre set - CA', () => {
481
+ wrapper.rerender(
482
+ <DayPicker
483
+ {...props}
484
+ typeable
485
+ dateRange={false}
486
+ yearRange={{ min: 2000, max: 2100 }}
487
+ range={{
488
+ from: new Date('2019-08-01'),
489
+ to: undefined
490
+ }}
491
+ locale="CA"
492
+ />
493
+ );
494
+ const inputSelect = wrapper.getByTestId('new input');
495
+
496
+ fireEvent.focus(inputSelect);
497
+
498
+ expect(inputSelect.value).toEqual('19/08/01');
499
+ });
500
+
501
+ it('should allow clearing date', () => {
502
+ wrapper.rerender(
503
+ <DayPicker
504
+ {...props}
505
+ typeable
506
+ dateRange={false}
507
+ yearRange={{ min: 2000, max: 2100 }}
508
+ range={{
509
+ from: new Date('2019-08-01'),
510
+ to: undefined
511
+ }}
512
+ />
513
+ );
514
+ const inputSelect = wrapper.getByTestId('new input');
515
+
516
+ fireEvent.focus(inputSelect);
517
+ userEvent.type(inputSelect, '{selectall}{backspace}');
518
+ fireEvent.blur(inputSelect);
519
+
520
+ expect(onMockSelectedDate).toHaveBeenLastCalledWith({
521
+ from: undefined,
522
+ to: undefined
523
+ });
524
+ });
525
+
526
+ it('shouldnt save invalid dates', () => {
527
+ wrapper.rerender(
528
+ <DayPicker
529
+ {...props}
530
+ typeable
531
+ dateRange={false}
532
+ yearRange={{ min: 2000, max: 2100 }}
533
+ range={{
534
+ from: new Date('2019-08-01'),
535
+ to: undefined
536
+ }}
537
+ />
538
+ );
539
+ const inputSelect = wrapper.getByTestId('new input');
540
+
541
+ fireEvent.focus(inputSelect);
542
+ fireEvent.blur(inputSelect);
543
+ fireEvent.focus(inputSelect);
544
+ userEvent.type(inputSelect, '{selectall}{backspace}0101');
545
+ fireEvent.blur(inputSelect);
546
+
547
+ expect(onMockSelectedDate).toHaveBeenLastCalledWith('');
548
+ expect(wrapper.getByText('Date is invalid')).toBeInTheDocument();
549
+ });
550
+
551
+ it('shouldnt save dates outside the range', () => {
552
+ wrapper.rerender(
553
+ <DayPicker
554
+ {...props}
555
+ typeable
556
+ dateRange={false}
557
+ yearRange={{ min: 2018, max: 2030 }}
558
+ range={{
559
+ from: new Date('2019-08-01'),
560
+ to: undefined
561
+ }}
562
+ allowClear={false}
563
+ />
564
+ );
565
+ const inputSelect = wrapper.getByTestId('new input');
566
+
567
+ fireEvent.focus(inputSelect);
568
+ userEvent.type(inputSelect, '{selectall}{backspace}010131');
569
+ fireEvent.blur(inputSelect);
570
+
571
+ expect(onMockSelectedDate).toHaveBeenLastCalledWith({
572
+ from: new Date('2019-08-01T00:00:00.000Z'),
573
+ to: undefined
574
+ });
575
+ expect(
576
+ wrapper.getByText(
577
+ 'Year is outside of range. Must be between 2018 - 2030'
578
+ )
579
+ ).toBeInTheDocument();
580
+ });
581
+
582
+ it('should revert invalid date to empty if no initial date', () => {
583
+ onMockSelectedDate.mockClear();
584
+ wrapper.rerender(
585
+ <DayPicker
586
+ {...props}
587
+ typeable
588
+ dateRange={false}
589
+ yearRange={{ min: 2000, max: 2100 }}
590
+ range={{
591
+ from: undefined,
592
+ to: undefined
593
+ }}
594
+ allowClear={false}
595
+ />
596
+ );
597
+ const inputSelect = wrapper.getByTestId('new input');
598
+
599
+ fireEvent.focus(inputSelect);
600
+ fireEvent.blur(inputSelect);
601
+ fireEvent.focus(inputSelect);
602
+ userEvent.type(inputSelect, '{selectall}{backspace}0101');
603
+ fireEvent.blur(inputSelect);
604
+
605
+ expect(onMockSelectedDate).not.toHaveBeenCalled();
606
+ });
607
+
608
+ it('backspace / automatically', () => {
609
+ wrapper.rerender(
610
+ <DayPicker
611
+ {...props}
612
+ typeable
613
+ dateRange={false}
614
+ yearRange={{ min: 2000, max: 2100 }}
615
+ range={{
616
+ from: new Date('2019-08-01'),
617
+ to: undefined
618
+ }}
619
+ />
620
+ );
621
+ const inputSelect = wrapper.getByTestId('new input');
622
+
623
+ fireEvent.focus(inputSelect);
624
+ userEvent.type(inputSelect, `{backspace}{backspace}/`);
625
+ expect(inputSelect.value).toEqual('01/08/');
626
+ });
627
+
628
+ it('should open calendar view with the icon', () => {
629
+ wrapper.rerender(
630
+ <DayPicker
631
+ {...props}
632
+ typeable
633
+ dateRange={false}
634
+ yearRange={{ min: 2000, max: 2100 }}
635
+ range={{
636
+ from: new Date('2019-08-01'),
637
+ to: undefined
638
+ }}
639
+ />
640
+ );
641
+
642
+ fireEvent.click(wrapper.getByTestId('calendarIcon'));
643
+ expect(wrapper.getByTestId('daypicker-panel')).toBeInTheDocument();
644
+
645
+ const inputSelect = wrapper.getByTestId('new input');
646
+ fireEvent.focus(inputSelect);
647
+ fireEvent.blur(inputSelect);
648
+
649
+ expect(
650
+ wrapper.queryByTestId('daypicker-panel')
651
+ ).not.toBeInTheDocument();
652
+ });
653
+
654
+ it('should apply the error styling if error', () => {
655
+ wrapper.rerender(
656
+ <DayPicker {...props} dateRange={false} typeable error />
657
+ );
658
+
659
+ const inputSelect = wrapper.getByTestId('input-selector');
660
+ expect(inputSelect).toHaveStyleRule(
661
+ 'border-color',
662
+ '#FF5000 !important'
663
+ );
664
+ });
665
+
666
+ it('should apply the error styling if disabled', () => {
667
+ wrapper.rerender(
668
+ <DayPicker {...props} typeable dateRange={false} disabled />
669
+ );
670
+
671
+ const inputSelect = wrapper.getByTestId('new input');
672
+ expect(inputSelect).toBeDisabled();
673
+ });
674
+
675
+ it('should call the correct GA tags when the date is typed', async () => {
676
+ wrapper.rerender(
677
+ <DayPicker
678
+ {...props}
679
+ typeable
680
+ dateRange={false}
681
+ range={{
682
+ from: undefined,
683
+ to: undefined
684
+ }}
685
+ recordValue
686
+ />
687
+ );
688
+ expect(ReactGA.event).toHaveBeenCalledTimes(0);
689
+
690
+ const inputSelect = wrapper.getByTestId('new input');
691
+ fireEvent.focus(inputSelect);
692
+ userEvent.type(inputSelect, `201222`);
693
+ await wait(() => expect(inputSelect.value).toEqual('20/12/22'));
694
+ fireEvent.blur(inputSelect);
695
+
696
+ await wait(() =>
697
+ expect(ReactGA.event).toHaveBeenCalledWith({
698
+ action: 'ActionID - Set via Typing',
699
+ category: 'Test',
700
+ label: 'Tue 20 Dec 2022'
701
+ })
702
+ );
703
+ });
704
+ });
409
705
  });
@@ -178,7 +178,7 @@ class DayPickerPanel extends React.Component {
178
178
  range: { from, to }
179
179
  } = this.state;
180
180
 
181
- const actionEvent = selectedDay ? 'Set' : 'Apply';
181
+ const actionEvent = selectedDay ? 'Set via Date Picker' : 'Apply';
182
182
 
183
183
  if (category) {
184
184
  const label = dateRange
@@ -73,7 +73,7 @@ describe('Month Year and Calendar view', () => {
73
73
 
74
74
  expect(event).toHaveBeenCalledWith({
75
75
  category: 'Cat',
76
- action: 'Action ID - Set',
76
+ action: 'Action ID - Set via Date Picker',
77
77
  label: 'Tue 03 Apr 2018'
78
78
  });
79
79
  });
@@ -20,7 +20,7 @@ describe('<DurationInput />', () => {
20
20
  });
21
21
 
22
22
  it('should select a new days value when the days input is changed', () => {
23
- const daysInput = wrapper.getByRole('textbox');
23
+ const daysInput = wrapper.getByRole('spinbutton');
24
24
 
25
25
  expect(onChange).not.toHaveBeenCalled();
26
26
 
@@ -36,7 +36,7 @@ describe('<DurationInput />', () => {
36
36
  const blurProps = { ...props, onBlur: jest.fn() };
37
37
  wrapper.rerender(<DurationInput {...blurProps} />);
38
38
 
39
- const daysInput = wrapper.getByRole('textbox');
39
+ const daysInput = wrapper.getByRole('spinbutton');
40
40
  fireEvent.blur(daysInput);
41
41
 
42
42
  expect(blurProps.onBlur).toHaveBeenCalled();
@@ -54,7 +54,9 @@ describe('<DurationInput />', () => {
54
54
  });
55
55
 
56
56
  it('should select a new hours value and a new minutes value when the inputs are changed', () => {
57
- const [hoursInput, minutesInput] = wrapper.getAllByRole('textbox');
57
+ const [hoursInput, minutesInput] = wrapper.getAllByRole(
58
+ 'spinbutton'
59
+ );
58
60
 
59
61
  expect(onChange).not.toHaveBeenCalled();
60
62
 
@@ -33,7 +33,7 @@ describe('EmployeePickerFilterBar', () => {
33
33
  });
34
34
 
35
35
  const getInput = () => element.getByPlaceholderText('Enter name');
36
- const getSelect = () => element.getByRole('listbox');
36
+ const getSelect = () => element.getByRole('combobox');
37
37
 
38
38
  it('should call updateType when the user selects a filter type', () => {
39
39
  const input = getInput();
@@ -15,7 +15,7 @@ describe('<ResponsiveTabs />', () => {
15
15
  </ResponsiveTabs>
16
16
  );
17
17
 
18
- expect(getByRole('listbox')).toBeInTheDocument();
18
+ expect(getByRole('combobox')).toBeInTheDocument();
19
19
  expect(getAllByRole('option')).toHaveLength(2);
20
20
  });
21
21
 
@@ -38,7 +38,7 @@ describe('<ResponsiveTabs />', () => {
38
38
  expect(getByTestId('tab1')).toBeVisible();
39
39
  expect(getByTestId('tab2')).not.toBeVisible();
40
40
 
41
- const dropdown = getByRole('listbox');
41
+ const dropdown = getByRole('combobox');
42
42
  fireEvent.change(dropdown, { target: { value: 'Two' } });
43
43
 
44
44
  expect(getByTestId('tab1')).not.toBeVisible();
@@ -90,7 +90,7 @@ describe('<ResponsiveTabs />', () => {
90
90
  </ResponsiveTabs>
91
91
  );
92
92
 
93
- fireEvent.change(getByRole('listbox'), { target: { value: 'One' } });
93
+ fireEvent.change(getByRole('combobox'), { target: { value: 'One' } });
94
94
  expect(event).not.toHaveBeenCalled();
95
95
 
96
96
  fireEvent.click(getByRole('tab'));
@@ -118,6 +118,7 @@ const TimePicker = ({
118
118
  <>
119
119
  <MobileTimePicker
120
120
  type="time"
121
+ data-testid="mobileTimePicker"
121
122
  onChange={e => {
122
123
  const timeString = e.target.value;
123
124
  if (timeString.length === 5) {
@@ -7,21 +7,22 @@ describe('<TimePicker />', () => {
7
7
  describe('given a null value', () => {
8
8
  it('should render without throwing exceptions', () => {
9
9
  const onChange = jest.fn();
10
- const { getAllByRole } = render(
10
+ const { getByRole, getByTestId } = render(
11
11
  <TimePicker value={null} onChange={onChange} />
12
12
  );
13
13
 
14
- expect(getAllByRole('textbox').length).toBe(2);
14
+ expect(getByRole('textbox')).toBeInTheDocument();
15
+ expect(getByTestId('mobileTimePicker')).toBeInTheDocument();
15
16
  });
16
17
 
17
18
  it('should allow a new value to be input', () => {
18
19
  const onChange = jest.fn();
19
- const { getAllByRole } = render(
20
+ const { getByRole } = render(
20
21
  <TimePicker value={null} onChange={onChange} />
21
22
  );
22
23
 
23
24
  // check a new value can be input
24
- const textBox = getAllByRole('textbox')[1];
25
+ const textBox = getByRole('textbox');
25
26
  expect(onChange).not.toHaveBeenCalled();
26
27
  fireEvent.change(textBox, { target: { value: '17:15' } });
27
28
  expect(onChange).toHaveBeenCalledWith({
@@ -44,14 +45,14 @@ describe('<TimePicker />', () => {
44
45
 
45
46
  describe('on initial render', () => {
46
47
  it('should render a native time input for mobile devices', () => {
47
- const { getAllByRole } = render(
48
+ const { getByTestId } = render(
48
49
  <TimePicker
49
50
  value={{ hours: 1, minutes: 30 }}
50
51
  onChange={() => {}}
51
52
  />
52
53
  );
53
54
 
54
- const textBox = getAllByRole('textbox')[0];
55
+ const textBox = getByTestId('mobileTimePicker');
55
56
  expect(textBox.type).toBe('time');
56
57
  expect(textBox.value).toBe('01:30');
57
58
  expect(textBox).toHaveStyleRule('display', 'inline-block');
@@ -62,14 +63,14 @@ describe('<TimePicker />', () => {
62
63
  });
63
64
 
64
65
  it('should render a standard input for desktop-class devices', () => {
65
- const { getAllByRole } = render(
66
+ const { getByRole } = render(
66
67
  <TimePicker
67
68
  value={{ hours: 1, minutes: 30 }}
68
69
  onChange={() => {}}
69
70
  />
70
71
  );
71
72
 
72
- const textBox = getAllByRole('textbox')[1];
73
+ const textBox = getByRole('textbox');
73
74
  expect(textBox.type).toBe('text');
74
75
  expect(textBox.value).toBe('01:30');
75
76
  });
@@ -77,7 +78,7 @@ describe('<TimePicker />', () => {
77
78
  it('should support onFocus and onBlur props and call down to them', () => {
78
79
  const onFocus = jest.fn();
79
80
  const onBlur = jest.fn();
80
- const { getAllByRole } = render(
81
+ const { getByRole } = render(
81
82
  <TimePicker
82
83
  value={{ hours: 1, minutes: 30 }}
83
84
  onFocus={onFocus}
@@ -86,7 +87,7 @@ describe('<TimePicker />', () => {
86
87
  />
87
88
  );
88
89
 
89
- const textBox = getAllByRole('textbox')[1];
90
+ const textBox = getByRole('textbox');
90
91
  expect(onFocus).not.toHaveBeenCalled();
91
92
  expect(onBlur).not.toHaveBeenCalled();
92
93
  fireEvent.focus(textBox);
@@ -152,14 +153,14 @@ describe('<TimePicker />', () => {
152
153
  describe('given a valid value is supplied', () => {
153
154
  it('should call the onChange handler when the mobile input is used', () => {
154
155
  const onChange = jest.fn();
155
- const { getAllByRole } = render(
156
+ const { getByTestId } = render(
156
157
  <TimePicker
157
158
  value={{ hours: 1, minutes: 30 }}
158
159
  onChange={onChange}
159
160
  />
160
161
  );
161
162
 
162
- const textBox = getAllByRole('textbox')[0];
163
+ const textBox = getByTestId('mobileTimePicker');
163
164
  expect(onChange).not.toHaveBeenCalled();
164
165
  fireEvent.focus(textBox);
165
166
  fireEvent.change(textBox, { target: { value: '17:15' } });
@@ -171,14 +172,14 @@ describe('<TimePicker />', () => {
171
172
 
172
173
  it('should call the onChange handler when the desktop-class input is used', () => {
173
174
  const onChange = jest.fn();
174
- const { getAllByRole } = render(
175
+ const { getByRole } = render(
175
176
  <TimePicker
176
177
  value={{ hours: 1, minutes: 30 }}
177
178
  onChange={onChange}
178
179
  />
179
180
  );
180
181
 
181
- const textBox = getAllByRole('textbox')[1];
182
+ const textBox = getByRole('textbox');
182
183
  expect(onChange).not.toHaveBeenCalled();
183
184
  fireEvent.focus(textBox);
184
185
  fireEvent.change(textBox, { target: { value: '17:15' } });
@@ -192,14 +193,14 @@ describe('<TimePicker />', () => {
192
193
  describe('given an incomplete value is supplied', () => {
193
194
  it('should not call the onChange handler when the mobile input is used', () => {
194
195
  const onChange = jest.fn();
195
- const { getAllByRole } = render(
196
+ const { getByTestId } = render(
196
197
  <TimePicker
197
198
  value={{ hours: 1, minutes: 30 }}
198
199
  onChange={onChange}
199
200
  />
200
201
  );
201
202
 
202
- const textBox = getAllByRole('textbox')[0];
203
+ const textBox = getByTestId('mobileTimePicker');
203
204
  expect(onChange).not.toHaveBeenCalled();
204
205
  fireEvent.change(textBox, { target: { value: '17:1' } });
205
206
  expect(onChange).not.toHaveBeenCalledWith();
@@ -207,14 +208,14 @@ describe('<TimePicker />', () => {
207
208
 
208
209
  it('should not call the onChange handler when the desktop-class input is used', () => {
209
210
  const onChange = jest.fn();
210
- const { getAllByRole } = render(
211
+ const { getByRole } = render(
211
212
  <TimePicker
212
213
  value={{ hours: 1, minutes: 30 }}
213
214
  onChange={onChange}
214
215
  />
215
216
  );
216
217
 
217
- const textBox = getAllByRole('textbox')[1];
218
+ const textBox = getByRole('textbox');
218
219
  expect(onChange).not.toHaveBeenCalled();
219
220
  fireEvent.change(textBox, { target: { value: '17:1' } });
220
221
  expect(onChange).not.toHaveBeenCalledWith();
@@ -224,14 +225,14 @@ describe('<TimePicker />', () => {
224
225
  describe('given the box is cleared', () => {
225
226
  it('should call the onChange handler with null', () => {
226
227
  const onChange = jest.fn();
227
- const { getAllByRole } = render(
228
+ const { getByRole } = render(
228
229
  <TimePicker
229
230
  value={{ hours: 1, minutes: 30 }}
230
231
  onChange={onChange}
231
232
  />
232
233
  );
233
234
 
234
- const textBox = getAllByRole('textbox')[1];
235
+ const textBox = getByRole('textbox');
235
236
  expect(onChange).not.toHaveBeenCalled();
236
237
  fireEvent.focus(textBox);
237
238
  fireEvent.change(textBox, { target: { value: '' } });
@@ -242,14 +243,14 @@ describe('<TimePicker />', () => {
242
243
  describe('when the field loses focus', () => {
243
244
  it('should parse reasonable partial values into valid values', () => {
244
245
  const onChange = jest.fn();
245
- const { getAllByRole } = render(
246
+ const { getByRole } = render(
246
247
  <TimePicker
247
248
  value={{ hours: 1, minutes: 30 }}
248
249
  onChange={onChange}
249
250
  />
250
251
  );
251
252
 
252
- const textBox = getAllByRole('textbox')[1];
253
+ const textBox = getByRole('textbox');
253
254
  fireEvent.focus(textBox);
254
255
  fireEvent.change(textBox, { target: { value: '01:3' } });
255
256
  expect(onChange).not.toHaveBeenCalled();
@@ -259,14 +260,14 @@ describe('<TimePicker />', () => {
259
260
 
260
261
  it('should not call onChange if parsed partial value is same as given value', () => {
261
262
  const onChange = jest.fn();
262
- const { getAllByRole } = render(
263
+ const { getByRole } = render(
263
264
  <TimePicker
264
265
  value={{ hours: 1, minutes: 3 }}
265
266
  onChange={onChange}
266
267
  />
267
268
  );
268
269
 
269
- const textBox = getAllByRole('textbox')[1];
270
+ const textBox = getByRole('textbox');
270
271
  fireEvent.focus(textBox);
271
272
  fireEvent.change(textBox, { target: { value: '01:3' } });
272
273
  expect(onChange).not.toHaveBeenCalled();
@@ -276,14 +277,14 @@ describe('<TimePicker />', () => {
276
277
 
277
278
  it('should not parse unreasonable values into valid values', () => {
278
279
  const onChange = jest.fn();
279
- const { getAllByRole } = render(
280
+ const { getByRole } = render(
280
281
  <TimePicker
281
282
  value={{ hours: 1, minutes: 30 }}
282
283
  onChange={onChange}
283
284
  />
284
285
  );
285
286
 
286
- const textBox = getAllByRole('textbox')[1];
287
+ const textBox = getByRole('textbox');
287
288
  fireEvent.focus(textBox);
288
289
  fireEvent.change(textBox, { target: { value: '01' } });
289
290
  expect(onChange).not.toHaveBeenCalled();
@@ -297,7 +298,7 @@ describe('<TimePicker />', () => {
297
298
  // this is a convoluted test to cover some code that works around an issue with
298
299
  // stores (mobx) that only re-render when actual values change
299
300
  const onChange = jest.fn();
300
- const { getAllByRole } = render(
301
+ const { getByRole } = render(
301
302
  <TimePicker
302
303
  value={{ hours: 1, minutes: 30 }}
303
304
  minuteIncrements={5}
@@ -305,7 +306,7 @@ describe('<TimePicker />', () => {
305
306
  />
306
307
  );
307
308
 
308
- const textBox = getAllByRole('textbox')[1];
309
+ const textBox = getByRole('textbox');
309
310
  fireEvent.focus(textBox);
310
311
  fireEvent.change(textBox, { target: { value: '01:32' } });
311
312
  expect(onChange).toHaveBeenCalledWith({
package/src/setupTests.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { configure } from 'enzyme';
2
+ import { configure as RTLConfig } from '@testing-library/react';
2
3
  import Adapter from 'enzyme-adapter-react-16';
3
4
  import ReactGA from 'react-ga';
4
5
  import 'jest-enzyme';
@@ -34,3 +35,4 @@ afterEach(() => {
34
35
  });
35
36
 
36
37
  configure({ adapter: new Adapter() });
38
+ RTLConfig({ defaultHidden: true });