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.
- package/dist/components/DayPicker/NewDayPicker/index.js +207 -0
- package/dist/components/DayPicker/index.js +30 -7
- package/dist/components/DayPickerPanel/index.js +1 -1
- package/dist/components/Time/TimePicker/index.js +1 -0
- package/dist/setupTests.js +5 -0
- package/package.json +3 -2
- package/src/components/DayPicker/NewDayPicker/index.js +208 -0
- package/src/components/DayPicker/examples.md +17 -0
- package/src/components/DayPicker/index.js +43 -17
- package/src/components/DayPicker/test.js +297 -1
- package/src/components/DayPickerPanel/index.js +1 -1
- package/src/components/DayPickerPanel/test.js +1 -1
- package/src/components/DurationInput/test.js +5 -3
- package/src/components/EmployeePicker/FilterBar/test.js +1 -1
- package/src/components/ResponsiveTabs/test.js +3 -3
- package/src/components/Time/TimePicker/index.js +1 -0
- package/src/components/Time/TimePicker/test.js +29 -28
- package/src/setupTests.js +2 -0
@@ -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
|
-
}))
|
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
|
|
package/dist/setupTests.js
CHANGED
@@ -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.
|
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": "^
|
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
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
@@ -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('
|
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('
|
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(
|
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('
|
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('
|
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('
|
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('
|
93
|
+
fireEvent.change(getByRole('combobox'), { target: { value: 'One' } });
|
94
94
|
expect(event).not.toHaveBeenCalled();
|
95
95
|
|
96
96
|
fireEvent.click(getByRole('tab'));
|
@@ -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 {
|
10
|
+
const { getByRole, getByTestId } = render(
|
11
11
|
<TimePicker value={null} onChange={onChange} />
|
12
12
|
);
|
13
13
|
|
14
|
-
expect(
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 {
|
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 =
|
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 });
|