@shopgate/pwa-common 7.32.0-beta.9 → 7.32.1-beta.1

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.
Files changed (50) hide show
  1. package/actions/app/handleLink.js +6 -2
  2. package/components/Swiper/index.js +16 -2
  3. package/helpers/html/decodeHTML.js +5 -3
  4. package/helpers/html/htmlToText.js +15 -0
  5. package/helpers/mocks/mockRenderOptions.js +21 -0
  6. package/helpers/router/index.js +85 -0
  7. package/package.json +3 -3
  8. package/subscriptions/router.js +20 -6
  9. package/action-creators/app/spec.js +0 -96
  10. package/action-creators/client/spec.js +0 -44
  11. package/action-creators/menu/spec.js +0 -37
  12. package/action-creators/modal/spec.js +0 -26
  13. package/action-creators/page/spec.js +0 -38
  14. package/action-creators/url/spec.js +0 -45
  15. package/action-creators/user/spec.js +0 -186
  16. package/components/Backdrop/spec.js +0 -24
  17. package/components/Button/spec.js +0 -42
  18. package/components/Checkbox/spec.js +0 -111
  19. package/components/CountdownTimer/spec.js +0 -141
  20. package/components/Drawer/spec.js +0 -79
  21. package/components/Ellipsis/spec.js +0 -15
  22. package/components/EmbeddedMedia/spec.js +0 -61
  23. package/components/Grid/components/Item/spec.js +0 -24
  24. package/components/Grid/spec.js +0 -24
  25. package/components/HtmlSanitizer/spec.js +0 -229
  26. package/components/I18n/components/FormatDate/spec.js +0 -54
  27. package/components/I18n/components/FormatNumber/spec.js +0 -51
  28. package/components/I18n/components/FormatPrice/spec.js +0 -54
  29. package/components/I18n/components/FormatTime/spec.js +0 -51
  30. package/components/I18n/components/I18nProvider/spec.js +0 -40
  31. package/components/I18n/components/Placeholder/spec.js +0 -37
  32. package/components/I18n/components/Translate/spec.js +0 -33
  33. package/components/InfiniteContainer/spec.js +0 -204
  34. package/components/Input/spec.js +0 -123
  35. package/components/Link/spec.js +0 -60
  36. package/components/List/spec.js +0 -26
  37. package/components/ModalContainer/spec.js +0 -115
  38. package/components/Select/spec.js +0 -122
  39. package/components/SelectBox/spec.js +0 -68
  40. package/components/Swiper/components/SwiperItem/spec.js +0 -26
  41. package/components/Widgets/components/Widget/spec.js +0 -75
  42. package/components/Widgets/components/WidgetGrid/spec.js +0 -53
  43. package/components/Widgets/spec.js +0 -234
  44. package/helpers/data/spec.js +0 -194
  45. package/helpers/date/spec.js +0 -92
  46. package/helpers/style/spec.js +0 -108
  47. package/helpers/validation/spec.js +0 -10
  48. package/providers/toast/spec.js +0 -110
  49. package/tsconfig.build.json +0 -16
  50. package/tsconfig.json +0 -3
@@ -6,6 +6,7 @@ import "core-js/modules/web.url-search-params.js";
6
6
  import { logger } from '@shopgate/pwa-core/helpers';
7
7
  import { DEEPLINK_CART_ADD_PRODUCT_PATTERN } from '@shopgate/pwa-common-commerce/cart/constants';
8
8
  import fetchProduct from '@shopgate/pwa-common-commerce/product/actions/fetchProduct';
9
+ import { sanitizeLink } from "../../helpers/router";
9
10
  import { historyPush, historyReset } from "../router";
10
11
  import { INDEX_PATH_DEEPLINK, INDEX_PATH } from "../../constants/RoutePaths";
11
12
 
@@ -24,8 +25,11 @@ export default function handleLink(payload, allowExternalLinks = false) {
24
25
  return;
25
26
  }
26
27
 
27
- // cast to string
28
- link = String(link);
28
+ // Cast to string and remove potentially malicious content from the external link
29
+ link = sanitizeLink(String(link));
30
+ if (!link) {
31
+ return;
32
+ }
29
33
  let pathname;
30
34
  if (link.startsWith('http')) {
31
35
  // Link is common URL schema.
@@ -11,6 +11,9 @@ import { useReduceMotion } from '@shopgate/engage/a11y/hooks';
11
11
  import { makeStyles } from '@shopgate/engage/styles';
12
12
  import SwiperItem from "./components/SwiperItem";
13
13
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
14
+ const INSET_LEFT = 'var(--swiper-pagination-inset-left, 0px)';
15
+ const INSET_RIGHT = 'var(--swiper-pagination-inset-right, 0px)';
16
+ const INSET_WIDTH = `calc(100% - ${INSET_LEFT} - ${INSET_RIGHT})`;
14
17
  const useStyles = makeStyles()(theme => ({
15
18
  container: {
16
19
  position: 'relative',
@@ -18,6 +21,8 @@ const useStyles = makeStyles()(theme => ({
18
21
  '&.pagination-below': {
19
22
  '& .swiper-pagination.swiper-pagination-bullets': {
20
23
  '--swiper-pagination-bottom': 0,
24
+ '--swiper-pagination-inset-left': '0px',
25
+ '--swiper-pagination-inset-right': '0px',
21
26
  position: 'relative',
22
27
  height: 32,
23
28
  lineHeight: '40px'
@@ -47,7 +52,7 @@ const useStyles = makeStyles()(theme => ({
47
52
  '& .swiper-pagination-fraction': {
48
53
  top: 'var(--swiper-pagination-fraction-top-offset, 4px)',
49
54
  left: 'auto',
50
- right: 0,
55
+ right: INSET_RIGHT,
51
56
  bottom: 'auto',
52
57
  fontSize: theme.typography.caption.fontSize,
53
58
  background: theme.components.swiper.paginationFractionBackground,
@@ -58,7 +63,16 @@ const useStyles = makeStyles()(theme => ({
58
63
  margin: '4px 16px',
59
64
  transition: 'opacity 300ms cubic-bezier(0.25, 0.1, 0.25, 1)'
60
65
  },
61
- '& .swiper-pagination-progressbar': {
66
+ '&& .swiper-pagination-bullets': {
67
+ left: INSET_LEFT,
68
+ width: INSET_WIDTH
69
+ },
70
+ '&& .swiper-pagination-bullets.swiper-pagination-bullets-dynamic': {
71
+ left: `calc(50% + (${INSET_LEFT} - ${INSET_RIGHT}) / 2)`
72
+ },
73
+ '&& .swiper-pagination-progressbar': {
74
+ left: INSET_LEFT,
75
+ width: INSET_WIDTH,
62
76
  background: theme.components.swiper.paginationProgressbarBackground,
63
77
  '& .swiper-pagination-progressbar-fill': {
64
78
  background: theme.components.swiper.paginationProgressbarActiveColor
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Unescape HTML entities.
3
+ * The input is assigned to a textarea element whose content is parsed as plain text, so markup
4
+ * within the input is never turned into DOM nodes and can't execute scripts.
3
5
  * @param {string} input The escaped HTML.
4
6
  * @returns {string} The unescaped HTML.
5
7
  */
6
8
  const decodeHTML = input => {
7
- const e = document.createElement('div');
8
- e.innerHTML = input;
9
- return e.childNodes.length === 0 ? '' : e.childNodes[0].nodeValue;
9
+ const textarea = document.createElement('textarea');
10
+ textarea.innerHTML = input;
11
+ return textarea.value;
10
12
  };
11
13
  export default decodeHTML;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Converts a string which might contain HTML markup or HTML entities into plain text.
3
+ * Tags are stripped and entities are decoded. The input is parsed within an inert document
4
+ * created by DOMParser, so scripts are not executed and no resources are loaded.
5
+ * @param {string} input The input string.
6
+ * @returns {string} The plain text.
7
+ */
8
+ const htmlToText = input => {
9
+ if (typeof input !== 'string' || input === '') {
10
+ return '';
11
+ }
12
+ const doc = new DOMParser().parseFromString(input, 'text/html');
13
+ return doc.body?.textContent ?? '';
14
+ };
15
+ export default htmlToText;
@@ -0,0 +1,21 @@
1
+ import PropTypes from 'prop-types';
2
+ const options = {
3
+ context: {
4
+ i18n: () => ({
5
+ // eslint-disable-next-line no-underscore-dangle
6
+ __(input) {
7
+ if (input) {
8
+ return input;
9
+ }
10
+ return '';
11
+ },
12
+ _p: () => 'p',
13
+ _d: () => 'd',
14
+ _n: () => 'n'
15
+ })
16
+ },
17
+ childContextTypes: {
18
+ i18n: PropTypes.func
19
+ }
20
+ };
21
+ export default options;
@@ -1,7 +1,9 @@
1
1
  import "core-js/modules/es.array.reduce.js";
2
+ import "core-js/modules/es.string.replace.js";
2
3
  import "core-js/modules/web.url-search-params.js";
3
4
  import { createBrowserHistory } from 'history';
4
5
  import { router } from '@virtuous/conductor';
6
+ import { logger } from '@shopgate/pwa-core/helpers';
5
7
  const match = /^(.*)index.html/.exec(window.location.pathname);
6
8
  const {
7
9
  getCurrentRoute
@@ -56,4 +58,87 @@ export const parseObjectToQueryString = (obj, includePrefix = true) => {
56
58
  return `?${urlParams.toString()}`;
57
59
  }
58
60
  return urlParams.toString();
61
+ };
62
+
63
+ // Angle brackets and their HTML entity representations.
64
+ const ANGLE_BRACKET_ENTITIES = '&(lt|gt|#0*6[02]|#x0*3[ce]);?';
65
+ const UNSAFE_CHARS_REGEX = new RegExp(`[<>]|${ANGLE_BRACKET_ENTITIES}`, 'i');
66
+ const UNSAFE_PROTOCOL_REGEX = /^\s*(javascript|data|vbscript):/i;
67
+
68
+ /**
69
+ * Removes HTML tags, angle brackets and their entity representations from a string.
70
+ * @param {string} value The (decoded) value.
71
+ * @returns {string}
72
+ */
73
+ const stripHtml = value => value.replace(new RegExp(ANGLE_BRACKET_ENTITIES, 'gi'), '').replace(/<[^>]*>/g, '').replace(/[<>]/g, '');
74
+
75
+ /**
76
+ * Removes HTML from a single URI component. The component is only modified when it contains
77
+ * suspicious characters, so regular components (e.g. encoded product ids) stay untouched.
78
+ * @param {string} component A single URI encoded component like a path segment.
79
+ * @returns {string}
80
+ */
81
+ const sanitizeUriComponent = component => {
82
+ let decoded;
83
+ try {
84
+ decoded = decodeURIComponent(component);
85
+ } catch (e) {
86
+ // Malformed encoding - fall back to the raw component.
87
+ decoded = component;
88
+ }
89
+ if (!UNSAFE_CHARS_REGEX.test(decoded)) {
90
+ return component;
91
+ }
92
+ return encodeURIComponent(stripHtml(decoded));
93
+ };
94
+
95
+ /**
96
+ * Removes HTML from a decoded query parameter key or value.
97
+ * @param {string} value The decoded value.
98
+ * @returns {string}
99
+ */
100
+ const sanitizeQueryValue = value => UNSAFE_CHARS_REGEX.test(value) ? stripHtml(value) : value;
101
+
102
+ /**
103
+ * Sanitizes a link which was received from an external source (e.g. deep links or push messages)
104
+ * before it's passed to the router. HTML markup is removed from the path, query and hash to prevent
105
+ * that it's rendered somewhere within the app. Links with a script protocol are rejected.
106
+ * Links without suspicious content are returned unchanged.
107
+ * @param {string} link The link to sanitize.
108
+ * @returns {string} The sanitized link or an empty string when the link was rejected.
109
+ */
110
+ export const sanitizeLink = link => {
111
+ if (typeof link !== 'string' || link === '') {
112
+ return '';
113
+ }
114
+ if (UNSAFE_PROTOCOL_REGEX.test(link)) {
115
+ logger.warn('sanitizeLink: Rejected link with unsafe protocol', link);
116
+ return '';
117
+ }
118
+ const hashIndex = link.indexOf('#');
119
+ const beforeHash = hashIndex === -1 ? link : link.slice(0, hashIndex);
120
+ const hash = hashIndex === -1 ? null : link.slice(hashIndex + 1);
121
+ const queryIndex = beforeHash.indexOf('?');
122
+ const base = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
123
+ const query = queryIndex === -1 ? null : beforeHash.slice(queryIndex + 1);
124
+ let sanitized = base.split('/').map(sanitizeUriComponent).join('/');
125
+ if (query !== null) {
126
+ const params = new URLSearchParams(query);
127
+ const sanitizedParams = new URLSearchParams();
128
+ let modified = false;
129
+ params.forEach((value, key) => {
130
+ const sanitizedKey = sanitizeQueryValue(key);
131
+ const sanitizedValue = sanitizeQueryValue(value);
132
+ modified = modified || sanitizedKey !== key || sanitizedValue !== value;
133
+ sanitizedParams.append(sanitizedKey, sanitizedValue);
134
+ });
135
+ sanitized += `?${modified ? sanitizedParams.toString() : query}`;
136
+ }
137
+ if (hash !== null) {
138
+ sanitized += `#${sanitizeUriComponent(hash)}`;
139
+ }
140
+ if (sanitized !== link) {
141
+ logger.warn('sanitizeLink: Removed unsafe content from link', link);
142
+ }
143
+ return sanitized;
59
144
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopgate/pwa-common",
3
- "version": "7.32.0-beta.9",
3
+ "version": "7.32.1-beta.1",
4
4
  "description": "Common library for the Shopgate Connect PWA.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Shopgate <support@shopgate.com>",
@@ -17,7 +17,7 @@
17
17
  "dependencies": {
18
18
  "@redux-devtools/extension": "^3.3.0",
19
19
  "@sentry/browser": "6.0.1",
20
- "@shopgate/pwa-benchmark": "7.32.0-beta.9",
20
+ "@shopgate/pwa-benchmark": "7.32.1-beta.1",
21
21
  "@virtuous/conductor": "~2.5.0",
22
22
  "@virtuous/react-conductor": "~2.5.0",
23
23
  "@virtuous/redux-persister": "1.1.0-beta.7",
@@ -40,7 +40,7 @@
40
40
  "swiper": "12.2.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@shopgate/pwa-core": "7.32.0-beta.9",
43
+ "@shopgate/pwa-core": "7.32.1-beta.1",
44
44
  "@types/lodash": "^4.17.24",
45
45
  "@types/react-portal": "^3.0.9",
46
46
  "lodash": "^4.17.23",
@@ -21,6 +21,7 @@ import { navigate$, userDidLogin$, appWillStart$, windowOpenOverride$ } from "..
21
21
  import { isUserLoggedIn } from "../selectors/user";
22
22
  import { getIsConnected } from "../selectors/client";
23
23
  import { INDEX_PATH } from "../constants/RoutePaths";
24
+ import { sanitizeLink } from "../helpers/router";
24
25
  import appConfig from "../helpers/config";
25
26
  import authRoutes from "../collections/AuthRoutes";
26
27
  import ToastProvider from "../providers/toast";
@@ -108,6 +109,10 @@ export default function routerSubscriptions(subscribe) {
108
109
  }
109
110
  case HISTORY_RESET_TO:
110
111
  {
112
+ const sanitizedResetToPathname = sanitizeLink(String(resetToPathname || ''));
113
+ if (!sanitizedResetToPathname) {
114
+ return;
115
+ }
111
116
  await router.pop({
112
117
  steps: historyLength - 1,
113
118
  state: routeState,
@@ -115,7 +120,7 @@ export default function routerSubscriptions(subscribe) {
115
120
  emitAfter: false
116
121
  });
117
122
  await router.replace({
118
- pathname: resetToPathname,
123
+ pathname: sanitizedResetToPathname,
119
124
  state: routeState
120
125
  });
121
126
  return;
@@ -124,6 +129,13 @@ export default function routerSubscriptions(subscribe) {
124
129
  break;
125
130
  }
126
131
 
132
+ // Remove HTML markup from the location (e.g. within query parameters of links from CMS content)
133
+ // to prevent that it's rendered within pages or sent within requests. Links with a script
134
+ // protocol are rejected.
135
+ if (location) {
136
+ location = sanitizeLink(String(location));
137
+ }
138
+
127
139
  // Remove trailing slashes from internal links, since they might break the routing mechanism.
128
140
  // External links are treated as valid, since we don't know about the requirements at the
129
141
  // 3rd party server (e.g. google maps links might require trailing slashes).
@@ -331,9 +343,10 @@ export default function routerSubscriptions(subscribe) {
331
343
  * so that the router can decide how to handle the URL.
332
344
  */
333
345
  Linking.addEventListener('windowOpenRequested', event => {
334
- const {
335
- targetUrl
336
- } = event.detail;
346
+ const targetUrl = sanitizeLink(String(event.detail?.targetUrl || ''));
347
+ if (!targetUrl) {
348
+ return;
349
+ }
337
350
  dispatch(historyPush({
338
351
  pathname: targetUrl,
339
352
  state: {}
@@ -371,9 +384,10 @@ export default function routerSubscriptions(subscribe) {
371
384
  action,
372
385
  dispatch
373
386
  }) => {
374
- if (action.pathname) {
387
+ const pathname = sanitizeLink(String(action.pathname || ''));
388
+ if (pathname) {
375
389
  dispatch(historyPush({
376
- pathname: action.pathname
390
+ pathname
377
391
  }));
378
392
  }
379
393
  });
@@ -1,96 +0,0 @@
1
- import { APP_WILL_START, APP_DID_START, PWA_DID_APPEAR, PWA_DID_DISAPPEAR, WILL_REGISTER_LINK_EVENTS, DID_REGISTER_LINK_EVENTS, OPEN_DEEP_LINK, OPEN_PUSH_NOTIFICATION, OPEN_UNIVERSAL_LINK } from "../../constants/ActionTypes";
2
- import { appWillStart, appDidStart, pwaDidAppear, pwaDidDisappear, willRegisterLinkEvents, didRegisterLinkEvents, openDeepLink, openPushNotification, openUniversalLink } from "./index";
3
- const dataMock = {
4
- some: 'data'
5
- };
6
- describe('Action Creators: app', () => {
7
- describe('appWillStart()', () => {
8
- it('should work as expected', () => {
9
- const expected = {
10
- type: APP_WILL_START,
11
- location: dataMock
12
- };
13
- expect(appWillStart(dataMock)).toEqual(expected);
14
- });
15
- });
16
- describe('appDidStart()', () => {
17
- it('should work as expected', () => {
18
- const expected = {
19
- type: APP_DID_START
20
- };
21
- expect(appDidStart()).toEqual(expected);
22
- });
23
- });
24
- describe('pwaDidAppear()', () => {
25
- it('should work as expected', () => {
26
- const expected = {
27
- type: PWA_DID_APPEAR
28
- };
29
- expect(pwaDidAppear()).toEqual(expected);
30
- });
31
- });
32
- describe('pwaDidDisappear()', () => {
33
- it('should work as expected', () => {
34
- const expected = {
35
- type: PWA_DID_DISAPPEAR
36
- };
37
- expect(pwaDidDisappear()).toEqual(expected);
38
- });
39
- });
40
- describe('willRegisterLinkEvents()', () => {
41
- it('should work as expected', () => {
42
- const expected = {
43
- type: WILL_REGISTER_LINK_EVENTS
44
- };
45
- expect(willRegisterLinkEvents()).toEqual(expected);
46
- });
47
- });
48
- describe('didRegisterLinkEvents()', () => {
49
- it('should work as expected', () => {
50
- const expected = {
51
- type: DID_REGISTER_LINK_EVENTS
52
- };
53
- expect(didRegisterLinkEvents()).toEqual(expected);
54
- });
55
- });
56
- describe('openDeepLink()', () => {
57
- it('should work as expected', () => {
58
- const expected = {
59
- type: OPEN_DEEP_LINK,
60
- payload: dataMock
61
- };
62
- expect(openDeepLink(dataMock)).toEqual(expected);
63
- });
64
- });
65
- describe('openPushNotification()', () => {
66
- const notificationId = 'abc123';
67
- const link = '/link/to/somewhere';
68
- it('should work as expected', () => {
69
- const expected = {
70
- type: OPEN_PUSH_NOTIFICATION,
71
- notificationId,
72
- link
73
- };
74
- expect(openPushNotification(notificationId, link)).toEqual(expected);
75
- });
76
- it('should work as expected when the link is empty', () => {
77
- const expected = {
78
- type: OPEN_PUSH_NOTIFICATION,
79
- notificationId,
80
- link: ''
81
- };
82
- expect(openPushNotification(notificationId)).toEqual(expected);
83
- });
84
- });
85
- describe('openUniversalLink()', () => {
86
- const expected = {
87
- type: OPEN_UNIVERSAL_LINK,
88
- payload: {
89
- link: 'https://testshop.shopgate.com/item/313131313132',
90
- wasOpenedFromSearchIndex: true,
91
- linkSerial: 1003
92
- }
93
- };
94
- expect(openUniversalLink(expected.payload)).toEqual(expected);
95
- });
96
- });
@@ -1,44 +0,0 @@
1
- import { requestClientInformation, receiveClientInformation, errorClientInformation, receiveClientConnectivity } from "./index";
2
- import { REQUEST_CLIENT_INFORMATION, RECEIVE_CLIENT_INFORMATION, ERROR_CLIENT_INFORMATION, RECEIVE_CLIENT_CONNECTIVITY } from "../../constants/ActionTypes";
3
- describe('Action Creators: client', () => {
4
- describe('requestClientInformation()', () => {
5
- it('should work as expected', () => {
6
- const expected = {
7
- type: REQUEST_CLIENT_INFORMATION
8
- };
9
- expect(requestClientInformation()).toEqual(expected);
10
- });
11
- });
12
- describe('receiveClientInformation()', () => {
13
- it('should work as expected', () => {
14
- const data = {
15
- some: 'data'
16
- };
17
- const expected = {
18
- type: RECEIVE_CLIENT_INFORMATION,
19
- data
20
- };
21
- expect(receiveClientInformation(data)).toEqual(expected);
22
- });
23
- });
24
- describe('errorClientInformation()', () => {
25
- it('should work as expected', () => {
26
- const expected = {
27
- type: ERROR_CLIENT_INFORMATION
28
- };
29
- expect(errorClientInformation()).toEqual(expected);
30
- });
31
- });
32
- describe('receiveClientConnectivity()', () => {
33
- it('should work as expected', () => {
34
- const data = {
35
- some: 'data'
36
- };
37
- const expected = {
38
- type: RECEIVE_CLIENT_CONNECTIVITY,
39
- data
40
- };
41
- expect(receiveClientConnectivity(data)).toEqual(expected);
42
- });
43
- });
44
- });
@@ -1,37 +0,0 @@
1
- import { REQUEST_MENU, RECEIVE_MENU, ERROR_MENU } from "../../constants/ActionTypes";
2
- import { requestMenu, receiveMenu, errorMenu } from "./index";
3
- const entries = [{
4
- url: '/',
5
- label: 'Page Label'
6
- }];
7
- const id = 'quicklinks';
8
- describe('Action Creators: menu', () => {
9
- describe('requestMenu()', () => {
10
- it('should work as expected', () => {
11
- const expected = {
12
- type: REQUEST_MENU,
13
- id
14
- };
15
- expect(requestMenu(id)).toEqual(expected);
16
- });
17
- });
18
- describe('receiveMenu()', () => {
19
- it('should work as expected', () => {
20
- const expected = {
21
- type: RECEIVE_MENU,
22
- id,
23
- entries
24
- };
25
- expect(receiveMenu(id, entries)).toEqual(expected);
26
- });
27
- });
28
- describe('errorMenu()', () => {
29
- it('should work as expected', () => {
30
- const expected = {
31
- type: ERROR_MENU,
32
- id
33
- };
34
- expect(errorMenu(id)).toEqual(expected);
35
- });
36
- });
37
- });
@@ -1,26 +0,0 @@
1
- import { CREATE_MODAL, REMOVE_MODAL } from "../../constants/ActionTypes";
2
- import { createModal, removeModal } from "./index";
3
- const options = {
4
- some: 'data'
5
- };
6
- const id = 'modalId';
7
- describe('Action Creators: modal', () => {
8
- describe('createModal()', () => {
9
- it('should work as expected', () => {
10
- const expected = {
11
- type: CREATE_MODAL,
12
- options
13
- };
14
- expect(createModal(options)).toEqual(expected);
15
- });
16
- });
17
- describe('removeModal()', () => {
18
- it('should work as expected', () => {
19
- const expected = {
20
- type: REMOVE_MODAL,
21
- id
22
- };
23
- expect(removeModal(id)).toEqual(expected);
24
- });
25
- });
26
- });
@@ -1,38 +0,0 @@
1
- import { REQUEST_PAGE_CONFIG, RECEIVE_PAGE_CONFIG, ERROR_PAGE_CONFIG } from "../../constants/ActionTypes";
2
- import { requestPageConfig, receivePageConfig, errorPageConfig } from "./index";
3
- const pageId = 'index';
4
- const config = {
5
- some: 'data'
6
- };
7
- describe('Action Creators: page', () => {
8
- describe('requestPageConfig()', () => {
9
- it('should work as expected', () => {
10
- const expected = {
11
- type: REQUEST_PAGE_CONFIG,
12
- pageId
13
- };
14
- expect(requestPageConfig(pageId)).toEqual(expected);
15
- });
16
- });
17
- describe('receivePageConfig()', () => {
18
- it('should work as expected', () => {
19
- const expected = {
20
- type: RECEIVE_PAGE_CONFIG,
21
- pageId,
22
- config
23
- };
24
- expect(receivePageConfig(pageId, config)).toEqual(expected);
25
- });
26
- });
27
- describe('errorPageConfig()', () => {
28
- it('should work as expected', () => {
29
- const errorCode = 'error';
30
- const expected = {
31
- type: ERROR_PAGE_CONFIG,
32
- errorCode,
33
- pageId
34
- };
35
- expect(errorPageConfig(pageId, errorCode)).toEqual(expected);
36
- });
37
- });
38
- });
@@ -1,45 +0,0 @@
1
- import { RECEIVE_URL, REQUEST_URL, ERROR_URL } from "../../constants/ActionTypes";
2
- import { receiveUrl, requestUrl, errorUrl } from "./index";
3
- const url = 'https://www.myshop.com/checkout';
4
- const urlType = 'checkout';
5
- const expires = 123456;
6
- describe('Action Creators: url', () => {
7
- describe('receiveUrl()', () => {
8
- it('should work as expected', () => {
9
- const expected = {
10
- type: RECEIVE_URL,
11
- url,
12
- urlType,
13
- expires
14
- };
15
- expect(receiveUrl(urlType, url, expires)).toEqual(expected);
16
- });
17
- it('should work as expected when no expires parameter is passed', () => {
18
- const expected = {
19
- type: RECEIVE_URL,
20
- url,
21
- urlType,
22
- expires: null
23
- };
24
- expect(receiveUrl(urlType, url)).toEqual(expected);
25
- });
26
- });
27
- describe('requestUrl()', () => {
28
- it('should work as expected', () => {
29
- const expected = {
30
- type: REQUEST_URL,
31
- urlType
32
- };
33
- expect(requestUrl(urlType)).toEqual(expected);
34
- });
35
- });
36
- describe('errorUrl()', () => {
37
- it('should work as expected', () => {
38
- const expected = {
39
- type: ERROR_URL,
40
- urlType
41
- };
42
- expect(errorUrl(urlType)).toEqual(expected);
43
- });
44
- });
45
- });