bright-components 10.1.1 → 10.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 });