@shopgate/pwa-unit-test 7.31.5-beta.6 → 7.31.6-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.
- package/README.md +59 -2
- package/envSetup.js +25 -36
- package/jest.config.js +33 -41
- package/mocks/appCommand.js +65 -0
- package/mocks/assets.js +7 -0
- package/mocks/i18n.js +24 -0
- package/mocks/styles.js +7 -0
- package/mocks/useTheme.js +18 -0
- package/package.json +6 -2
- package/testSetup.js +67 -22
- package/testUtils/createMockStore.d.ts +40 -0
- package/testUtils/createMockStore.js +59 -0
- package/testUtils/index.d.ts +1 -0
- package/testUtils/index.js +1 -0
package/README.md
CHANGED
|
@@ -66,12 +66,69 @@ const defaultConfig = require('@shopgate/pwa-unit-test/jest.config');
|
|
|
66
66
|
module.exports = {
|
|
67
67
|
...defaultConfig,
|
|
68
68
|
moduleNameMapper: {
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// Keep the default mappings - they would be replaced otherwise
|
|
70
|
+
...defaultConfig.moduleNameMapper,
|
|
71
|
+
'^Components(.*)$': '<rootDir>/components$1',
|
|
72
|
+
'^Styles(.*)$': '<rootDir>/styles$1',
|
|
71
73
|
},
|
|
72
74
|
};
|
|
73
75
|
```
|
|
74
76
|
|
|
77
|
+
__NOTE: Options which hold an object or an array - like `moduleNameMapper`, `setupFiles` or
|
|
78
|
+
`transformIgnorePatterns` - are replaced and not merged when they are set. Spread the value of the
|
|
79
|
+
default configuration first to keep it, as shown above. Without that, the mappings for styles and
|
|
80
|
+
assets are lost, and imports of stylesheets or images within the tested components fail.__
|
|
81
|
+
|
|
82
|
+
## Test utilities
|
|
83
|
+
|
|
84
|
+
### `createMockStore`
|
|
85
|
+
|
|
86
|
+
Creates a minimal Redux store for component tests. It implements the contract that `react-redux`
|
|
87
|
+
relies on, without pulling in the reducers of the application:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import { createMockStore } from '@shopgate/pwa-unit-test/testUtils';
|
|
91
|
+
|
|
92
|
+
const store = createMockStore({ counter: 0 });
|
|
93
|
+
|
|
94
|
+
mount(
|
|
95
|
+
<Provider store={store}>
|
|
96
|
+
<MyComponent />
|
|
97
|
+
</Provider>
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Dispatched actions are recorded, so they can be asserted on. `store.dispatch` is a jest mock:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
expect(store.getActions()).toEqual([{ type: 'INCREMENT' }]);
|
|
105
|
+
expect(store.dispatch).toHaveBeenCalledWith({ type: 'INCREMENT' });
|
|
106
|
+
|
|
107
|
+
store.clearActions();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
__NOTE: `getActions()` reads the recorded calls of the `dispatch` mock, so a `jest.clearAllMocks()`
|
|
111
|
+
- e.g. within a `beforeEach` - also clears the recorded actions. Create the store after that call,
|
|
112
|
+
or use `store.clearActions()` to reset only this store.__
|
|
113
|
+
|
|
114
|
+
By default the state doesn't change when an action is dispatched, which keeps it predictable. Use
|
|
115
|
+
`setState` to simulate a state change - the subscribers are notified, so connected components
|
|
116
|
+
re-render:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
store.setState({ counter: 5 });
|
|
120
|
+
store.setState(current => ({ counter: current.counter + 1 }));
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Pass a reducer as second argument to also apply dispatched actions to the state, e.g. to assert
|
|
124
|
+
what a component renders after it dispatched something:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const store = createMockStore({ counter: 0 }, (state, action) => (
|
|
128
|
+
action.type === 'INCREMENT' ? { counter: state.counter + 1 } : state
|
|
129
|
+
));
|
|
130
|
+
```
|
|
131
|
+
|
|
75
132
|
## About Shopgate
|
|
76
133
|
|
|
77
134
|
Shopgate is the leading mobile commerce platform.
|
package/envSetup.js
CHANGED
|
@@ -10,70 +10,59 @@ expect.extend({
|
|
|
10
10
|
const isFn = fn => typeof fn === 'function';
|
|
11
11
|
|
|
12
12
|
// Prefer Enzyme's API when available (shallow)
|
|
13
|
-
let empty =
|
|
14
|
-
isFn(received.isEmptyRender) ? received.isEmptyRender() : null;
|
|
13
|
+
let empty = isFn(received.isEmptyRender) ? received.isEmptyRender() : null;
|
|
15
14
|
|
|
16
15
|
// Fallback: try to infer from HTML (mount / generic wrappers)
|
|
17
16
|
if (empty === null) {
|
|
18
17
|
const html = isFn(received.html) ? received.html() : undefined;
|
|
19
18
|
empty = html == null || /^\s*$/.test(String(html));
|
|
20
19
|
}
|
|
21
|
-
|
|
22
20
|
const pass = empty === true;
|
|
23
21
|
return {
|
|
24
22
|
pass,
|
|
25
|
-
message: () =>
|
|
26
|
-
(pass
|
|
27
|
-
? 'expected wrapper NOT to be an empty render'
|
|
28
|
-
: 'expected wrapper to be an empty render'),
|
|
23
|
+
message: () => pass ? 'expected wrapper NOT to be an empty render' : 'expected wrapper to be an empty render'
|
|
29
24
|
};
|
|
30
25
|
},
|
|
31
26
|
toExist(received) {
|
|
32
27
|
const pass = typeof received.exists === 'function' ? received.exists() : !!received.length;
|
|
33
28
|
return {
|
|
34
29
|
pass,
|
|
35
|
-
message: () =>
|
|
36
|
-
(pass ? 'expected wrapper NOT to exist' : 'expected wrapper to exist'),
|
|
30
|
+
message: () => pass ? 'expected wrapper NOT to exist' : 'expected wrapper to exist'
|
|
37
31
|
};
|
|
38
|
-
}
|
|
32
|
+
}
|
|
39
33
|
});
|
|
40
|
-
|
|
41
34
|
global.SGEvent = {
|
|
42
|
-
__call: () => {
|
|
35
|
+
__call: () => {}
|
|
43
36
|
};
|
|
44
|
-
|
|
45
37
|
jest.setTimeout(10000);
|
|
46
|
-
|
|
47
38
|
global.mutationConstructorSpy = jest.fn();
|
|
48
39
|
global.mutationObserveSpy = jest.fn();
|
|
49
40
|
global.mutationDisconnectSpy = jest.fn();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
};
|
|
58
|
-
|
|
41
|
+
global.MutationObserver = /*#__PURE__*/function () {
|
|
42
|
+
function _class(callback) {
|
|
43
|
+
global.mutationConstructorSpy(callback);
|
|
44
|
+
}
|
|
45
|
+
var _proto = _class.prototype;
|
|
46
|
+
_proto.observe = function observe(element, initObject) {
|
|
47
|
+
global.mutationObserveSpy(element, initObject);
|
|
48
|
+
};
|
|
49
|
+
_proto.disconnect = function disconnect() {
|
|
50
|
+
global.mutationDisconnectSpy();
|
|
51
|
+
};
|
|
52
|
+
return _class;
|
|
53
|
+
}();
|
|
59
54
|
global.IntersectionObserver = jest.fn((callback, options) => ({
|
|
60
|
-
observe: jest.fn(
|
|
61
|
-
|
|
62
|
-
}),
|
|
63
|
-
unobserve: jest.fn((element) => {
|
|
64
|
-
|
|
65
|
-
}),
|
|
66
|
-
disconnect: jest.fn(() => {
|
|
67
|
-
|
|
68
|
-
}),
|
|
55
|
+
observe: jest.fn(element => {}),
|
|
56
|
+
unobserve: jest.fn(element => {}),
|
|
57
|
+
disconnect: jest.fn(() => {}),
|
|
69
58
|
takeRecords: jest.fn(() => []),
|
|
70
|
-
trigger:
|
|
59
|
+
trigger: entries => {
|
|
71
60
|
callback(entries, {
|
|
72
61
|
disconnect: jest.fn(),
|
|
73
|
-
unobserve: jest.fn()
|
|
62
|
+
unobserve: jest.fn()
|
|
74
63
|
});
|
|
75
|
-
}
|
|
64
|
+
}
|
|
76
65
|
}));
|
|
77
66
|
|
|
78
67
|
/* eslint-enable require-jsdoc, extra-rules/potential-point-free, class-methods-use-this, no-unused-vars */
|
|
79
|
-
/* eslint-enable max-len */
|
|
68
|
+
/* eslint-enable max-len */
|
package/jest.config.js
CHANGED
|
@@ -4,13 +4,21 @@ const babelJest = require.resolve('babel-jest');
|
|
|
4
4
|
const babelOptions = {
|
|
5
5
|
babelrc: false,
|
|
6
6
|
configFile: false,
|
|
7
|
-
presets: [
|
|
8
|
-
require.resolve('@babel/preset-env'),
|
|
9
|
-
require.resolve('@babel/preset-react'),
|
|
10
|
-
require.resolve('@babel/preset-typescript'),
|
|
11
|
-
],
|
|
7
|
+
presets: [require.resolve('@babel/preset-env'), require.resolve('@babel/preset-react'), require.resolve('@babel/preset-typescript')]
|
|
12
8
|
};
|
|
13
9
|
const babelTransform = [babelJest, babelOptions];
|
|
10
|
+
const stylesMock = require.resolve("./mocks/styles.js");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Swiper exposes its React build via the "exports" field of its package.json, which jest can't
|
|
14
|
+
* resolve. Mapping it to the concrete file fixes that. Resolving it from this package works for
|
|
15
|
+
* consumers as well, and only falls back to the rootDir of the consumer when swiper is installed
|
|
16
|
+
* somewhere this package can't reach.
|
|
17
|
+
*/
|
|
18
|
+
let swiperReact = '<rootDir>/node_modules/swiper/swiper-react.mjs';
|
|
19
|
+
try {
|
|
20
|
+
swiperReact = require.resolve('swiper/react');
|
|
21
|
+
} catch (e) {/* swiper is not installed */}
|
|
14
22
|
|
|
15
23
|
/** @type {import('jest').Config} */
|
|
16
24
|
module.exports = {
|
|
@@ -19,46 +27,30 @@ module.exports = {
|
|
|
19
27
|
moduleFileExtensions: ['js', 'jsx', 'json', 'mjs', 'ts', 'tsx'],
|
|
20
28
|
moduleNameMapper: {
|
|
21
29
|
// Mock styles since they are not needed for unit tests
|
|
22
|
-
'\\.(css|sass)$':
|
|
23
|
-
//
|
|
24
|
-
'
|
|
30
|
+
'\\.(css|sass|scss|less)$': stylesMock,
|
|
31
|
+
// Mock assets since they are not needed for unit tests
|
|
32
|
+
'\\.(jpg|jpeg|png|gif|webp|svg|ico|eot|otf|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': require.resolve("./mocks/assets.js"),
|
|
33
|
+
'^swiper/react$': swiperReact,
|
|
34
|
+
// Mock Swiper styles - they are imported without a file extension
|
|
35
|
+
'^swiper/css(?:/.*)?$': stylesMock
|
|
25
36
|
},
|
|
26
37
|
transform: {
|
|
27
38
|
'^.+\\.jsx?$': babelTransform,
|
|
28
39
|
'^.+\\.mjs$': babelTransform,
|
|
29
|
-
'^.+\\.[jt]sx?$': babelTransform
|
|
40
|
+
'^.+\\.[jt]sx?$': babelTransform
|
|
30
41
|
},
|
|
31
|
-
snapshotSerializers: [
|
|
32
|
-
'enzyme-to-json/serializer',
|
|
33
|
-
],
|
|
42
|
+
snapshotSerializers: ['enzyme-to-json/serializer'],
|
|
34
43
|
testRegex: '(/__tests__/.*|(\\.|/)spec)\\.(js|jsx|ts|tsx)?$',
|
|
35
|
-
testPathIgnorePatterns: [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
],
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
],
|
|
44
|
-
unmockedModulePathPatterns: [
|
|
45
|
-
'node_modules/react/',
|
|
46
|
-
'node_modules/enzyme/',
|
|
47
|
-
],
|
|
48
|
-
collectCoverageFrom: [
|
|
49
|
-
'**/*.{js,jsx,ts,tsx}',
|
|
50
|
-
'!**/node_modules/**',
|
|
51
|
-
'!**/coverage/**',
|
|
52
|
-
'!.eslintrc.js',
|
|
53
|
-
'!**/jest.config.js',
|
|
54
|
-
],
|
|
55
|
-
setupFiles: [
|
|
56
|
-
'@shopgate/pwa-unit-test/testSetup.js',
|
|
57
|
-
],
|
|
58
|
-
setupFilesAfterEnv: [
|
|
59
|
-
'@shopgate/pwa-unit-test/envSetup.js',
|
|
60
|
-
],
|
|
44
|
+
testPathIgnorePatterns: ['/node_modules/', '/dist/', '/coverage/', '/config/'],
|
|
45
|
+
// @shopgate packages are published untranspiled, so they need to be transformed as well. Within
|
|
46
|
+
// this repository they are symlinked to their sources outside of node_modules, which is why the
|
|
47
|
+
// missing entry didn't surface here, but only within extensions which install them for real.
|
|
48
|
+
transformIgnorePatterns: ['node_modules/(?!(@shopgate|swiper|dom7|intl-messageformat|@formatjs|tslib)/)'],
|
|
49
|
+
unmockedModulePathPatterns: ['node_modules/react/', 'node_modules/enzyme/'],
|
|
50
|
+
collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/node_modules/**', '!**/coverage/**', '!.eslintrc.js', '!**/jest.config.js'],
|
|
51
|
+
setupFiles: ['@shopgate/pwa-unit-test/testSetup.js'],
|
|
52
|
+
setupFilesAfterEnv: ['@shopgate/pwa-unit-test/envSetup.js'],
|
|
61
53
|
testEnvironmentOptions: {
|
|
62
|
-
url: 'http://localhost'
|
|
63
|
-
}
|
|
64
|
-
};
|
|
54
|
+
url: 'http://localhost'
|
|
55
|
+
}
|
|
56
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for @shopgate/pwa-core/classes/AppCommand.
|
|
3
|
+
*
|
|
4
|
+
* The published packages don't contain their __mocks__ folders, so consumers like extensions can't
|
|
5
|
+
* rely on the manual mock of the package itself. Without a chainable mock the app commands that
|
|
6
|
+
* are dispatched on module level - e.g. by the Scanner class - throw as soon as a component from
|
|
7
|
+
* @shopgate/engage is imported.
|
|
8
|
+
*/
|
|
9
|
+
let dispatchError = false;
|
|
10
|
+
const mockedSetCommandName = jest.fn(name => name);
|
|
11
|
+
const mockedSetCommandParams = jest.fn(params => params);
|
|
12
|
+
const mockedSetLibVersion = jest.fn(version => version);
|
|
13
|
+
const mockedBuildCommand = jest.fn(command => command);
|
|
14
|
+
const mockedDispatch = jest.fn(params => params);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Causes that the dispatch method resolves with FALSE.
|
|
18
|
+
* @param {boolean} value Trigger an error or not.
|
|
19
|
+
*/
|
|
20
|
+
const triggerDispatchError = (value = true) => {
|
|
21
|
+
dispatchError = value;
|
|
22
|
+
};
|
|
23
|
+
const mockedAppCommand = jest.fn().mockImplementation(function MockedAppCommand() {
|
|
24
|
+
this.name = '';
|
|
25
|
+
this.params = null;
|
|
26
|
+
this.libVersion = '9.0';
|
|
27
|
+
this.setCommandName = name => {
|
|
28
|
+
this.name = mockedSetCommandName(name);
|
|
29
|
+
return this;
|
|
30
|
+
};
|
|
31
|
+
this.setCommandParams = params => {
|
|
32
|
+
this.params = mockedSetCommandParams(params);
|
|
33
|
+
return this;
|
|
34
|
+
};
|
|
35
|
+
this.setLibVersion = version => {
|
|
36
|
+
this.libVersion = mockedSetLibVersion(version);
|
|
37
|
+
return this;
|
|
38
|
+
};
|
|
39
|
+
this.buildCommand = () => {
|
|
40
|
+
const command = this.name ? {
|
|
41
|
+
c: this.name,
|
|
42
|
+
...(this.params && {
|
|
43
|
+
p: this.params
|
|
44
|
+
})
|
|
45
|
+
} : null;
|
|
46
|
+
return mockedBuildCommand(command);
|
|
47
|
+
};
|
|
48
|
+
this.dispatch = params => {
|
|
49
|
+
mockedDispatch(params);
|
|
50
|
+
const success = !dispatchError;
|
|
51
|
+
dispatchError = false;
|
|
52
|
+
return Promise.resolve(success);
|
|
53
|
+
};
|
|
54
|
+
return this;
|
|
55
|
+
});
|
|
56
|
+
module.exports = {
|
|
57
|
+
__esModule: true,
|
|
58
|
+
default: mockedAppCommand,
|
|
59
|
+
mockedSetCommandName,
|
|
60
|
+
mockedSetCommandParams,
|
|
61
|
+
mockedSetLibVersion,
|
|
62
|
+
mockedBuildCommand,
|
|
63
|
+
mockedDispatch,
|
|
64
|
+
triggerDispatchError
|
|
65
|
+
};
|
package/mocks/assets.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replacement for asset imports (images, fonts, media) which are irrelevant for unit tests.
|
|
3
|
+
*
|
|
4
|
+
* Written in CommonJS on purpose, so that the mapping also works for consumers which replaced the
|
|
5
|
+
* transformIgnorePatterns of this package with ones that don't transform @shopgate packages.
|
|
6
|
+
*/
|
|
7
|
+
module.exports = '';
|
package/mocks/i18n.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for @shopgate/engage/core/helpers/i18n.
|
|
3
|
+
*
|
|
4
|
+
* The published packages don't contain their __mocks__ folders, so consumers like extensions can't
|
|
5
|
+
* rely on the manual mock of the package itself. Without it the translation helpers are not
|
|
6
|
+
* initialized, so components which render a translated string don't work.
|
|
7
|
+
*/
|
|
8
|
+
const i18n = {
|
|
9
|
+
ready: true,
|
|
10
|
+
init: () => {},
|
|
11
|
+
text: input => input || '',
|
|
12
|
+
price: () => 'p',
|
|
13
|
+
number: () => 'n',
|
|
14
|
+
date: () => 'd',
|
|
15
|
+
time: () => 't',
|
|
16
|
+
getLang: () => 'de-DE',
|
|
17
|
+
getPath: path => path,
|
|
18
|
+
has: path => typeof path === 'string' && /^\S+\.\S+/.test(path)
|
|
19
|
+
};
|
|
20
|
+
module.exports = {
|
|
21
|
+
__esModule: true,
|
|
22
|
+
i18n,
|
|
23
|
+
getWeekDaysOrder: () => ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
|
|
24
|
+
};
|
package/mocks/styles.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replacement for style imports which are irrelevant for unit tests.
|
|
3
|
+
*
|
|
4
|
+
* Written in CommonJS on purpose, so that the mapping also works for consumers which replaced the
|
|
5
|
+
* transformIgnorePatterns of this package with ones that don't transform @shopgate packages.
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock for @shopgate/engage/styles/theme/hooks/useTheme.
|
|
3
|
+
*
|
|
4
|
+
* The published packages don't contain their __mocks__ folders, so consumers like extensions can't
|
|
5
|
+
* rely on the manual mock of the package itself. Without a theme object the styles of components
|
|
6
|
+
* from @shopgate/pwa-ui-shared throw when they access theme values.
|
|
7
|
+
*
|
|
8
|
+
* The theme is created with the published createTheme helper, so this mock doesn't need to
|
|
9
|
+
* duplicate any theme defaults.
|
|
10
|
+
*/
|
|
11
|
+
const {
|
|
12
|
+
createTheme
|
|
13
|
+
} = require('@shopgate/engage/styles/theme/createTheme');
|
|
14
|
+
const theme = createTheme({});
|
|
15
|
+
module.exports = {
|
|
16
|
+
__esModule: true,
|
|
17
|
+
default: () => theme
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopgate/pwa-unit-test",
|
|
3
|
-
"version": "7.31.
|
|
3
|
+
"version": "7.31.6-beta.1",
|
|
4
4
|
"description": "The unit tests setup for Shopgate's ENGAGE.",
|
|
5
5
|
"author": "Shopgate <support@shopgate.com>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,9 +22,13 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/enzyme": "^3.10.19",
|
|
25
|
+
"@types/jest": "^29.5.14",
|
|
26
|
+
"@types/react": "^17.0.83",
|
|
25
27
|
"jest": "^29.7.0",
|
|
26
28
|
"react": "^17.0.2",
|
|
27
|
-
"react-dom": "^17.0.2"
|
|
29
|
+
"react-dom": "^17.0.2",
|
|
30
|
+
"react-redux": "^8.1.3",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
28
32
|
},
|
|
29
33
|
"peerDependencies": {
|
|
30
34
|
"jest": "^29.7.0",
|
package/testSetup.js
CHANGED
|
@@ -1,13 +1,73 @@
|
|
|
1
1
|
const Enzyme = require('enzyme');
|
|
2
2
|
const Adapter = require('@wojtekmaj/enzyme-adapter-react-17');
|
|
3
|
+
Enzyme.configure({
|
|
4
|
+
adapter: new Adapter()
|
|
5
|
+
});
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Checks whether a module can be resolved. Consumers of this package don't necessarily have all
|
|
9
|
+
* of the mocked packages installed, and jest.mock() resolves the module path even when a factory
|
|
10
|
+
* is passed, so mocking an absent module would break the whole setup.
|
|
11
|
+
* @param {string} modulePath Path of the module.
|
|
12
|
+
* @returns {boolean} Whether the module can be resolved.
|
|
13
|
+
*/
|
|
14
|
+
const canResolve = modulePath => {
|
|
15
|
+
try {
|
|
16
|
+
require.resolve(modulePath);
|
|
17
|
+
return true;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
5
22
|
|
|
6
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The mocks below are provided by this package instead of relying on the manual __mocks__ folders
|
|
25
|
+
* of the mocked packages. Those folders are stripped from the published packages, so consumers
|
|
26
|
+
* like extensions would silently fall back to automocks, which breaks as soon as a component from
|
|
27
|
+
* @shopgate/engage is imported.
|
|
28
|
+
*
|
|
29
|
+
* The mock factories have to require their module lazily and inline, which is enforced by
|
|
30
|
+
* babel-plugin-jest-hoist.
|
|
31
|
+
*/
|
|
32
|
+
/* eslint-disable global-require */
|
|
33
|
+
|
|
34
|
+
// Mock the translation helpers, since they are not initialized during test runs
|
|
35
|
+
if (canResolve('@shopgate/engage/core/helpers/i18n')) {
|
|
36
|
+
jest.mock('@shopgate/engage/core/helpers/i18n', () => require("./mocks/i18n"));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Mock the AppCommand class by default to avoid log spamming during test runs
|
|
40
|
+
if (canResolve('@shopgate/pwa-core/classes/AppCommand')) {
|
|
41
|
+
jest.mock('@shopgate/pwa-core/classes/AppCommand', () => require("./mocks/appCommand"));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Mock the useTheme hook to prevent failing tests due to missing theme object
|
|
45
|
+
if (canResolve('@shopgate/engage/styles/theme/hooks/useTheme')) {
|
|
46
|
+
jest.mock('@shopgate/engage/styles/theme/hooks/useTheme', () => require("./mocks/useTheme"));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Mock the media provider styles to prevent failing tests
|
|
50
|
+
if (canResolve('@shopgate/pwa-common/collections/media-providers/style')) {
|
|
51
|
+
jest.mock('@shopgate/pwa-common/collections/media-providers/style', () => ({
|
|
52
|
+
responsiveContainer: 'responsiveContainer',
|
|
53
|
+
consentContainer: 'consentContainer',
|
|
54
|
+
consentLink: 'consentLink',
|
|
55
|
+
consentIcon: 'consentIcon'
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The themes are not installed for consumers like extensions. They can't be mocked virtually,
|
|
60
|
+
// since a virtual mock is only applied to imports which use this exact module path.
|
|
61
|
+
if (canResolve('@shopgate/theme-gmd/extensions/reducers')) {
|
|
62
|
+
jest.mock('@shopgate/theme-gmd/extensions/reducers', () => null);
|
|
63
|
+
}
|
|
64
|
+
if (canResolve('@shopgate/theme-ios11/extensions/reducers')) {
|
|
65
|
+
jest.mock('@shopgate/theme-ios11/extensions/reducers', () => null);
|
|
66
|
+
}
|
|
67
|
+
/* eslint-enable global-require */
|
|
7
68
|
|
|
8
69
|
const localStorageMock = (() => {
|
|
9
70
|
let store = {};
|
|
10
|
-
|
|
11
71
|
return {
|
|
12
72
|
getItem(key) {
|
|
13
73
|
return store[key] || null;
|
|
@@ -17,24 +77,9 @@ const localStorageMock = (() => {
|
|
|
17
77
|
},
|
|
18
78
|
clear() {
|
|
19
79
|
store = {};
|
|
20
|
-
}
|
|
80
|
+
}
|
|
21
81
|
};
|
|
22
82
|
})();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Mock the AppCommand class by default to avoid log spamming during test runs
|
|
27
|
-
jest.mock('@shopgate/pwa-core/classes/AppCommand');
|
|
28
|
-
jest.mock('@shopgate/theme-gmd/extensions/reducers', () => null);
|
|
29
|
-
jest.mock('@shopgate/theme-ios11/extensions/reducers', () => null);
|
|
30
|
-
|
|
31
|
-
// Mock the media provider styles to prevent failing tests
|
|
32
|
-
jest.mock('@shopgate/pwa-common/collections/media-providers/style', () => ({
|
|
33
|
-
responsiveContainer: 'responsiveContainer',
|
|
34
|
-
consentContainer: 'consentContainer',
|
|
35
|
-
consentLink: 'consentLink',
|
|
36
|
-
consentIcon: 'consentIcon',
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
// Mock the useTheme hook to prevent failing tests due to missing theme object
|
|
40
|
-
jest.mock('@shopgate/engage/styles/theme/hooks/useTheme');
|
|
83
|
+
Object.defineProperty(window, 'localStorage', {
|
|
84
|
+
value: localStorageMock
|
|
85
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Redux store for component tests.
|
|
3
|
+
*
|
|
4
|
+
* This module is only meant to be imported from test files, since it uses the jest globals.
|
|
5
|
+
*/
|
|
6
|
+
type Listener = () => void;
|
|
7
|
+
/** An action which was dispatched to the store. */
|
|
8
|
+
export interface MockAction {
|
|
9
|
+
type: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Reducer which is applied to the dispatched actions. */
|
|
13
|
+
export type MockReducer<S> = (state: S, action: MockAction) => S;
|
|
14
|
+
export interface MockStore<S> {
|
|
15
|
+
/** Returns the current state. */
|
|
16
|
+
getState: () => S;
|
|
17
|
+
/** Records every dispatched action. Assert on it like on any other jest mock. */
|
|
18
|
+
dispatch: jest.Mock<MockAction, [MockAction]>;
|
|
19
|
+
/** Registers a listener which is invoked whenever the state changed. Returns an unsubscribe. */
|
|
20
|
+
subscribe: (listener: Listener) => () => void;
|
|
21
|
+
/** Replaces the state and notifies the subscribers, so connected components re-render. */
|
|
22
|
+
setState: (next: S | ((current: S) => S)) => void;
|
|
23
|
+
/** Returns the dispatched actions in the order they were dispatched. */
|
|
24
|
+
getActions: () => MockAction[];
|
|
25
|
+
/** Forgets all recorded actions. */
|
|
26
|
+
clearActions: () => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a Redux store for component tests. It implements the contract that react-redux relies on
|
|
30
|
+
* - getState, dispatch and subscribe - without pulling in the reducers of the application.
|
|
31
|
+
*
|
|
32
|
+
* By default dispatched actions are only recorded, which keeps the state predictable while
|
|
33
|
+
* asserting that a component dispatches the right action. Pass a reducer to also apply the
|
|
34
|
+
* dispatched actions to the state, e.g. to assert what a component renders afterwards.
|
|
35
|
+
* @param initialState The state the store starts with.
|
|
36
|
+
* @param reducer Optional reducer which is applied to dispatched actions.
|
|
37
|
+
* @returns The mocked store.
|
|
38
|
+
*/
|
|
39
|
+
export declare const createMockStore: <S>(initialState: S, reducer?: MockReducer<S>) => MockStore<S>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Redux store for component tests.
|
|
3
|
+
*
|
|
4
|
+
* This module is only meant to be imported from test files, since it uses the jest globals.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** An action which was dispatched to the store. */
|
|
8
|
+
|
|
9
|
+
/** Reducer which is applied to the dispatched actions. */
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a Redux store for component tests. It implements the contract that react-redux relies on
|
|
13
|
+
* - getState, dispatch and subscribe - without pulling in the reducers of the application.
|
|
14
|
+
*
|
|
15
|
+
* By default dispatched actions are only recorded, which keeps the state predictable while
|
|
16
|
+
* asserting that a component dispatches the right action. Pass a reducer to also apply the
|
|
17
|
+
* dispatched actions to the state, e.g. to assert what a component renders afterwards.
|
|
18
|
+
* @param initialState The state the store starts with.
|
|
19
|
+
* @param reducer Optional reducer which is applied to dispatched actions.
|
|
20
|
+
* @returns The mocked store.
|
|
21
|
+
*/
|
|
22
|
+
export const createMockStore = (initialState, reducer) => {
|
|
23
|
+
let state = initialState;
|
|
24
|
+
const listeners = new Set();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Notifies all subscribers about a state change.
|
|
28
|
+
*/
|
|
29
|
+
const notify = () => {
|
|
30
|
+
listeners.forEach(listener => {
|
|
31
|
+
listener();
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const dispatch = jest.fn(action => {
|
|
35
|
+
if (reducer) {
|
|
36
|
+
state = reducer(state, action);
|
|
37
|
+
notify();
|
|
38
|
+
}
|
|
39
|
+
return action;
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
getState: () => state,
|
|
43
|
+
dispatch,
|
|
44
|
+
subscribe: listener => {
|
|
45
|
+
listeners.add(listener);
|
|
46
|
+
return () => {
|
|
47
|
+
listeners.delete(listener);
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
setState: next => {
|
|
51
|
+
state = typeof next === 'function' ? next(state) : next;
|
|
52
|
+
notify();
|
|
53
|
+
},
|
|
54
|
+
getActions: () => dispatch.mock.calls.map(([action]) => action),
|
|
55
|
+
clearActions: () => {
|
|
56
|
+
dispatch.mockClear();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './createMockStore';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./createMockStore";
|