Skip to content

Commit 4fa3a0b

Browse files
authored
feat: custom haste (#11107)
1 parent 2047a36 commit 4fa3a0b

File tree

20 files changed

+314
-27
lines changed

20 files changed

+314
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Features
44

5+
- `[jest-config, jest-haste-map, jest-resolve, jest-runner, jest-runtime, jest-test-sequencer, jest-transform, jest-types]` [**BREAKING**] Add custom HasteMap class implementation config option ([#11107](https://github.com/facebook/jest/pull/11107))
56
- `[babel-jest]` Add async transformation ([#11192](https://github.com/facebook/jest/pull/11192))
67
- `[jest-changed-files]` Use '--' to separate paths from revisions ([#11160](https://github.com/facebook/jest/pull/11160))
78
- `[jest-circus]` [**BREAKING**] Fail tests when multiple `done()` calls are made ([#10624](https://github.com/facebook/jest/pull/10624))

docs/Configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,8 @@ type HasteConfig = {
514514
platforms?: Array<string>;
515515
/** Whether to throw on error on module collision. */
516516
throwOnModuleCollision?: boolean;
517+
/** Custom HasteMap module */
518+
hasteMapModulePath?: string;
517519
};
518520
```
519521

e2e/__tests__/customHaste.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import * as path from 'path';
9+
import runJest from '../runJest';
10+
11+
describe('Custom Haste Integration', () => {
12+
test('valid test with fake module resolutions', () => {
13+
const config = {
14+
haste: {
15+
hasteMapModulePath: path.resolve(
16+
__dirname,
17+
'..',
18+
'custom-haste-map/hasteMap.js',
19+
),
20+
},
21+
};
22+
23+
const {exitCode} = runJest('custom-haste-map', [
24+
'--config',
25+
JSON.stringify(config),
26+
'hasteExample.test.js',
27+
]);
28+
expect(exitCode).toBe(0);
29+
});
30+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
'use strict';
10+
11+
const add = require('fakeModuleName');
12+
13+
describe('Custom Haste', () => {
14+
test('adds ok', () => {
15+
expect(true).toBe(true);
16+
expect(add(1, 2)).toBe(3);
17+
});
18+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
'use strict';
10+
11+
function add(a, b) {
12+
return a + b;
13+
}
14+
15+
module.exports = add;

e2e/custom-haste-map/hasteMap.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
const path = require('path');
11+
const fakeFile = {
12+
file: path.resolve(__dirname, '__tests__/hasteExampleHelper.js'),
13+
moduleName: 'fakeModuleName',
14+
sha1: 'fakeSha1',
15+
};
16+
17+
const fakeJSON = 'fakeJSON';
18+
19+
const testPath = path.resolve(__dirname, '__tests__/hasteExample.test.js');
20+
21+
const allFiles = [fakeFile.file, testPath];
22+
23+
class HasteFS {
24+
getModuleName(file) {
25+
if (file === fakeFile.file) {
26+
return fakeFile.moduleName;
27+
}
28+
return null;
29+
}
30+
31+
getSize(file) {
32+
return null;
33+
}
34+
35+
getDependencies(file) {
36+
if (file === testPath) {
37+
return fakeFile.file;
38+
}
39+
return [];
40+
}
41+
42+
getSha1(file) {
43+
if (file === fakeFile.file) {
44+
return fakeFile.sha1;
45+
}
46+
return null;
47+
}
48+
49+
exists(file) {
50+
return allFiles.includes(file);
51+
}
52+
53+
getAllFiles() {
54+
return allFiles;
55+
}
56+
57+
getFileIterator() {
58+
return allFiles;
59+
}
60+
61+
getAbsoluteFileIterator() {
62+
return allFiles;
63+
}
64+
65+
matchFiles(pattern) {
66+
if (!(pattern instanceof RegExp)) {
67+
pattern = new RegExp(pattern);
68+
}
69+
const files = [];
70+
for (const file of this.getAbsoluteFileIterator()) {
71+
if (pattern.test(file)) {
72+
files.push(file);
73+
}
74+
}
75+
return files;
76+
}
77+
78+
matchFilesWithGlob(globs, root) {
79+
return [];
80+
}
81+
}
82+
83+
class ModuleMap {
84+
getModule(name, platform, supportsNativePlatform, type) {
85+
if (name === fakeFile.moduleName) {
86+
return fakeFile.file;
87+
}
88+
return null;
89+
}
90+
91+
getPackage() {
92+
return null;
93+
}
94+
95+
getMockModule() {
96+
return undefined;
97+
}
98+
99+
getRawModuleMap() {
100+
return {};
101+
}
102+
103+
toJSON() {
104+
return fakeJSON;
105+
}
106+
}
107+
108+
class HasteMap {
109+
constructor(options) {
110+
this._cachePath = HasteMap.getCacheFilePath(
111+
options.cacheDirectory,
112+
options.name,
113+
);
114+
}
115+
116+
async build() {
117+
return {
118+
hasteFS: new HasteFS(),
119+
moduleMap: new ModuleMap(),
120+
};
121+
}
122+
123+
static getCacheFilePath(tmpdir, name) {
124+
return path.join(tmpdir, name);
125+
}
126+
127+
getCacheFilePath() {
128+
return this._cachePath;
129+
}
130+
131+
static getModuleMapFromJSON(json) {
132+
if (json === fakeJSON) {
133+
return new ModuleMap();
134+
}
135+
throw new Error('Failed to parse serialized module map');
136+
}
137+
}
138+
139+
module.exports = HasteMap;

e2e/custom-haste-map/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"jest": {
3+
"haste": {
4+
"hasteMapModulePath": "<rootDir>/hasteMap.js"
5+
}
6+
}
7+
}

packages/jest-config/src/ValidConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const initialOptions: Config.InitialOptions = {
6161
enableSymlinks: false,
6262
forceNodeFilesystemAPI: false,
6363
hasteImplModulePath: '<rootDir>/haste_impl.js',
64+
hasteMapModulePath: '',
6465
platforms: ['ios', 'android'],
6566
throwOnModuleCollision: false,
6667
},

packages/jest-haste-map/src/ModuleMap.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,16 @@ import * as fastPath from './lib/fast_path';
1111
import type {
1212
DuplicatesSet,
1313
HTypeValue,
14-
MockData,
15-
ModuleMapData,
14+
IModuleMap,
1615
ModuleMetaData,
1716
RawModuleMap,
17+
SerializableModuleMap,
1818
} from './types';
1919

2020
const EMPTY_OBJ: Record<string, ModuleMetaData> = {};
2121
const EMPTY_MAP = new Map();
2222

23-
type ValueType<T> = T extends Map<string, infer V> ? V : never;
24-
25-
export type SerializableModuleMap = {
26-
duplicates: ReadonlyArray<[string, [string, [string, [string, number]]]]>;
27-
map: ReadonlyArray<[string, ValueType<ModuleMapData>]>;
28-
mocks: ReadonlyArray<[string, ValueType<MockData>]>;
29-
rootDir: Config.Path;
30-
};
31-
32-
export default class ModuleMap {
23+
export default class ModuleMap implements IModuleMap<SerializableModuleMap> {
3324
static DuplicateHasteCandidatesError: typeof DuplicateHasteCandidatesError;
3425
private readonly _raw: RawModuleMap;
3526
private json: SerializableModuleMap | undefined;

packages/jest-haste-map/src/index.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ import type {
3232
EventsQueue,
3333
FileData,
3434
FileMetaData,
35+
HasteMapStatic,
3536
HasteRegExp,
3637
InternalHasteMap,
3738
HasteMap as InternalHasteMapObject,
3839
MockData,
3940
ModuleMapData,
4041
ModuleMetaData,
42+
SerializableModuleMap,
4143
WorkerMetadata,
4244
} from './types';
4345
import FSEventsWatcher = require('./watchers/FSEventsWatcher');
@@ -60,6 +62,7 @@ type Options = {
6062
extensions: Array<string>;
6163
forceNodeFilesystemAPI?: boolean;
6264
hasteImplModulePath?: string;
65+
hasteMapModulePath?: string;
6366
ignorePattern?: HasteRegExp;
6467
maxWorkers: number;
6568
mocksPattern?: string;
@@ -106,7 +109,8 @@ type Watcher = {
106109
type WorkerInterface = {worker: typeof worker; getSha1: typeof getSha1};
107110

108111
export {default as ModuleMap} from './ModuleMap';
109-
export type {SerializableModuleMap} from './ModuleMap';
112+
export type {SerializableModuleMap} from './types';
113+
export type {IModuleMap} from './types';
110114
export type {default as FS} from './HasteFS';
111115
export type {ChangeEvent, HasteMap as HasteMapObject} from './types';
112116

@@ -219,7 +223,22 @@ export default class HasteMap extends EventEmitter {
219223
private _watchers: Array<Watcher>;
220224
private _worker: WorkerInterface | null;
221225

222-
constructor(options: Options) {
226+
static getStatic(config: Config.ProjectConfig): HasteMapStatic {
227+
if (config.haste.hasteMapModulePath) {
228+
return require(config.haste.hasteMapModulePath);
229+
}
230+
return HasteMap;
231+
}
232+
233+
static create(options: Options): HasteMap {
234+
if (options.hasteMapModulePath) {
235+
const CustomHasteMap = require(options.hasteMapModulePath);
236+
return new CustomHasteMap(options);
237+
}
238+
return new HasteMap(options);
239+
}
240+
241+
private constructor(options: Options) {
223242
super();
224243
this._options = {
225244
cacheDirectory: options.cacheDirectory || tmpdir(),
@@ -324,6 +343,10 @@ export default class HasteMap extends EventEmitter {
324343
);
325344
}
326345

346+
static getModuleMapFromJSON(json: SerializableModuleMap): HasteModuleMap {
347+
return HasteModuleMap.fromJSON(json);
348+
}
349+
327350
getCacheFilePath(): string {
328351
return this._cachePath;
329352
}

packages/jest-haste-map/src/types.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,45 @@ import type {Config} from '@jest/types';
1010
import type HasteFS from './HasteFS';
1111
import type ModuleMap from './ModuleMap';
1212

13+
type ValueType<T> = T extends Map<string, infer V> ? V : never;
14+
15+
export type SerializableModuleMap = {
16+
duplicates: ReadonlyArray<[string, [string, [string, [string, number]]]]>;
17+
map: ReadonlyArray<[string, ValueType<ModuleMapData>]>;
18+
mocks: ReadonlyArray<[string, ValueType<MockData>]>;
19+
rootDir: Config.Path;
20+
};
21+
22+
export interface IModuleMap<S = SerializableModuleMap> {
23+
getModule(
24+
name: string,
25+
platform?: string | null,
26+
supportsNativePlatform?: boolean | null,
27+
type?: HTypeValue | null,
28+
): Config.Path | null;
29+
30+
getPackage(
31+
name: string,
32+
platform: string | null | undefined,
33+
_supportsNativePlatform: boolean | null,
34+
): Config.Path | null;
35+
36+
getMockModule(name: string): Config.Path | undefined;
37+
38+
getRawModuleMap(): RawModuleMap;
39+
40+
toJSON(): S;
41+
}
42+
43+
export type HasteMapStatic<S = SerializableModuleMap> = {
44+
getCacheFilePath(
45+
tmpdir: Config.Path,
46+
name: string,
47+
...extra: Array<string>
48+
): string;
49+
getModuleMapFromJSON(json: S): IModuleMap<S>;
50+
};
51+
1352
export type IgnoreMatcher = (item: string) => boolean;
1453

1554
export type WorkerMessage = {
@@ -71,6 +110,12 @@ export type InternalHasteMap = {
71110
mocks: MockData;
72111
};
73112

113+
export type IHasteMap = {
114+
hasteFS: HasteFS;
115+
moduleMap: IModuleMap;
116+
__hasteMapForTest?: InternalHasteMap | null;
117+
};
118+
74119
export type HasteMap = {
75120
hasteFS: HasteFS;
76121
moduleMap: ModuleMap;

0 commit comments

Comments
 (0)