@shopgate/pwa-common 7.30.0-alpha.6 → 7.30.0-alpha.8
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.
- package/App.js +48 -6
- package/action-creators/app/index.js +75 -12
- package/action-creators/app/spec.js +96 -1
- package/action-creators/client/index.js +27 -5
- package/action-creators/client/spec.js +44 -1
- package/action-creators/error/index.js +15 -3
- package/action-creators/index.js +9 -1
- package/action-creators/menu/index.js +23 -4
- package/action-creators/menu/spec.js +37 -1
- package/action-creators/modal/index.js +15 -3
- package/action-creators/modal/spec.js +26 -1
- package/action-creators/page/index.js +24 -4
- package/action-creators/page/spec.js +38 -1
- package/action-creators/router/index.js +48 -7
- package/action-creators/url/index.js +24 -4
- package/action-creators/url/spec.js +45 -1
- package/action-creators/user/index.js +90 -13
- package/action-creators/user/spec.js +186 -2
- package/actions/app/handleDeepLink.js +11 -2
- package/actions/app/handleLink.js +62 -6
- package/actions/app/handlePushNotification.js +32 -4
- package/actions/app/handleUniversalLink.js +11 -2
- package/actions/app/registerLinkEvents.js +24 -3
- package/actions/client/fetchClientInformation.js +26 -2
- package/actions/menu/fetchMenu.js +23 -2
- package/actions/modal/closeModal.js +18 -2
- package/actions/modal/promiseMap.js +3 -1
- package/actions/modal/showModal.js +54 -8
- package/actions/page/fetchPageConfig.js +69 -2
- package/actions/page/getPageConfig.js +6 -2
- package/actions/page/index.js +1 -1
- package/actions/router/historyPop.js +12 -2
- package/actions/router/historyPopToRoute.js +27 -2
- package/actions/router/historyPush.js +12 -2
- package/actions/router/historyRedirect.js +21 -2
- package/actions/router/historyReplace.js +20 -3
- package/actions/router/historyReset.js +11 -2
- package/actions/router/historyResetTo.js +12 -2
- package/actions/router/index.js +17 -1
- package/actions/router/routeDidPop.js +11 -2
- package/actions/router/routeDidPush.js +13 -2
- package/actions/router/routeDidReplace.js +11 -2
- package/actions/router/routeDidReset.js +11 -2
- package/actions/router/routeDidUpdate.js +10 -2
- package/actions/router/routeWillPop.js +11 -2
- package/actions/router/routeWillPush.js +13 -2
- package/actions/router/routeWillReplace.js +11 -2
- package/actions/router/routeWillReset.js +11 -2
- package/actions/router/windowOpenOverride.js +10 -2
- package/actions/user/fetchRegisterUrl.js +36 -2
- package/actions/user/fetchUser.js +29 -3
- package/actions/user/getUser.js +6 -2
- package/actions/user/index.js +1 -1
- package/actions/user/login.js +76 -9
- package/actions/user/logout.js +30 -2
- package/collections/AuthRoutes.js +73 -14
- package/collections/Configuration.js +54 -7
- package/collections/EmbeddedMedia.js +84 -11
- package/collections/PersistedReducers.js +41 -6
- package/collections/Redirects.js +103 -17
- package/collections/index.js +5 -1
- package/collections/media-providers/MediaProvider.js +151 -26
- package/collections/media-providers/Vimeo.js +113 -19
- package/collections/media-providers/YouTube.js +74 -14
- package/collections/media-providers/index.js +3 -1
- package/collections/media-providers/style.js +52 -2
- package/components/Backdrop/index.js +95 -6
- package/components/Backdrop/spec.js +23 -1
- package/components/Backdrop/style.js +11 -2
- package/components/Button/index.js +47 -5
- package/components/Button/spec.js +36 -1
- package/components/Button/style.js +6 -1
- package/components/Checkbox/index.js +126 -32
- package/components/Checkbox/spec.js +94 -3
- package/components/Consume/helpers/buildParams.js +13 -2
- package/components/Consume/index.js +14 -2
- package/components/CountdownTimer/index.js +115 -17
- package/components/CountdownTimer/spec.js +126 -12
- package/components/Drawer/index.js +131 -16
- package/components/Drawer/spec.js +76 -1
- package/components/Drawer/style.js +37 -1
- package/components/Dropdown/index.js +65 -6
- package/components/Dropdown/style.js +4 -1
- package/components/Dropdown/transitions.js +34 -1
- package/components/Ellipsis/index.js +16 -2
- package/components/Ellipsis/spec.js +13 -1
- package/components/EmbeddedMedia/index.js +56 -6
- package/components/EmbeddedMedia/spec.js +52 -3
- package/components/ErrorBoundary/connector.js +9 -2
- package/components/ErrorBoundary/index.js +43 -7
- package/components/Grid/components/Item/index.js +40 -4
- package/components/Grid/components/Item/spec.js +23 -1
- package/components/Grid/components/Item/style.js +17 -3
- package/components/Grid/index.js +36 -4
- package/components/Grid/spec.js +23 -1
- package/components/Grid/style.js +11 -2
- package/components/HtmlSanitizer/connector.js +24 -3
- package/components/HtmlSanitizer/index.js +104 -12
- package/components/HtmlSanitizer/spec.js +207 -6
- package/components/I18n/components/FormatDate/index.js +26 -2
- package/components/I18n/components/FormatDate/spec.js +46 -1
- package/components/I18n/components/FormatNumber/index.js +34 -2
- package/components/I18n/components/FormatNumber/spec.js +41 -2
- package/components/I18n/components/FormatPrice/index.js +32 -2
- package/components/I18n/components/FormatPrice/spec.js +46 -1
- package/components/I18n/components/FormatTime/index.js +26 -2
- package/components/I18n/components/FormatTime/spec.js +43 -2
- package/components/I18n/components/I18nProvider/index.js +52 -9
- package/components/I18n/components/I18nProvider/spec.js +39 -1
- package/components/I18n/components/Placeholder/index.js +8 -2
- package/components/I18n/components/Placeholder/spec.js +30 -1
- package/components/I18n/components/Translate/index.js +68 -7
- package/components/I18n/components/Translate/spec.js +30 -1
- package/components/I18n/index.js +16 -1
- package/components/Icon/index.js +25 -2
- package/components/Icon/style.js +6 -1
- package/components/Image/Image.js +176 -19
- package/components/Image/ImageInner.js +48 -2
- package/components/Image/index.js +1 -1
- package/components/Image/style.js +29 -2
- package/components/InfiniteContainer/index.js +381 -49
- package/components/InfiniteContainer/spec.js +199 -10
- package/components/Input/components/DateInput.js +262 -6
- package/components/Input/components/MultiLineInput.js +98 -12
- package/components/Input/components/SimpleInput.js +207 -31
- package/components/Input/index.js +32 -3
- package/components/Input/spec.js +122 -1
- package/components/KeyboardConsumer/index.js +48 -7
- package/components/Link/connector.js +7 -1
- package/components/Link/index.js +96 -11
- package/components/Link/spec.js +56 -1
- package/components/Link/style.js +10 -1
- package/components/List/components/Item/index.js +35 -3
- package/components/List/components/Item/style.js +16 -1
- package/components/List/index.js +20 -2
- package/components/List/spec.js +31 -1
- package/components/Loading/index.js +6 -2
- package/components/Modal/index.js +38 -3
- package/components/Modal/style.js +36 -1
- package/components/ModalContainer/connector.js +17 -3
- package/components/ModalContainer/index.js +36 -3
- package/components/ModalContainer/spec.js +105 -5
- package/components/Picker/components/Button/index.js +34 -2
- package/components/Picker/components/Button/style.js +19 -1
- package/components/Picker/components/List/index.js +33 -2
- package/components/Picker/components/List/style.js +17 -1
- package/components/Picker/components/Modal/index.js +60 -7
- package/components/Picker/components/Modal/style.js +78 -1
- package/components/Picker/index.js +167 -21
- package/components/Picker/spec.js +83 -2
- package/components/Portal/index.js +130 -19
- package/components/ProductCharacteristics/connector.js +33 -4
- package/components/ProductCharacteristics/context.js +2 -1
- package/components/ProductCharacteristics/helpers/index.js +135 -21
- package/components/ProductCharacteristics/index.js +266 -31
- package/components/RangeSlider/components/Handle/index.js +25 -2
- package/components/RangeSlider/components/Handle/style.js +14 -1
- package/components/RangeSlider/helper.js +43 -8
- package/components/RangeSlider/index.js +228 -38
- package/components/RangeSlider/style.js +14 -1
- package/components/Route/RouteNotFound.js +46 -3
- package/components/Route/index.js +78 -10
- package/components/Router/connector.js +9 -2
- package/components/Router/index.js +237 -31
- package/components/ScannerContainer/connector.js +9 -2
- package/components/ScannerContainer/index.js +42 -6
- package/components/Select/components/Item/index.js +20 -4
- package/components/Select/components/Item/style.js +4 -1
- package/components/Select/index.js +149 -28
- package/components/Select/spec.js +86 -2
- package/components/Select/style.js +17 -1
- package/components/SelectBox/components/Item/index.js +47 -5
- package/components/SelectBox/components/Item/style.js +7 -1
- package/components/SelectBox/index.js +173 -17
- package/components/SelectBox/spec.js +59 -3
- package/components/SelectBox/style.js +18 -1
- package/components/Slider/index.js +6 -2
- package/components/SurroundPortals/index.js +26 -2
- package/components/Swiper/components/SwiperItem/index.js +28 -4
- package/components/Swiper/components/SwiperItem/spec.js +17 -1
- package/components/Swiper/components/SwiperItem/styles.js +5 -1
- package/components/Swiper/index.js +210 -18
- package/components/Swiper/styles.js +75 -7
- package/components/Toaster/index.js +10 -2
- package/components/Transition/index.js +89 -13
- package/components/Widgets/components/Widget/index.js +52 -4
- package/components/Widgets/components/Widget/spec.js +68 -3
- package/components/Widgets/components/Widget/style.js +21 -3
- package/components/Widgets/components/WidgetGrid/index.js +52 -7
- package/components/Widgets/components/WidgetGrid/spec.js +46 -2
- package/components/Widgets/components/WidgetGrid/style.js +8 -1
- package/components/Widgets/helpers/shouldShowWidget.js +44 -7
- package/components/Widgets/index.js +127 -15
- package/components/Widgets/spec.js +213 -6
- package/components/index.js +9 -1
- package/constants/ActionTypes.js +97 -19
- package/constants/Configuration.js +12 -2
- package/constants/Device.js +29 -2
- package/constants/DisplayOptions.js +8 -1
- package/constants/MenuIDs.js +2 -1
- package/constants/ModalTypes.js +1 -1
- package/constants/PageIDs.js +1 -1
- package/constants/Pipelines.js +7 -1
- package/constants/Portals.js +136 -3
- package/constants/Registration.js +3 -1
- package/constants/RoutePaths.js +13 -2
- package/constants/Tracking.js +3 -1
- package/constants/client.js +6 -1
- package/constants/ui.js +2 -1
- package/constants/user.js +6 -2
- package/context/index.js +33 -3
- package/helpers/config/index.js +139 -21
- package/helpers/config/mock.js +200 -8
- package/helpers/config/theme.js +50 -4
- package/helpers/data/index.js +204 -29
- package/helpers/data/spec.js +187 -7
- package/helpers/date/index.js +58 -6
- package/helpers/date/spec.js +92 -1
- package/helpers/dom/index.js +48 -11
- package/helpers/environment/index.js +14 -2
- package/helpers/html/decodeHTML.js +7 -1
- package/helpers/html/handleDOM.js +172 -21
- package/helpers/html/parseHTML.js +67 -12
- package/helpers/i18n/getDateFormatter.js +23 -4
- package/helpers/i18n/getNumberFormatter.js +32 -4
- package/helpers/i18n/getPriceFormatter.js +38 -4
- package/helpers/i18n/getTimeFormatter.js +23 -4
- package/helpers/i18n/getTranslator.js +62 -8
- package/helpers/i18n/index.js +5 -1
- package/helpers/i18n/mergeTranslations.js +36 -9
- package/helpers/i18n/messageCache.js +3 -1
- package/helpers/legacy/index.js +47 -9
- package/helpers/modal/withShowModal.js +13 -2
- package/helpers/portals/portalCollection.js +28 -6
- package/helpers/portals/routePortals.js +12 -1
- package/helpers/redux/compareObjects.js +7 -2
- package/helpers/redux/generateResultHash.js +36 -3
- package/helpers/redux/generateSortedHash.js +7 -2
- package/helpers/redux/hasExpired.js +10 -2
- package/helpers/redux/index.js +7 -1
- package/helpers/redux/mutable.js +143 -24
- package/helpers/redux/shouldFetchData.js +46 -10
- package/helpers/redux/shouldFetchFilters.js +17 -4
- package/helpers/router/index.js +49 -5
- package/helpers/style/index.js +43 -4
- package/helpers/style/spec.js +108 -2
- package/helpers/tracking/index.js +52 -9
- package/helpers/validation/index.js +39 -12
- package/helpers/validation/spec.js +10 -1
- package/package.json +3 -3
- package/providers/index.js +4 -1
- package/providers/loading/context.js +2 -1
- package/providers/loading/index.js +137 -22
- package/providers/toast/context.js +2 -1
- package/providers/toast/index.js +105 -11
- package/reducers/client/connectivity.js +22 -2
- package/reducers/client/index.js +7 -1
- package/reducers/client/info.js +27 -2
- package/reducers/index.js +23 -4
- package/reducers/menu/index.js +5 -1
- package/reducers/menu/menusById.js +41 -2
- package/reducers/modal/index.js +14 -2
- package/reducers/page/index.js +68 -5
- package/reducers/router/index.js +48 -2
- package/reducers/url/index.js +42 -3
- package/reducers/user/data.js +27 -2
- package/reducers/user/index.js +7 -1
- package/reducers/user/login.js +65 -2
- package/selectors/client.js +138 -21
- package/selectors/history.js +49 -11
- package/selectors/menu.js +34 -6
- package/selectors/modal.js +15 -4
- package/selectors/page.js +25 -4
- package/selectors/router.js +154 -30
- package/selectors/url.js +25 -4
- package/selectors/user.js +90 -13
- package/store/index.js +60 -6
- package/store/middelwares/logger.js +7 -1
- package/store/middelwares/streams.js +19 -2
- package/streams/app.js +60 -8
- package/streams/client.js +8 -2
- package/streams/error.js +14 -3
- package/streams/index.js +6 -1
- package/streams/interval.js +6 -2
- package/streams/main.js +27 -2
- package/streams/router.js +45 -8
- package/streams/user.js +89 -15
- package/streams/view.js +97 -25
- package/styles/reset/form.js +57 -5
- package/styles/reset/index.js +6 -1
- package/styles/reset/media.js +22 -1
- package/styles/reset/root.js +33 -1
- package/styles/reset/table.js +10 -1
- package/styles/reset/typography.js +26 -1
- package/subscriptions/app.js +148 -17
- package/subscriptions/error.js +292 -13
- package/subscriptions/helpers/buildRegisterUrl.js +25 -6
- package/subscriptions/helpers/clearUpInAppBrowser.js +14 -3
- package/subscriptions/helpers/handleLinks.js +267 -25
- package/subscriptions/helpers/pipeline.js +12 -1
- package/subscriptions/history.js +34 -6
- package/subscriptions/index.js +25 -4
- package/subscriptions/menu.js +22 -5
- package/subscriptions/mock.js +39 -7
- package/subscriptions/router.js +336 -23
- package/subscriptions/user.js +93 -3
|
@@ -1,7 +1,98 @@
|
|
|
1
|
-
import React from'react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallow } from 'enzyme';
|
|
3
|
+
import Checkbox from "./index";
|
|
4
|
+
|
|
5
|
+
/**
|
|
2
6
|
* Checked Icon
|
|
3
7
|
* @returns {JSX}
|
|
4
|
-
*/
|
|
8
|
+
*/
|
|
9
|
+
const Checked = () => /*#__PURE__*/React.createElement("div", null);
|
|
10
|
+
|
|
11
|
+
/**
|
|
5
12
|
* Unchecked Icon
|
|
6
13
|
* @returns {JSX}
|
|
7
|
-
*/
|
|
14
|
+
*/
|
|
15
|
+
const Unchecked = () => /*#__PURE__*/React.createElement("div", null);
|
|
16
|
+
describe('<Checkbox />', () => {
|
|
17
|
+
it('should render the checkbox with the label before the icon', () => {
|
|
18
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Checkbox, {
|
|
19
|
+
label: /*#__PURE__*/React.createElement("span", null, "Test Label Deluxe"),
|
|
20
|
+
labelPosition: "left",
|
|
21
|
+
checkedIcon: /*#__PURE__*/React.createElement(Checked, null),
|
|
22
|
+
uncheckedIcon: /*#__PURE__*/React.createElement(Unchecked, null),
|
|
23
|
+
checked: false
|
|
24
|
+
}));
|
|
25
|
+
const expected = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("span", null, "Test Label Deluxe"), /*#__PURE__*/React.createElement(Unchecked, null));
|
|
26
|
+
expect(wrapper).toMatchSnapshot();
|
|
27
|
+
expect(wrapper.matchesElement(expected)).toBeTruthy();
|
|
28
|
+
});
|
|
29
|
+
it('should render the checkbox with the label after the icon', () => {
|
|
30
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Checkbox, {
|
|
31
|
+
label: /*#__PURE__*/React.createElement("span", null, "Test Label Deluxe"),
|
|
32
|
+
labelPosition: "right",
|
|
33
|
+
checkedIcon: /*#__PURE__*/React.createElement(Checked, null),
|
|
34
|
+
uncheckedIcon: /*#__PURE__*/React.createElement(Unchecked, null),
|
|
35
|
+
checked: false
|
|
36
|
+
}));
|
|
37
|
+
const expected = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Unchecked, null), /*#__PURE__*/React.createElement("span", null, "Test Label Deluxe"));
|
|
38
|
+
expect(wrapper).toMatchSnapshot();
|
|
39
|
+
expect(wrapper.matchesElement(expected)).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
it('should render the unchecked icon if "checked" is false', () => {
|
|
42
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Checkbox, {
|
|
43
|
+
checked: false,
|
|
44
|
+
label: "Test Label Deluxe",
|
|
45
|
+
checkedIcon: /*#__PURE__*/React.createElement(Checked, null),
|
|
46
|
+
uncheckedIcon: /*#__PURE__*/React.createElement(Unchecked, null)
|
|
47
|
+
}));
|
|
48
|
+
expect(wrapper).toMatchSnapshot();
|
|
49
|
+
expect(wrapper.find(Checked).length).toBe(0);
|
|
50
|
+
expect(wrapper.find(Unchecked).length).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
it('should render the unchecked icon if "checked" is false', () => {
|
|
53
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Checkbox, {
|
|
54
|
+
checked: true,
|
|
55
|
+
label: "Test Label Deluxe",
|
|
56
|
+
checkedIcon: /*#__PURE__*/React.createElement(Checked, null),
|
|
57
|
+
uncheckedIcon: /*#__PURE__*/React.createElement(Unchecked, null)
|
|
58
|
+
}));
|
|
59
|
+
expect(wrapper).toMatchSnapshot();
|
|
60
|
+
expect(wrapper.find(Checked).length).toBe(1);
|
|
61
|
+
expect(wrapper.find(Unchecked).length).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
it('should call the callback with the inverted value', () => {
|
|
64
|
+
const spy = jest.fn();
|
|
65
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Checkbox, {
|
|
66
|
+
label: "Test Label Deluxe",
|
|
67
|
+
checkedIcon: /*#__PURE__*/React.createElement(Checked, null),
|
|
68
|
+
uncheckedIcon: /*#__PURE__*/React.createElement(Unchecked, null),
|
|
69
|
+
checked: false,
|
|
70
|
+
onCheck: spy
|
|
71
|
+
}));
|
|
72
|
+
wrapper.simulate('click');
|
|
73
|
+
expect(spy).toHaveBeenCalledWith(true);
|
|
74
|
+
});
|
|
75
|
+
it('should render an <input> element if a name prop is provided', () => {
|
|
76
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Checkbox, {
|
|
77
|
+
label: "Test Label Deluxe",
|
|
78
|
+
checkedIcon: /*#__PURE__*/React.createElement(Checked, null),
|
|
79
|
+
uncheckedIcon: /*#__PURE__*/React.createElement(Unchecked, null),
|
|
80
|
+
defaultChecked: false,
|
|
81
|
+
name: "myCheckbox"
|
|
82
|
+
}));
|
|
83
|
+
const input = wrapper.find('input');
|
|
84
|
+
expect(input.length).toBe(1);
|
|
85
|
+
expect(input.prop('name')).toEqual('myCheckbox');
|
|
86
|
+
expect(input.prop('value')).toEqual(0);
|
|
87
|
+
});
|
|
88
|
+
it('should work as an uncontrolled input', () => {
|
|
89
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Checkbox, {
|
|
90
|
+
label: "Test Label Deluxe",
|
|
91
|
+
checkedIcon: /*#__PURE__*/React.createElement(Checked, null),
|
|
92
|
+
uncheckedIcon: /*#__PURE__*/React.createElement(Unchecked, null),
|
|
93
|
+
defaultChecked: false
|
|
94
|
+
}));
|
|
95
|
+
wrapper.simulate('click');
|
|
96
|
+
expect(wrapper.state('checked')).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* Searches an object for the given paths.
|
|
3
5
|
* Returns a new object with the values found at the end of each path.
|
|
4
6
|
* @param {Object} obj The object to look through.
|
|
5
7
|
* @param {Object} paths A set of paths to find inside the object.
|
|
6
8
|
* @returns {Object}
|
|
7
|
-
*/
|
|
9
|
+
*/
|
|
10
|
+
const buildParams = (obj, paths) => {
|
|
11
|
+
const params = {};
|
|
12
|
+
Object.entries(paths).forEach(([prop, path]) => {
|
|
13
|
+
const value = get(obj, path);
|
|
14
|
+
params[prop] = typeof value !== 'undefined' ? value : null;
|
|
15
|
+
});
|
|
16
|
+
return params;
|
|
17
|
+
};
|
|
18
|
+
export default buildParams;
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import React from'react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import buildParams from "./helpers/buildParams";
|
|
4
|
+
|
|
5
|
+
/**
|
|
2
6
|
* @param {Function} props.children The child function to call.
|
|
3
7
|
* @param {Object} props.context The react context.
|
|
4
8
|
* @param {Object} props.props The props to create.
|
|
5
9
|
* @returns {JSX}
|
|
6
|
-
*/
|
|
10
|
+
*/
|
|
11
|
+
const Consume = ({
|
|
12
|
+
children,
|
|
13
|
+
context: {
|
|
14
|
+
Consumer
|
|
15
|
+
},
|
|
16
|
+
props
|
|
17
|
+
}) => /*#__PURE__*/React.createElement(Consumer, null, value => children(buildParams(value, props)));
|
|
18
|
+
export default Consume;
|
|
@@ -1,37 +1,135 @@
|
|
|
1
|
-
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import padStart from 'lodash/padStart';
|
|
4
|
+
import I18n from "../I18n";
|
|
5
|
+
|
|
6
|
+
/**
|
|
2
7
|
* Creates the appropriate format for a given time period.
|
|
3
8
|
* @param {number} days The remaining days.
|
|
4
9
|
* @param {number} hours The remaining hours.
|
|
5
10
|
* @param {number} minutes The remaining minutes.
|
|
6
11
|
* @param {number} seconds The remaining seconds.
|
|
7
12
|
* @return {Object} String and params for the i18n component
|
|
8
|
-
*/
|
|
13
|
+
*/
|
|
14
|
+
export const getFormattedTimeString = (days, hours, minutes, seconds) => {
|
|
15
|
+
const formattedHours = padStart(hours, 2, '0');
|
|
16
|
+
const formattedMinutes = padStart(minutes, 2, '0');
|
|
17
|
+
const formattedSeconds = padStart(seconds, 2, '0');
|
|
18
|
+
const hourlyFormat = `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
|
19
|
+
return {
|
|
20
|
+
string: 'common.countdown',
|
|
21
|
+
params: {
|
|
22
|
+
days,
|
|
23
|
+
time: hourlyFormat
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
9
29
|
* Creates a formatted duration string for a given time span represented as unix time stamp.
|
|
10
30
|
* @param {number} timeSpanInput The remaining time span (in seconds).
|
|
11
31
|
* @return {Object} String and params for the I18n component
|
|
12
|
-
*/
|
|
13
|
-
|
|
32
|
+
*/
|
|
33
|
+
const createFormattedTime = timeSpanInput => {
|
|
34
|
+
const timeSpan = Math.max(0, timeSpanInput);
|
|
35
|
+
|
|
36
|
+
// Calculate remaining days, hours, minutes and seconds.
|
|
37
|
+
const days = Math.floor(timeSpan / 86400);
|
|
38
|
+
const hours = Math.floor(timeSpan % 86400 / 3600);
|
|
39
|
+
const minutes = Math.floor(timeSpan % 3600 / 60);
|
|
40
|
+
const seconds = timeSpan % 60;
|
|
41
|
+
return getFormattedTimeString(days, hours, minutes, seconds);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
14
45
|
* The Countdown timer component.
|
|
15
|
-
*/
|
|
46
|
+
*/
|
|
47
|
+
class CountdownTimer extends Component {
|
|
48
|
+
/**
|
|
16
49
|
* The component constructor.
|
|
17
50
|
* @param {Object} props The component properties.
|
|
18
|
-
*/
|
|
19
|
-
|
|
51
|
+
*/
|
|
52
|
+
constructor(props) {
|
|
53
|
+
super(props);
|
|
54
|
+
this.intervalHandle = null;
|
|
55
|
+
this.remainingTime = this.getRemainingTime();
|
|
56
|
+
this.expired = this.remainingTime <= 0;
|
|
57
|
+
|
|
58
|
+
// Calculate the initial formatted time string.
|
|
59
|
+
this.state = {
|
|
60
|
+
formattedTime: createFormattedTime(this.remainingTime)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
20
65
|
* Installs a new interval to update the timer if the component did mount.
|
|
21
|
-
*/
|
|
22
|
-
|
|
66
|
+
*/
|
|
67
|
+
componentDidMount() {
|
|
68
|
+
// Install the interval.
|
|
69
|
+
this.intervalHandle = setInterval(() => {
|
|
70
|
+
/**
|
|
23
71
|
* To allow mocked tests of the timing functions and still be able to deal
|
|
24
72
|
* with paused execution, the delta time is expected to be at least 1.
|
|
25
|
-
*/
|
|
73
|
+
*/
|
|
74
|
+
this.remainingTime = this.getRemainingTime();
|
|
75
|
+
this.updateTimer();
|
|
76
|
+
}, 1000);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
26
80
|
* Clears the timer interval.
|
|
27
|
-
*/
|
|
81
|
+
*/
|
|
82
|
+
componentWillUnmount() {
|
|
83
|
+
if (this.intervalHandle) {
|
|
84
|
+
clearInterval(this.intervalHandle);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
28
89
|
* @returns {number} The remaining time until the timer runs out.
|
|
29
|
-
*/
|
|
90
|
+
*/
|
|
91
|
+
getRemainingTime() {
|
|
92
|
+
return Math.ceil(this.props.timeout - Date.now() / 1000);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
30
96
|
* Updates the formatted time. Will not cause a re-rendering of the component.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
97
|
+
*/
|
|
98
|
+
updateTimer() {
|
|
99
|
+
// Calculate the remaining time until the timer is expired. Also ignore negative durations.
|
|
100
|
+
const deltaTime = Math.max(0, this.remainingTime);
|
|
101
|
+
const isExpired = deltaTime <= 0;
|
|
102
|
+
if (isExpired && !this.expired) {
|
|
103
|
+
this.expired = true;
|
|
104
|
+
|
|
105
|
+
// Clear the interval.
|
|
106
|
+
clearInterval(this.intervalHandle);
|
|
107
|
+
this.intervalHandle = null;
|
|
108
|
+
|
|
109
|
+
// The timer just expired, invoke the callback.
|
|
110
|
+
if (this.props.onExpire) {
|
|
111
|
+
this.props.onExpire();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this.setState({
|
|
115
|
+
formattedTime: createFormattedTime(deltaTime)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
35
120
|
* Renders the element.
|
|
36
121
|
* @return {JSX.Element}
|
|
37
|
-
*/
|
|
122
|
+
*/
|
|
123
|
+
render() {
|
|
124
|
+
return /*#__PURE__*/React.createElement(I18n.Text, {
|
|
125
|
+
string: this.state.formattedTime.string,
|
|
126
|
+
params: this.state.formattedTime.params,
|
|
127
|
+
className: `${this.props.className} common__countdown-timer`
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
CountdownTimer.defaultProps = {
|
|
132
|
+
className: '',
|
|
133
|
+
onExpire: null
|
|
134
|
+
};
|
|
135
|
+
export default CountdownTimer;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React from'react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallow } from 'enzyme';
|
|
3
|
+
import CountdownTimer, { getFormattedTimeString } from "./index";
|
|
4
|
+
describe('<CountdownTimer>', () => {
|
|
5
|
+
jest.useFakeTimers();
|
|
6
|
+
|
|
7
|
+
/**
|
|
2
8
|
* Creates a new countdown timer element.
|
|
3
9
|
* @param {number} remainingDays The remaining days.
|
|
4
10
|
* @param {number} remainingHours The remaining hours.
|
|
@@ -6,21 +12,129 @@ import React from'react';import{shallow}from'enzyme';import CountdownTimer,{getF
|
|
|
6
12
|
* @param {number} remainingSeconds The remaining seconds.
|
|
7
13
|
* @param {Function} callback The expiration callback.
|
|
8
14
|
* @return {JSX}
|
|
9
|
-
*/
|
|
15
|
+
*/
|
|
16
|
+
const createTimerElement = (remainingDays, remainingHours, remainingMinutes, remainingSeconds, callback) => {
|
|
17
|
+
const timeout = Math.floor(Date.now() / 1000) + remainingDays * 86400 + remainingHours * 3600 + remainingMinutes * 60 + remainingSeconds;
|
|
18
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(CountdownTimer, {
|
|
19
|
+
timeout: timeout,
|
|
20
|
+
onExpire: callback
|
|
21
|
+
}));
|
|
22
|
+
let currentTimeOffset = timeout - Math.floor(Date.now() / 1000);
|
|
23
|
+
wrapper.instance().getRemainingTime = () => {
|
|
24
|
+
currentTimeOffset -= 1;
|
|
25
|
+
return currentTimeOffset;
|
|
26
|
+
};
|
|
27
|
+
return wrapper;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
10
31
|
* Performs a time format check for a specific remaining time.
|
|
11
32
|
* @param {number} remainingDays The remaining days.
|
|
12
33
|
* @param {number} remainingHours The remaining hours.
|
|
13
34
|
* @param {number} remainingMinutes The remaining minutes.
|
|
14
35
|
* @param {number} remainingSeconds The remaining seconds.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
36
|
+
*/
|
|
37
|
+
const performFormatCheck = (remainingDays, remainingHours, remainingMinutes, remainingSeconds) => {
|
|
38
|
+
jest.clearAllTimers();
|
|
39
|
+
setInterval.mock.calls = [];
|
|
40
|
+
const wrapper = createTimerElement(remainingDays, remainingHours, remainingMinutes, remainingSeconds, null);
|
|
41
|
+
const expectedTimeFormat = getFormattedTimeString(remainingDays, remainingHours, remainingMinutes, remainingSeconds - 1 // Expect the decremented timeout.
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// We cannot perform a snapshot match here because the timestamp changes for each call.
|
|
45
|
+
expect(setInterval.mock.calls.length).toBe(1);
|
|
46
|
+
jest.runTimersToTime(1000);
|
|
47
|
+
wrapper.update();
|
|
48
|
+
const {
|
|
49
|
+
params,
|
|
50
|
+
string
|
|
51
|
+
} = wrapper.props();
|
|
52
|
+
const renderedTimeFormat = {
|
|
53
|
+
params,
|
|
54
|
+
string
|
|
55
|
+
};
|
|
56
|
+
expect(renderedTimeFormat).toEqual(expectedTimeFormat);
|
|
57
|
+
};
|
|
58
|
+
it('should render the correct time for < 24h', () => performFormatCheck(0, 0, 0, 5));
|
|
59
|
+
it('should render the correct time for 24h - 48h', () => performFormatCheck(1, 12, 6, 5));
|
|
60
|
+
it('should render the correct time for > 2d', () => performFormatCheck(30, 1, 2, 3));
|
|
61
|
+
it('should not render negative durations', () => {
|
|
62
|
+
jest.clearAllTimers();
|
|
63
|
+
const wrapper = createTimerElement(-1, -2, -3, -5, null);
|
|
64
|
+
const expectedTimeFormat = getFormattedTimeString(0, 0, 0, 0);
|
|
65
|
+
jest.runTimersToTime(1000);
|
|
66
|
+
const {
|
|
67
|
+
params,
|
|
68
|
+
string
|
|
69
|
+
} = wrapper.props();
|
|
70
|
+
const renderedTimeFormat = {
|
|
71
|
+
params,
|
|
72
|
+
string
|
|
73
|
+
};
|
|
74
|
+
expect(renderedTimeFormat).toEqual(expectedTimeFormat);
|
|
75
|
+
});
|
|
76
|
+
it('should stop at 00:00:00 when the timer expires', () => {
|
|
77
|
+
const wrapper = createTimerElement(0, 0, 0, 1, null);
|
|
78
|
+
const expectedTimeFormat = getFormattedTimeString(0, 0, 0, 0);
|
|
79
|
+
let renderedTimeFormat;
|
|
80
|
+
|
|
81
|
+
// Run down to 00:00:00.
|
|
82
|
+
jest.runTimersToTime(1000);
|
|
83
|
+
wrapper.update();
|
|
84
|
+
let {
|
|
85
|
+
params,
|
|
86
|
+
string
|
|
87
|
+
} = wrapper.props();
|
|
88
|
+
renderedTimeFormat = {
|
|
89
|
+
params,
|
|
90
|
+
string
|
|
91
|
+
};
|
|
92
|
+
expect(renderedTimeFormat).toEqual(expectedTimeFormat);
|
|
93
|
+
|
|
94
|
+
// Advance time a bit further and make sure the timer stays at 00:00:00.
|
|
95
|
+
jest.runTimersToTime(1000);
|
|
96
|
+
({
|
|
97
|
+
params,
|
|
98
|
+
string
|
|
99
|
+
} = wrapper.props());
|
|
100
|
+
renderedTimeFormat = {
|
|
101
|
+
params,
|
|
102
|
+
string
|
|
103
|
+
};
|
|
104
|
+
expect(renderedTimeFormat).toEqual(expectedTimeFormat);
|
|
105
|
+
});
|
|
106
|
+
it('should invoke the callback when the timer expires', () => {
|
|
107
|
+
let timesCallbackInvoked = 0;
|
|
108
|
+
/**
|
|
20
109
|
* The callback method will just increment a counter when invoked.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
110
|
+
*/
|
|
111
|
+
const callback = () => {
|
|
112
|
+
timesCallbackInvoked += 1;
|
|
113
|
+
};
|
|
114
|
+
createTimerElement(0, 0, 0, 2, callback);
|
|
115
|
+
|
|
116
|
+
// The timer is not expired yet. Make sure the callback is not invoked.
|
|
117
|
+
jest.runTimersToTime(1000);
|
|
118
|
+
expect(timesCallbackInvoked).toBe(0);
|
|
119
|
+
|
|
120
|
+
// The timer should expire by now.
|
|
121
|
+
jest.runTimersToTime(1000);
|
|
122
|
+
expect(timesCallbackInvoked).toBe(1);
|
|
123
|
+
|
|
124
|
+
// Run it again and make sure it won't be called twice.
|
|
125
|
+
jest.runTimersToTime(1000);
|
|
126
|
+
expect(timesCallbackInvoked).toBe(1);
|
|
127
|
+
});
|
|
128
|
+
it('should not invoke the callback when the timeout is already expired.', () => {
|
|
129
|
+
let timesCallbackInvoked = 0;
|
|
130
|
+
/**
|
|
25
131
|
* The callback method will just increment a counter when invoked.
|
|
26
|
-
*/
|
|
132
|
+
*/
|
|
133
|
+
const callback = () => {
|
|
134
|
+
timesCallbackInvoked += 1;
|
|
135
|
+
};
|
|
136
|
+
createTimerElement(0, 0, 0, 0, callback);
|
|
137
|
+
jest.runTimersToTime(1000);
|
|
138
|
+
expect(timesCallbackInvoked).toBe(0);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -1,25 +1,140 @@
|
|
|
1
|
-
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import styles from "./style";
|
|
5
|
+
|
|
6
|
+
/**
|
|
2
7
|
* Drawer component.
|
|
3
|
-
*/
|
|
8
|
+
*/
|
|
9
|
+
class Drawer extends Component {
|
|
10
|
+
/**
|
|
4
11
|
* Initializes the Drawer component.
|
|
5
12
|
* @param {Object} props The components props.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
|
|
13
|
+
*/
|
|
14
|
+
constructor(props) {
|
|
15
|
+
super(props);
|
|
16
|
+
/**
|
|
17
|
+
* Syncs the internal active state when an animation ends.
|
|
18
|
+
*/
|
|
19
|
+
this.handleAnimationEnd = () => {
|
|
20
|
+
this.setState({
|
|
21
|
+
active: this.props.isOpen
|
|
22
|
+
});
|
|
23
|
+
if (!this.props.isOpen) {
|
|
24
|
+
this.props.onDidClose();
|
|
25
|
+
} else {
|
|
26
|
+
this.props.onDidOpen();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
this.sheetRef = /*#__PURE__*/React.createRef();
|
|
30
|
+
this.state = {
|
|
31
|
+
active: props.isOpen
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** inheritdoc */
|
|
36
|
+
componentDidMount() {
|
|
37
|
+
if (this.props.isOpen) {
|
|
38
|
+
if (this.sheetRef.current) {
|
|
39
|
+
this.sheetRef.current.focus();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
9
45
|
* Update state when isOpen changes.
|
|
10
46
|
* @param {Object} nextProps The next component props.
|
|
11
|
-
*/
|
|
47
|
+
*/
|
|
48
|
+
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
49
|
+
if (this.props.isOpen !== nextProps.isOpen) {
|
|
50
|
+
if (nextProps.isOpen) {
|
|
51
|
+
if (typeof nextProps.onOpen === 'function') {
|
|
52
|
+
nextProps.onOpen();
|
|
53
|
+
}
|
|
54
|
+
this.setState({
|
|
55
|
+
active: true
|
|
56
|
+
});
|
|
57
|
+
} else if (typeof nextProps.onClose === 'function') {
|
|
58
|
+
nextProps.onClose();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
12
64
|
* Set focus for a11y when sheet opens
|
|
13
65
|
* @param {Object} prevProps The previous component props.
|
|
14
|
-
*/
|
|
66
|
+
*/
|
|
67
|
+
componentDidUpdate(prevProps) {
|
|
68
|
+
if (!prevProps.isOpen && this.props.isOpen) {
|
|
69
|
+
if (this.sheetRef.current) {
|
|
70
|
+
this.sheetRef.current.focus();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
15
75
|
* Renders the component.
|
|
16
76
|
* @returns {JSX}
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
77
|
+
*/
|
|
78
|
+
render() {
|
|
79
|
+
const {
|
|
80
|
+
className,
|
|
81
|
+
children,
|
|
82
|
+
isOpen,
|
|
83
|
+
animation
|
|
84
|
+
} = this.props;
|
|
85
|
+
const {
|
|
86
|
+
active
|
|
87
|
+
} = this.state;
|
|
88
|
+
const animationIn = animation.in || styles.animation.in;
|
|
89
|
+
const animationOut = animation.out || styles.animation.out;
|
|
90
|
+
const combinedClassName = classNames(className, styles.container, {
|
|
91
|
+
[animationIn]: isOpen
|
|
92
|
+
}, {
|
|
93
|
+
[animationOut]: !isOpen
|
|
94
|
+
}, 'common__drawer');
|
|
95
|
+
const style = {};
|
|
96
|
+
if (typeof animation.duration === 'number') {
|
|
97
|
+
style.animationDuration = `${animation.duration}ms`;
|
|
98
|
+
}
|
|
99
|
+
return active ? /*#__PURE__*/React.createElement("div", {
|
|
100
|
+
ref: this.sheetRef,
|
|
101
|
+
className: combinedClassName,
|
|
102
|
+
style: style,
|
|
103
|
+
onAnimationEnd: () => {
|
|
104
|
+
this.handleAnimationEnd();
|
|
105
|
+
// clear any residual animation style to fix a11y issue on Android
|
|
106
|
+
// (focus ring is misaligned)
|
|
107
|
+
if (this.sheetRef?.style) {
|
|
108
|
+
this.sheetRef.style.animation = '';
|
|
109
|
+
this.sheetRef.style.transform = 'none';
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
role: "dialog",
|
|
113
|
+
"aria-modal": true,
|
|
114
|
+
tabIndex: -1
|
|
115
|
+
}, children) : null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The component prop types.
|
|
120
|
+
* @type {Object}
|
|
121
|
+
*/
|
|
122
|
+
/**
|
|
123
|
+
* The component default props.
|
|
124
|
+
* @type {Object}
|
|
125
|
+
*/
|
|
126
|
+
Drawer.defaultProps = {
|
|
127
|
+
className: '',
|
|
128
|
+
children: null,
|
|
129
|
+
isOpen: false,
|
|
130
|
+
onOpen: () => {},
|
|
131
|
+
onClose: () => {},
|
|
132
|
+
onDidClose: () => {},
|
|
133
|
+
onDidOpen: () => {},
|
|
134
|
+
animation: {
|
|
135
|
+
duration: null,
|
|
136
|
+
in: '',
|
|
137
|
+
out: ''
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
export default Drawer;
|