@shopgate/pwa-common 7.30.0-alpha.7 → 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
package/components/Grid/index.js
CHANGED
|
@@ -1,9 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { objectWithoutProps } from "../../helpers/data";
|
|
4
|
+
import GridItem from "./components/Item";
|
|
5
|
+
import styles, { wrap } from "./style";
|
|
6
|
+
|
|
7
|
+
/**
|
|
2
8
|
* The grid component.
|
|
3
|
-
*/
|
|
9
|
+
*/
|
|
10
|
+
class Grid extends Component {
|
|
11
|
+
/**
|
|
4
12
|
* Composes the props.
|
|
5
13
|
* @returns {Object} The composed props.
|
|
6
|
-
*/
|
|
14
|
+
*/
|
|
15
|
+
getProps() {
|
|
16
|
+
let className = `${this.props.className} ${styles} common__grid`;
|
|
17
|
+
if (this.props.wrap) {
|
|
18
|
+
className += ` ${wrap(this.props.wrap)}`;
|
|
19
|
+
}
|
|
20
|
+
const props = {
|
|
21
|
+
...this.props,
|
|
22
|
+
className
|
|
23
|
+
};
|
|
24
|
+
return objectWithoutProps(props, ['wrap', 'component']);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
7
28
|
* Renders the component.
|
|
8
29
|
* @returns {JSX}
|
|
9
|
-
*/
|
|
30
|
+
*/
|
|
31
|
+
render() {
|
|
32
|
+
return /*#__PURE__*/React.createElement(this.props.component, this.getProps());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
Grid.Item = GridItem;
|
|
36
|
+
Grid.defaultProps = {
|
|
37
|
+
className: '',
|
|
38
|
+
component: 'ul',
|
|
39
|
+
wrap: false
|
|
40
|
+
};
|
|
41
|
+
export default Grid;
|
package/components/Grid/spec.js
CHANGED
|
@@ -1 +1,23 @@
|
|
|
1
|
-
import React from'react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallow } from 'enzyme';
|
|
3
|
+
import Grid from "./index";
|
|
4
|
+
describe('<Grid />', () => {
|
|
5
|
+
it('should render without any further props', () => {
|
|
6
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Grid, null));
|
|
7
|
+
expect(wrapper).toMatchSnapshot();
|
|
8
|
+
});
|
|
9
|
+
it('should be able to render a custom tag', () => {
|
|
10
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Grid, {
|
|
11
|
+
component: "article"
|
|
12
|
+
}));
|
|
13
|
+
expect(wrapper).toMatchSnapshot();
|
|
14
|
+
expect(wrapper.type()).toEqual('article');
|
|
15
|
+
});
|
|
16
|
+
it('should add custom classes on demand', () => {
|
|
17
|
+
const wrapper = shallow(/*#__PURE__*/React.createElement(Grid, {
|
|
18
|
+
className: "custom-class-name"
|
|
19
|
+
}));
|
|
20
|
+
expect(wrapper).toMatchSnapshot();
|
|
21
|
+
expect(wrapper.hasClass('custom-class-name')).toEqual(true);
|
|
22
|
+
});
|
|
23
|
+
});
|
package/components/Grid/style.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import{css}from'glamor'
|
|
1
|
+
import { css } from 'glamor';
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* Creates a class name for the flex-wrap property
|
|
3
5
|
* @param {boolean} [value=false] TRUE for 'wrap', FALSE for 'nowrap'
|
|
4
6
|
* @return {string} The class name
|
|
5
|
-
*/
|
|
7
|
+
*/
|
|
8
|
+
export const wrap = (value = false) => css({
|
|
9
|
+
flexWrap: value ? 'wrap' : 'nowrap'
|
|
10
|
+
}).toString();
|
|
11
|
+
export default css({
|
|
12
|
+
display: 'flex',
|
|
13
|
+
minWidth: '100%'
|
|
14
|
+
}).toString();
|
|
@@ -1,9 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
import { connect } from 'react-redux';
|
|
2
|
+
import { getAreComfortCookiesAccepted, getAreStatisticsCookiesAccepted } from '@shopgate/engage/tracking/selectors';
|
|
3
|
+
import { historyPush } from "../../actions/router";
|
|
4
|
+
|
|
5
|
+
/**
|
|
2
6
|
* Maps the current application state to the component props.
|
|
3
7
|
* @param {Object} state The current application state.
|
|
4
8
|
* @return {Object} The populated component props.
|
|
5
|
-
*/
|
|
9
|
+
*/
|
|
10
|
+
const mapStateToProps = state => ({
|
|
11
|
+
comfortCookiesAccepted: getAreComfortCookiesAccepted(state),
|
|
12
|
+
statisticsCookiesAccepted: getAreStatisticsCookiesAccepted(state)
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
6
16
|
* Connects the dispatch function to a callable function in the props.
|
|
7
17
|
* @param {Function} dispatch The redux dispatch function.
|
|
8
18
|
* @return {Object} The extended component props.
|
|
9
|
-
*/
|
|
19
|
+
*/
|
|
20
|
+
const mapDispatchToProps = dispatch => ({
|
|
21
|
+
navigate: (pathname, target) => dispatch(historyPush({
|
|
22
|
+
pathname,
|
|
23
|
+
...(target && {
|
|
24
|
+
state: {
|
|
25
|
+
target
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}))
|
|
29
|
+
});
|
|
30
|
+
export default connect(mapStateToProps, mapDispatchToProps);
|
|
@@ -1,22 +1,114 @@
|
|
|
1
|
-
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { embeddedMedia } from '@shopgate/pwa-common/collections';
|
|
5
|
+
import EmbeddedMedia from "../EmbeddedMedia";
|
|
6
|
+
import parseHTML from "../../helpers/html/parseHTML";
|
|
7
|
+
import connect from "./connector";
|
|
8
|
+
|
|
9
|
+
/**
|
|
2
10
|
* HtmlSanitizer component.
|
|
3
|
-
*/
|
|
11
|
+
*/
|
|
12
|
+
class HtmlSanitizer extends Component {
|
|
13
|
+
/**
|
|
4
14
|
* @param {Object} props The component props.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
*/
|
|
16
|
+
constructor(props) {
|
|
17
|
+
super(props);
|
|
18
|
+
/**
|
|
19
|
+
* If the user tapped a link element, prevent the default behavior.
|
|
20
|
+
* @param {Object} event The touchstart event.
|
|
21
|
+
*/
|
|
22
|
+
this.handleTap = event => {
|
|
23
|
+
const linkTag = event.target.closest('a');
|
|
24
|
+
if (linkTag) {
|
|
25
|
+
const {
|
|
26
|
+
attributes: {
|
|
27
|
+
href: {
|
|
28
|
+
value: href = ''
|
|
29
|
+
} = {},
|
|
30
|
+
target: {
|
|
31
|
+
value: target = ''
|
|
32
|
+
} = {}
|
|
33
|
+
} = {}
|
|
34
|
+
} = linkTag;
|
|
35
|
+
if (href) {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
if (this.props.settings.handleClick) {
|
|
38
|
+
this.props.settings.handleClick(href, target);
|
|
39
|
+
} else {
|
|
40
|
+
this.props.navigate(href, target);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
this.htmlContainer = /*#__PURE__*/React.createRef();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
9
49
|
* Registers the event handler for when the user taps inside the html content.
|
|
10
|
-
*/
|
|
50
|
+
*/
|
|
51
|
+
componentDidMount() {
|
|
52
|
+
this.htmlContainer.current.addEventListener('click', this.handleTap, true);
|
|
53
|
+
embeddedMedia.add(this.htmlContainer.current);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
11
57
|
* Only update if the HTML changed.
|
|
12
58
|
* @param {Object} nextProps The next props for the component.
|
|
13
59
|
* @return {boolean}
|
|
14
|
-
*/
|
|
60
|
+
*/
|
|
61
|
+
shouldComponentUpdate(nextProps) {
|
|
62
|
+
return nextProps.children !== this.props.children || nextProps.comfortCookiesAccepted !== this.props.comfortCookiesAccepted || nextProps.statisticsCookiesAccepted !== this.props.statisticsCookiesAccepted;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
15
66
|
* Updates embedded media within the html container.
|
|
16
|
-
*/
|
|
67
|
+
*/
|
|
68
|
+
componentDidUpdate() {
|
|
69
|
+
embeddedMedia.add(this.htmlContainer.current);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
17
73
|
* Removes the event handler.
|
|
18
|
-
*/
|
|
74
|
+
*/
|
|
75
|
+
componentWillUnmount() {
|
|
76
|
+
this.htmlContainer.current.removeEventListener('click', this.handleTap, true);
|
|
77
|
+
embeddedMedia.remove(this.htmlContainer.current);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
19
80
|
* Renders the component.
|
|
20
81
|
* @returns {JSX}
|
|
21
|
-
*/
|
|
22
|
-
|
|
82
|
+
*/
|
|
83
|
+
render() {
|
|
84
|
+
const cookieConsentSettings = {
|
|
85
|
+
comfortCookiesAccepted: this.props.comfortCookiesAccepted,
|
|
86
|
+
statisticsCookiesAccepted: this.props.statisticsCookiesAccepted
|
|
87
|
+
};
|
|
88
|
+
const innerHTML = {
|
|
89
|
+
__html: parseHTML(this.props.children, this.props.decode, this.props.settings, this.props.processStyles, cookieConsentSettings)
|
|
90
|
+
};
|
|
91
|
+
const {
|
|
92
|
+
wrapper: Wrapper
|
|
93
|
+
} = this.props;
|
|
94
|
+
return /*#__PURE__*/React.createElement(Wrapper, {
|
|
95
|
+
cookieConsentSettings: cookieConsentSettings
|
|
96
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
97
|
+
// eslint-disable-next-line react/no-danger
|
|
98
|
+
dangerouslySetInnerHTML: innerHTML,
|
|
99
|
+
ref: this.htmlContainer,
|
|
100
|
+
className: classNames(this.props.className, 'common__html-sanitizer')
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
HtmlSanitizer.defaultProps = {
|
|
105
|
+
children: '',
|
|
106
|
+
className: '',
|
|
107
|
+
decode: false,
|
|
108
|
+
processStyles: false,
|
|
109
|
+
settings: {},
|
|
110
|
+
wrapper: EmbeddedMedia,
|
|
111
|
+
comfortCookiesAccepted: false,
|
|
112
|
+
statisticsCookiesAccepted: false
|
|
113
|
+
};
|
|
114
|
+
export default connect(HtmlSanitizer);
|
|
@@ -1,18 +1,219 @@
|
|
|
1
|
-
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { mount } from 'enzyme';
|
|
4
|
+
import { JSDOM } from 'jsdom';
|
|
5
|
+
import { embeddedMedia } from '@shopgate/pwa-common/collections';
|
|
6
|
+
import HtmlSanitizer from "./index";
|
|
7
|
+
jest.mock("../EmbeddedMedia", () => ({
|
|
8
|
+
children
|
|
9
|
+
}) => children);
|
|
10
|
+
jest.mock("./connector", () => Cmp => Cmp);
|
|
11
|
+
|
|
12
|
+
/**
|
|
2
13
|
* @param {string} html HTML markup.
|
|
3
14
|
* @param {Object} props Component props.
|
|
4
15
|
* @returns {JSX}
|
|
5
|
-
*/
|
|
16
|
+
*/
|
|
17
|
+
const createWrapper = (html, props = {}) => mount(/*#__PURE__*/React.createElement(HtmlSanitizer, _extends({
|
|
18
|
+
navigate: () => {}
|
|
19
|
+
}, props), html));
|
|
20
|
+
describe('<HtmlSanitizer />', () => {
|
|
21
|
+
let embeddedMediaAddSpy;
|
|
22
|
+
let embeddedMediaRemoveSpy;
|
|
23
|
+
let embeddedMediaHandleCookieConsentSpy;
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
embeddedMediaAddSpy = jest.spyOn(embeddedMedia, 'add');
|
|
27
|
+
embeddedMediaRemoveSpy = jest.spyOn(embeddedMedia, 'remove');
|
|
28
|
+
embeddedMediaHandleCookieConsentSpy = jest.spyOn(embeddedMedia, 'handleCookieConsent');
|
|
29
|
+
});
|
|
30
|
+
it('should render the HtmlSanitizer', () => {
|
|
31
|
+
/**
|
|
6
32
|
* The value for html is the HTML-escaped equivalent of the following:
|
|
7
33
|
* <h1>Hello World!</h1>
|
|
8
34
|
* @type {string}
|
|
9
|
-
*/
|
|
10
|
-
|
|
35
|
+
*/
|
|
36
|
+
const html = '<h1>Hello World!</h1>';
|
|
37
|
+
const wrapper = createWrapper(html, {
|
|
38
|
+
decode: true
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Test result of dangerouslySetInnerHTML.
|
|
42
|
+
expect(wrapper.html()).toEqual('<div class="common__html-sanitizer"><h1>Hello World!</h1></div>');
|
|
43
|
+
expect(wrapper.render()).toMatchSnapshot();
|
|
44
|
+
});
|
|
45
|
+
it('should add and remove handlers for embedded media', () => {
|
|
46
|
+
const wrapper = createWrapper('<div></div>', {
|
|
47
|
+
decode: true
|
|
48
|
+
});
|
|
49
|
+
const ref = wrapper.instance().htmlContainer.current;
|
|
50
|
+
expect(embeddedMediaAddSpy).toHaveBeenCalledTimes(1);
|
|
51
|
+
expect(embeddedMediaAddSpy).toHaveBeenCalledWith(ref);
|
|
52
|
+
expect(embeddedMediaRemoveSpy).toHaveBeenCalledTimes(0);
|
|
53
|
+
wrapper.setProps({
|
|
54
|
+
children: '<span></span>'
|
|
55
|
+
});
|
|
56
|
+
expect(embeddedMediaAddSpy).toHaveBeenCalledTimes(2);
|
|
57
|
+
expect(embeddedMediaAddSpy).toHaveBeenCalledWith(ref);
|
|
58
|
+
expect(embeddedMediaRemoveSpy).toHaveBeenCalledTimes(0);
|
|
59
|
+
wrapper.unmount();
|
|
60
|
+
expect(embeddedMediaAddSpy).toHaveBeenCalledTimes(2);
|
|
61
|
+
expect(embeddedMediaRemoveSpy).toHaveBeenCalledTimes(1);
|
|
62
|
+
expect(embeddedMediaRemoveSpy).toHaveBeenCalledWith(ref);
|
|
63
|
+
});
|
|
64
|
+
it('strips out images with relative paths', () => {
|
|
65
|
+
const html = `
|
|
66
|
+
<div>
|
|
67
|
+
<style>a { color: red }</style>
|
|
68
|
+
<a href="foo">
|
|
69
|
+
<img src="bar.jpg" />
|
|
70
|
+
</a>
|
|
71
|
+
</div>
|
|
72
|
+
`;
|
|
73
|
+
const wrapper = createWrapper(html);
|
|
74
|
+
expect(wrapper.html()).not.toContain('<img');
|
|
75
|
+
expect(wrapper.html()).toContain('<style>');
|
|
76
|
+
expect(wrapper.render()).toMatchSnapshot();
|
|
77
|
+
});
|
|
78
|
+
it('should move style blocks out of the content', () => {
|
|
79
|
+
const html = `
|
|
80
|
+
<div>
|
|
81
|
+
<style>a { color: red }</style>
|
|
82
|
+
<a href="foo">
|
|
83
|
+
<img src="bar.jpg" />
|
|
84
|
+
</a>
|
|
85
|
+
</div>
|
|
86
|
+
`;
|
|
87
|
+
const wrapper = createWrapper(html, {
|
|
88
|
+
processStyles: true
|
|
89
|
+
});
|
|
90
|
+
expect(wrapper.html()).not.toContain('<style>');
|
|
91
|
+
});
|
|
92
|
+
it('does not strip out images with absolute paths', () => {
|
|
93
|
+
const html = `
|
|
94
|
+
<div>
|
|
95
|
+
<a href="foo">
|
|
96
|
+
<img src="http://google.de/bar.jpg" />
|
|
97
|
+
</a>
|
|
98
|
+
</div>
|
|
99
|
+
`;
|
|
100
|
+
const wrapper = createWrapper(html);
|
|
101
|
+
expect(wrapper.html()).toContain('<img');
|
|
102
|
+
expect(wrapper.render()).toMatchSnapshot();
|
|
103
|
+
});
|
|
104
|
+
it('strips out the script tags', () => {
|
|
105
|
+
/**
|
|
11
106
|
* The value for html is the HTML-escaped equivalent of the following:
|
|
12
107
|
* <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
|
|
13
108
|
* <script type="text/javascript">var x = 42;</script>
|
|
14
109
|
* <p>Foo Bar</p>
|
|
15
110
|
* <script>var y = 23;</script>
|
|
16
111
|
* @type {string}
|
|
17
|
-
*/
|
|
18
|
-
|
|
112
|
+
*/
|
|
113
|
+
const html = '<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script> <script type="text/javascript">var x = 42;</script> <p>Foo Bar</p> <script>var y = 23;</script>';
|
|
114
|
+
const wrapper = createWrapper(html, {
|
|
115
|
+
decode: true
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Test result of dangerouslySetInnerHTML.
|
|
119
|
+
expect(wrapper.html()).toEqual('<div class="common__html-sanitizer"> <p>Foo Bar</p> </div>');
|
|
120
|
+
expect(wrapper).toMatchSnapshot();
|
|
121
|
+
});
|
|
122
|
+
describe('Link handling', () => {
|
|
123
|
+
const mockedHandleClick = jest.fn();
|
|
124
|
+
beforeEach(() => {
|
|
125
|
+
mockedHandleClick.mockClear();
|
|
126
|
+
});
|
|
127
|
+
it('follows a link from a plain <a>', () => {
|
|
128
|
+
const doc = new JSDOM('<!doctype html><html><body><div>/<div></body></html>').window.document;
|
|
129
|
+
const html = '<a id="link" href="#follow-me-and-everything-is-alright">Plain Link</a>';
|
|
130
|
+
const wrapper = mount(/*#__PURE__*/React.createElement(HtmlSanitizer, {
|
|
131
|
+
decode: true,
|
|
132
|
+
settings: {
|
|
133
|
+
handleClick: mockedHandleClick
|
|
134
|
+
},
|
|
135
|
+
navigate: () => {}
|
|
136
|
+
}, html), {
|
|
137
|
+
attachTo: doc.getElementsByTagName('div')[0]
|
|
138
|
+
});
|
|
139
|
+
const aTag = doc.getElementsByTagName('a')[0];
|
|
140
|
+
aTag.closest = () => aTag;
|
|
141
|
+
const event = {
|
|
142
|
+
target: aTag,
|
|
143
|
+
preventDefault: () => {}
|
|
144
|
+
};
|
|
145
|
+
wrapper.instance().handleTap(event);
|
|
146
|
+
expect(mockedHandleClick).toHaveBeenCalledTimes(1);
|
|
147
|
+
expect(mockedHandleClick).toHaveBeenCalledWith('#follow-me-and-everything-is-alright', '');
|
|
148
|
+
});
|
|
149
|
+
it('follows a link from a <a> with other HTML inside', () => {
|
|
150
|
+
const doc = new JSDOM('<!doctype html><html><body><div>/<div></body></html>').window.document;
|
|
151
|
+
const html = '<a id="link" target="_blank" href="#I-ll-be-the-one-to-tuck-you-in-at-night"><span>Span Link</span></a>';
|
|
152
|
+
const wrapper = mount(/*#__PURE__*/React.createElement(HtmlSanitizer, {
|
|
153
|
+
decode: true,
|
|
154
|
+
settings: {
|
|
155
|
+
handleClick: mockedHandleClick
|
|
156
|
+
},
|
|
157
|
+
navigate: () => {}
|
|
158
|
+
}, html), {
|
|
159
|
+
attachTo: doc.getElementsByTagName('div')[0]
|
|
160
|
+
});
|
|
161
|
+
const aTag = doc.getElementsByTagName('a')[0];
|
|
162
|
+
const spanTag = doc.getElementsByTagName('span')[0];
|
|
163
|
+
spanTag.closest = () => aTag;
|
|
164
|
+
const event = {
|
|
165
|
+
target: spanTag,
|
|
166
|
+
preventDefault: () => {}
|
|
167
|
+
};
|
|
168
|
+
wrapper.instance().handleTap(event);
|
|
169
|
+
expect(mockedHandleClick).toHaveBeenCalledTimes(1);
|
|
170
|
+
expect(mockedHandleClick).toHaveBeenCalledWith('#I-ll-be-the-one-to-tuck-you-in-at-night', '_blank');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('Cookie consent handling', () => {
|
|
174
|
+
it('should invoke handleCookieConsent method of embedded media with default cookie consent settings', () => {
|
|
175
|
+
createWrapper('<div></div>', {
|
|
176
|
+
decode: true
|
|
177
|
+
});
|
|
178
|
+
expect(embeddedMediaHandleCookieConsentSpy).toHaveBeenCalledTimes(1);
|
|
179
|
+
expect(embeddedMediaHandleCookieConsentSpy).toHaveBeenCalledWith(expect.any(Document), {
|
|
180
|
+
comfortCookiesAccepted: false,
|
|
181
|
+
statisticsCookiesAccepted: false
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
it('should invoke handleCookieConsent method of embedded media with accepted comfort cookies', () => {
|
|
185
|
+
createWrapper('<div></div>', {
|
|
186
|
+
decode: true,
|
|
187
|
+
comfortCookiesAccepted: true
|
|
188
|
+
});
|
|
189
|
+
expect(embeddedMediaHandleCookieConsentSpy).toHaveBeenCalledTimes(1);
|
|
190
|
+
expect(embeddedMediaHandleCookieConsentSpy).toHaveBeenCalledWith(expect.any(Document), {
|
|
191
|
+
comfortCookiesAccepted: true,
|
|
192
|
+
statisticsCookiesAccepted: false
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
it('should invoke handleCookieConsent method of embedded media with accepted statistics cookies', () => {
|
|
196
|
+
createWrapper('<div></div>', {
|
|
197
|
+
decode: true,
|
|
198
|
+
statisticsCookiesAccepted: true
|
|
199
|
+
});
|
|
200
|
+
expect(embeddedMediaHandleCookieConsentSpy).toHaveBeenCalledTimes(1);
|
|
201
|
+
expect(embeddedMediaHandleCookieConsentSpy).toHaveBeenCalledWith(expect.any(Document), {
|
|
202
|
+
comfortCookiesAccepted: false,
|
|
203
|
+
statisticsCookiesAccepted: true
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
it('should invoke handleCookieConsent method of embedded media with all cookies accepted', () => {
|
|
207
|
+
createWrapper('<div></div>', {
|
|
208
|
+
decode: true,
|
|
209
|
+
comfortCookiesAccepted: true,
|
|
210
|
+
statisticsCookiesAccepted: true
|
|
211
|
+
});
|
|
212
|
+
expect(embeddedMediaHandleCookieConsentSpy).toHaveBeenCalledTimes(1);
|
|
213
|
+
expect(embeddedMediaHandleCookieConsentSpy).toHaveBeenCalledWith(expect.any(Document), {
|
|
214
|
+
comfortCookiesAccepted: true,
|
|
215
|
+
statisticsCookiesAccepted: true
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import React,{Fragment,memo}from'react';
|
|
1
|
+
import React, { Fragment, memo } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { i18n } from '@shopgate/engage/core/helpers/i18n';
|
|
4
|
+
|
|
5
|
+
/**
|
|
2
6
|
* Formats a date.
|
|
3
7
|
* @param {Object} props The component props.
|
|
4
8
|
* @returns {JSX}
|
|
5
|
-
*/
|
|
9
|
+
*/
|
|
10
|
+
const FormatDate = ({
|
|
11
|
+
timestamp,
|
|
12
|
+
format
|
|
13
|
+
}) => /*#__PURE__*/React.createElement(Fragment, null, FormatDate.format({
|
|
14
|
+
timestamp,
|
|
15
|
+
format
|
|
16
|
+
}));
|
|
17
|
+
FormatDate.format = ({
|
|
18
|
+
timestamp,
|
|
19
|
+
format
|
|
20
|
+
}) => {
|
|
21
|
+
if (!i18n.ready) {
|
|
22
|
+
return timestamp;
|
|
23
|
+
}
|
|
24
|
+
return i18n.date(timestamp, format);
|
|
25
|
+
};
|
|
26
|
+
FormatDate.defaultProps = {
|
|
27
|
+
format: 'medium'
|
|
28
|
+
};
|
|
29
|
+
export default /*#__PURE__*/memo(FormatDate);
|
|
@@ -1 +1,46 @@
|
|
|
1
|
-
import React from'react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mount } from 'enzyme';
|
|
3
|
+
import { i18n } from '@shopgate/engage/core';
|
|
4
|
+
import I18n from "../../index";
|
|
5
|
+
jest.unmock('@shopgate/engage/core/helpers/i18n');
|
|
6
|
+
describe('<FormatDate />', () => {
|
|
7
|
+
const locales = {
|
|
8
|
+
greeting: 'Hello {date}'
|
|
9
|
+
};
|
|
10
|
+
const lang = 'en-US';
|
|
11
|
+
const timestamp = 123456789000;
|
|
12
|
+
const formattedDate = 'Nov 29, 1973';
|
|
13
|
+
const format = 'medium';
|
|
14
|
+
i18n.init({
|
|
15
|
+
locales,
|
|
16
|
+
lang
|
|
17
|
+
});
|
|
18
|
+
describe('Given the component was mounted to the DOM', () => {
|
|
19
|
+
let renderedElement;
|
|
20
|
+
it('should match snapshot', () => {
|
|
21
|
+
renderedElement = mount(/*#__PURE__*/React.createElement(I18n.Provider, null, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("span", {
|
|
22
|
+
className: "only-date"
|
|
23
|
+
}, /*#__PURE__*/React.createElement(I18n.Date, {
|
|
24
|
+
timestamp: timestamp,
|
|
25
|
+
format: format
|
|
26
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
27
|
+
className: "text-with-date"
|
|
28
|
+
}, /*#__PURE__*/React.createElement(I18n.Text, {
|
|
29
|
+
string: "greeting"
|
|
30
|
+
}, /*#__PURE__*/React.createElement(I18n.Date, {
|
|
31
|
+
forKey: "date",
|
|
32
|
+
timestamp: timestamp,
|
|
33
|
+
format: format
|
|
34
|
+
}))))));
|
|
35
|
+
expect(renderedElement).toMatchSnapshot();
|
|
36
|
+
});
|
|
37
|
+
it('should render formatted date', () => {
|
|
38
|
+
const text = renderedElement.find('.only-date').text();
|
|
39
|
+
expect(text).toBe(formattedDate);
|
|
40
|
+
});
|
|
41
|
+
it('should render within translated text', () => {
|
|
42
|
+
const text = renderedElement.find('.text-with-date').text();
|
|
43
|
+
expect(text).toBe(`Hello ${formattedDate}`);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -1,5 +1,37 @@
|
|
|
1
|
-
import React,{memo}from'react';
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { i18n } from '@shopgate/engage/core/helpers/i18n';
|
|
4
|
+
/**
|
|
2
5
|
* Formats a number.
|
|
3
6
|
* @param {Object} props The component props.
|
|
4
7
|
* @returns {JSX}
|
|
5
|
-
*/
|
|
8
|
+
*/
|
|
9
|
+
const FormatNumber = ({
|
|
10
|
+
className,
|
|
11
|
+
number,
|
|
12
|
+
fractions
|
|
13
|
+
}) => {
|
|
14
|
+
if (!className) {
|
|
15
|
+
FormatNumber.format({
|
|
16
|
+
number,
|
|
17
|
+
fractions
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
21
|
+
className: className
|
|
22
|
+
}, FormatNumber.format({
|
|
23
|
+
number,
|
|
24
|
+
fractions
|
|
25
|
+
}));
|
|
26
|
+
};
|
|
27
|
+
FormatNumber.format = props => {
|
|
28
|
+
if (!i18n.ready) {
|
|
29
|
+
return props.number;
|
|
30
|
+
}
|
|
31
|
+
return i18n.number(props.number, props.fractions);
|
|
32
|
+
};
|
|
33
|
+
FormatNumber.defaultProps = {
|
|
34
|
+
className: null,
|
|
35
|
+
fractions: 0
|
|
36
|
+
};
|
|
37
|
+
export default /*#__PURE__*/memo(FormatNumber);
|