jest-expo 58.0.6 → 58.0.7
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/config/getPlatformPreset.js +32 -26
- package/config/withWatchPlugins.js +7 -3
- package/jest-preset.js +21 -2
- package/package.json +12 -8
- package/rsc/jest-preset.js +1 -4
- package/src/preset/nativeEnvironment.js +58 -0
|
@@ -5,11 +5,15 @@ const { withWatchPlugins } = require('./withWatchPlugins');
|
|
|
5
5
|
const expoPreset = require('../jest-preset');
|
|
6
6
|
const { resolveBabelOptions } = require('../src/resolveBabelOptions');
|
|
7
7
|
|
|
8
|
+
const babelJestPath = require.resolve('babel-jest');
|
|
9
|
+
|
|
10
|
+
function isBabelJest(transformer) {
|
|
11
|
+
return transformer === 'babel-jest' || transformer === babelJestPath;
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
function getUpstreamBabelJest(transform) {
|
|
9
15
|
const upstreamBabelJest = Object.keys(transform).find((key) =>
|
|
10
|
-
Array.isArray(transform[key])
|
|
11
|
-
? transform[key][0] === 'babel-jest'
|
|
12
|
-
: transform[key] === 'babel-jest'
|
|
16
|
+
Array.isArray(transform[key]) ? isBabelJest(transform[key][0]) : isBabelJest(transform[key])
|
|
13
17
|
);
|
|
14
18
|
return upstreamBabelJest;
|
|
15
19
|
}
|
|
@@ -51,7 +55,7 @@ function getPlatformPreset(displayOptions, extensions, platform, { isServer, isR
|
|
|
51
55
|
transform: {
|
|
52
56
|
...expoPreset.transform,
|
|
53
57
|
[upstreamBabelJest]: [
|
|
54
|
-
|
|
58
|
+
babelJestPath,
|
|
55
59
|
{
|
|
56
60
|
...babelJestOptions,
|
|
57
61
|
caller: {
|
|
@@ -103,7 +107,8 @@ function getPlatformPreset(displayOptions, extensions, platform, { isServer, isR
|
|
|
103
107
|
// Source exports can contain TypeScript files that use explicit `.js`
|
|
104
108
|
// extensions for runtime ESM compatibility.
|
|
105
109
|
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
106
|
-
|
|
110
|
+
// See the note in `../jest-preset.js`: mapped targets must be absolute paths on Jest 30.
|
|
111
|
+
'^react-native/asset-registry$': expoPreset.moduleNameMapper['^react-native/asset-registry$'],
|
|
107
112
|
...preset.moduleNameMapper,
|
|
108
113
|
};
|
|
109
114
|
|
|
@@ -151,16 +156,17 @@ function getBaseWebPreset() {
|
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
// `getPlatformPreset` builds its own `moduleNameMapper` from scratch, so spreading it over the
|
|
154
|
-
// base preset drops the mappers that the
|
|
155
|
-
// 0.87
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
+
// base preset drops the mappers that the base preset defines. Those mappers are required: since
|
|
160
|
+
// React Native 0.87 `jest/setup.js` mocks `react-native/setup-env`, which is only reachable
|
|
161
|
+
// through the `^react-native/setup-env$` alias, and `withTypescriptMapping` adds the aliases from
|
|
162
|
+
// the project's `tsconfig.json` `paths`. Merge them back for every platform preset, keeping the
|
|
163
|
+
// platform mappers last so platform-specific entries still win.
|
|
164
|
+
function withBaseModuleNameMapper(basePreset, platformPreset) {
|
|
159
165
|
return {
|
|
160
|
-
...
|
|
166
|
+
...basePreset,
|
|
161
167
|
...platformPreset,
|
|
162
168
|
moduleNameMapper: {
|
|
163
|
-
...
|
|
169
|
+
...basePreset.moduleNameMapper,
|
|
164
170
|
...platformPreset.moduleNameMapper,
|
|
165
171
|
},
|
|
166
172
|
};
|
|
@@ -168,32 +174,32 @@ function withNativeModuleNameMapper(platformPreset) {
|
|
|
168
174
|
|
|
169
175
|
module.exports = {
|
|
170
176
|
getWebPreset({ isReactServer } = {}) {
|
|
171
|
-
|
|
172
|
-
...getBaseWebPreset(),
|
|
173
|
-
|
|
174
|
-
...getPlatformPreset({ name: 'Web', color: 'magenta' }, ['web'], 'web', {
|
|
177
|
+
return withBaseModuleNameMapper(
|
|
178
|
+
{ ...getBaseWebPreset(), testEnvironment: 'jsdom' },
|
|
179
|
+
getPlatformPreset({ name: 'Web', color: 'magenta' }, ['web'], 'web', {
|
|
175
180
|
isReactServer,
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
return preset;
|
|
181
|
+
})
|
|
182
|
+
);
|
|
179
183
|
},
|
|
180
184
|
getNodePreset() {
|
|
181
|
-
return
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
return withBaseModuleNameMapper(
|
|
186
|
+
getBaseWebPreset(),
|
|
187
|
+
getPlatformPreset({ name: 'Node', color: 'cyan' }, ['node', 'web'], 'web', {
|
|
184
188
|
isServer: true,
|
|
185
|
-
})
|
|
186
|
-
|
|
189
|
+
})
|
|
190
|
+
);
|
|
187
191
|
},
|
|
188
192
|
getIOSPreset({ isReactServer } = {}) {
|
|
189
|
-
return
|
|
193
|
+
return withBaseModuleNameMapper(
|
|
194
|
+
expoPreset,
|
|
190
195
|
getPlatformPreset({ name: 'iOS', color: 'white' }, ['ios', 'native'], 'ios', {
|
|
191
196
|
isReactServer,
|
|
192
197
|
})
|
|
193
198
|
);
|
|
194
199
|
},
|
|
195
200
|
getAndroidPreset({ isReactServer } = {}) {
|
|
196
|
-
return
|
|
201
|
+
return withBaseModuleNameMapper(
|
|
202
|
+
expoPreset,
|
|
197
203
|
getPlatformPreset(
|
|
198
204
|
{ name: 'Android', color: 'blueBright' },
|
|
199
205
|
['android', 'native'],
|
|
@@ -24,9 +24,13 @@ function withWatchPlugins({ watchPlugins = [], ...config }) {
|
|
|
24
24
|
if (Array.isArray(config.projects)) {
|
|
25
25
|
let projectsWantPass = false;
|
|
26
26
|
config.projects = config.projects.map((project) => {
|
|
27
|
-
if (project && typeof project === 'object'
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (project && typeof project === 'object') {
|
|
28
|
+
// `watchPlugins` and `passWithNoTests` are root-only options in multi-project mode.
|
|
29
|
+
// Strip them from each project to avoid Jest's validation warnings.
|
|
30
|
+
const { watchPlugins, passWithNoTests, ...rest } = project;
|
|
31
|
+
if ('passWithNoTests' in project) {
|
|
32
|
+
projectsWantPass ||= passWithNoTests;
|
|
33
|
+
}
|
|
30
34
|
return rest;
|
|
31
35
|
}
|
|
32
36
|
return project;
|
package/jest-preset.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const cloneDeep = require('lodash/cloneDeep');
|
|
4
4
|
const isEqual = require('lodash/isEqual');
|
|
5
|
+
const path = require('node:path');
|
|
5
6
|
|
|
6
7
|
let jestPreset;
|
|
7
8
|
try {
|
|
@@ -23,12 +24,26 @@ try {
|
|
|
23
24
|
|
|
24
25
|
jestPreset = cloneDeep(jestPreset);
|
|
25
26
|
|
|
27
|
+
// Use jest-expo's own copy of the React Native test environment. The upstream preset resolves
|
|
28
|
+
// `jest-environment-node` from its own (Jest 29) dependencies, which must not be mixed into a Jest 30
|
|
29
|
+
// runtime. The environment is otherwise identical to `@react-native/jest-preset/jest/react-native-env`.
|
|
30
|
+
jestPreset.testEnvironment = require.resolve('./src/preset/nativeEnvironment.js');
|
|
31
|
+
|
|
26
32
|
const { withTypescriptMapping } = require('./src/preset/withTypescriptMapping');
|
|
27
33
|
const { resolveBabelOptions } = require('./src/resolveBabelOptions');
|
|
28
34
|
|
|
35
|
+
// NOTE: Jest 30 replaced its resolver with `unrs-resolver` and dropped the `packageFilter` option,
|
|
36
|
+
// so `@react-native/jest-preset`'s resolver can no longer strip `react-native`'s `exports` map.
|
|
37
|
+
// Mapped module targets must be absolute paths, since bare `react-native/src/*` subpaths are
|
|
38
|
+
// not listed in `exports`.
|
|
39
|
+
const reactNativeAssetRegistry = path.join(
|
|
40
|
+
path.dirname(require.resolve('react-native/package.json')),
|
|
41
|
+
'src/asset-registry.js'
|
|
42
|
+
);
|
|
43
|
+
|
|
29
44
|
// Emulate the alias behavior of Expo's Metro resolver.
|
|
30
45
|
jestPreset.moduleNameMapper = {
|
|
31
|
-
'^react-native/asset-registry$':
|
|
46
|
+
'^react-native/asset-registry$': reactNativeAssetRegistry,
|
|
32
47
|
...(jestPreset.moduleNameMapper || {}),
|
|
33
48
|
'^react-native-vector-icons$': '@expo/vector-icons',
|
|
34
49
|
'^react-native-vector-icons/(.*)': '@expo/vector-icons/$1',
|
|
@@ -42,8 +57,12 @@ if (upstreamBabelJest) {
|
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
// transform
|
|
60
|
+
// Resolve `babel-jest` from jest-expo so the Jest 30 version is used. `@react-native/jest-preset`
|
|
61
|
+
// still depends on `babel-jest@29`, and a bare `'babel-jest'` would resolve from the project root,
|
|
62
|
+
// where hoisting decides which of the two copies wins.
|
|
63
|
+
const babelJestPath = require.resolve('babel-jest');
|
|
45
64
|
const babelOpts = resolveBabelOptions(process.cwd());
|
|
46
|
-
jestPreset.transform['\\.[jt]sx?$'] = [
|
|
65
|
+
jestPreset.transform['\\.[jt]sx?$'] = [babelJestPath, babelOpts];
|
|
47
66
|
|
|
48
67
|
/* Update this when metro changes their default extensions */
|
|
49
68
|
const defaultMetroAssetExts = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jest-expo",
|
|
3
|
-
"version": "58.0.
|
|
3
|
+
"version": "58.0.7",
|
|
4
4
|
"description": "A Jest preset to painlessly test your Expo / React Native apps.",
|
|
5
5
|
"homepage": "https://github.com/expo/expo/tree/main/packages/jest-expo",
|
|
6
6
|
"bugs": {
|
|
@@ -30,27 +30,31 @@
|
|
|
30
30
|
"main": "src/index.js",
|
|
31
31
|
"types": "src/index.d.ts",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@jest/create-cache-key-function": "^
|
|
34
|
-
"@jest/globals": "^
|
|
35
|
-
"babel-jest": "^
|
|
36
|
-
"jest-environment-jsdom": "^
|
|
37
|
-
"jest-
|
|
33
|
+
"@jest/create-cache-key-function": "^30.0.0",
|
|
34
|
+
"@jest/globals": "^30.0.0",
|
|
35
|
+
"babel-jest": "^30.0.0",
|
|
36
|
+
"jest-environment-jsdom": "^30.0.0",
|
|
37
|
+
"jest-environment-node": "^30.0.0",
|
|
38
|
+
"jest-mock": "^30.0.0",
|
|
39
|
+
"jest-snapshot": "^30.0.0",
|
|
38
40
|
"jest-watch-select-projects": "^2.0.0",
|
|
39
|
-
"jest-watch-typeahead": "
|
|
41
|
+
"jest-watch-typeahead": "^3.0.1",
|
|
40
42
|
"json5": "^2.2.3",
|
|
41
|
-
"lodash": "^4.
|
|
43
|
+
"lodash": "^4.18.1",
|
|
42
44
|
"server-only": "^0.0.1",
|
|
43
45
|
"stacktrace-js": "^2.0.2",
|
|
44
46
|
"test-renderer": "1.3.0"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@react-native/jest-preset": "0.88.0-rc.3",
|
|
50
|
+
"@types/node": "^22.14.0",
|
|
48
51
|
"@types/react": "~19.3.0",
|
|
49
52
|
"react-server-dom-webpack": "~19.0.8"
|
|
50
53
|
},
|
|
51
54
|
"peerDependencies": {
|
|
52
55
|
"@react-native/jest-preset": "^0.88.0-rc.3",
|
|
53
56
|
"expo": "*",
|
|
57
|
+
"jest": "^30.0.0",
|
|
54
58
|
"react-native": "*",
|
|
55
59
|
"react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4 || ~19.3.0"
|
|
56
60
|
},
|
package/rsc/jest-preset.js
CHANGED
|
@@ -10,8 +10,5 @@ module.exports = withWatchPlugins({
|
|
|
10
10
|
getIOSPreset({ isReactServer: true }),
|
|
11
11
|
getAndroidPreset({ isReactServer: true }),
|
|
12
12
|
getWebPreset({ isReactServer: true }),
|
|
13
|
-
|
|
14
|
-
].map(({ watchPlugins, ...config }) => config),
|
|
15
|
-
// See: https://jestjs.io/docs/configuration#prettierpath-string
|
|
16
|
-
prettierPath: require.resolve('jest-snapshot-prettier'),
|
|
13
|
+
],
|
|
17
14
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { TestEnvironment: NodeEnvironment } = require('jest-environment-node');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Clear mocks exposed on the global scope, but only look at data properties.
|
|
7
|
+
*
|
|
8
|
+
* Since Jest 30.4, `Runtime.resetModules()` (also run during teardown) calls
|
|
9
|
+
* `moduleMocker.clearMocksOnScope(global)`, which reads every enumerable global. Expo installs
|
|
10
|
+
* several globals lazily (`fetch`, `structuredClone`, `__ExpoImportMetaRegistry`, ...) through
|
|
11
|
+
* getters that `require()` their implementation on first access. Reading them after the test has
|
|
12
|
+
* finished throws `You are trying to require a file outside of the scope of the test code`.
|
|
13
|
+
* Accessor properties are skipped here so lazy globals are never evaluated during teardown.
|
|
14
|
+
*
|
|
15
|
+
* @this {import('jest-mock').ModuleMocker}
|
|
16
|
+
* @param {object} scope
|
|
17
|
+
*/
|
|
18
|
+
function clearMocksOnScope(scope) {
|
|
19
|
+
for (const key of Object.keys(scope)) {
|
|
20
|
+
const descriptor = Object.getOwnPropertyDescriptor(scope, key);
|
|
21
|
+
if (!descriptor || typeof descriptor.get === 'function') {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const value = descriptor.value;
|
|
25
|
+
if (
|
|
26
|
+
value != null &&
|
|
27
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
28
|
+
'_isMockFunction' in value &&
|
|
29
|
+
this.isMockFunction(value) &&
|
|
30
|
+
typeof value.mockClear === 'function'
|
|
31
|
+
) {
|
|
32
|
+
value.mockClear();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Jest test environment for the native (iOS/Android) presets.
|
|
39
|
+
*
|
|
40
|
+
* Mirrors `@react-native/jest-preset/jest/react-native-env.js`, but resolves `jest-environment-node`
|
|
41
|
+
* from `jest-expo` instead of from `@react-native/jest-preset`. The React Native preset still pins
|
|
42
|
+
* Jest 29 packages, so on Jest 30 its environment would load `jest-environment-node@29`, mixing
|
|
43
|
+
* Jest 29 mocks and fake timers into a Jest 30 runtime (see `clearMocksOnScope is not a function`).
|
|
44
|
+
*/
|
|
45
|
+
class ReactNativeEnvironment extends NodeEnvironment {
|
|
46
|
+
customExportConditions = ['require', 'react-native'];
|
|
47
|
+
|
|
48
|
+
constructor(config, context) {
|
|
49
|
+
super(config, context);
|
|
50
|
+
// Patch the module mocker in place. `NodeEnvironment` already handed this instance to the
|
|
51
|
+
// legacy fake timers, so replacing it would leave timer mocks owned by a mocker the runtime
|
|
52
|
+
// never sees (and `jest.clearAllMocks()` would no longer clear them).
|
|
53
|
+
this.moduleMocker.clearMocksOnScope = clearMocksOnScope;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = ReactNativeEnvironment;
|
|
58
|
+
module.exports.TestEnvironment = ReactNativeEnvironment;
|