Skip to content

Commit

Permalink
feat: custom haste (#11107)
Browse files Browse the repository at this point in the history
  • Loading branch information
DiZy authored May 20, 2021
1 parent 2047a36 commit 4fa3a0b
Show file tree
Hide file tree
Showing 20 changed files with 314 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Features

- `[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))
- `[babel-jest]` Add async transformation ([#11192](https://github.com/facebook/jest/pull/11192))
- `[jest-changed-files]` Use '--' to separate paths from revisions ([#11160](https://github.com/facebook/jest/pull/11160))
- `[jest-circus]` [**BREAKING**] Fail tests when multiple `done()` calls are made ([#10624](https://github.com/facebook/jest/pull/10624))
Expand Down
2 changes: 2 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@ type HasteConfig = {
platforms?: Array<string>;
/** Whether to throw on error on module collision. */
throwOnModuleCollision?: boolean;
/** Custom HasteMap module */
hasteMapModulePath?: string;
};
```

Expand Down
30 changes: 30 additions & 0 deletions e2e/__tests__/customHaste.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import * as path from 'path';
import runJest from '../runJest';

describe('Custom Haste Integration', () => {
test('valid test with fake module resolutions', () => {
const config = {
haste: {
hasteMapModulePath: path.resolve(
__dirname,
'..',
'custom-haste-map/hasteMap.js',
),
},
};

const {exitCode} = runJest('custom-haste-map', [
'--config',
JSON.stringify(config),
'hasteExample.test.js',
]);
expect(exitCode).toBe(0);
});
});
18 changes: 18 additions & 0 deletions e2e/custom-haste-map/__tests__/hasteExample.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

'use strict';

const add = require('fakeModuleName');

describe('Custom Haste', () => {
test('adds ok', () => {
expect(true).toBe(true);
expect(add(1, 2)).toBe(3);
});
});
15 changes: 15 additions & 0 deletions e2e/custom-haste-map/__tests__/hasteExampleHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

'use strict';

function add(a, b) {
return a + b;
}

module.exports = add;
139 changes: 139 additions & 0 deletions e2e/custom-haste-map/hasteMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

const path = require('path');
const fakeFile = {
file: path.resolve(__dirname, '__tests__/hasteExampleHelper.js'),
moduleName: 'fakeModuleName',
sha1: 'fakeSha1',
};

const fakeJSON = 'fakeJSON';

const testPath = path.resolve(__dirname, '__tests__/hasteExample.test.js');

const allFiles = [fakeFile.file, testPath];

class HasteFS {
getModuleName(file) {
if (file === fakeFile.file) {
return fakeFile.moduleName;
}
return null;
}

getSize(file) {
return null;
}

getDependencies(file) {
if (file === testPath) {
return fakeFile.file;
}
return [];
}

getSha1(file) {
if (file === fakeFile.file) {
return fakeFile.sha1;
}
return null;
}

exists(file) {
return allFiles.includes(file);
}

getAllFiles() {
return allFiles;
}

getFileIterator() {
return allFiles;
}

getAbsoluteFileIterator() {
return allFiles;
}

matchFiles(pattern) {
if (!(pattern instanceof RegExp)) {
pattern = new RegExp(pattern);
}
const files = [];
for (const file of this.getAbsoluteFileIterator()) {
if (pattern.test(file)) {
files.push(file);
}
}
return files;
}

matchFilesWithGlob(globs, root) {
return [];
}
}

class ModuleMap {
getModule(name, platform, supportsNativePlatform, type) {
if (name === fakeFile.moduleName) {
return fakeFile.file;
}
return null;
}

getPackage() {
return null;
}

getMockModule() {
return undefined;
}

getRawModuleMap() {
return {};
}

toJSON() {
return fakeJSON;
}
}

class HasteMap {
constructor(options) {
this._cachePath = HasteMap.getCacheFilePath(
options.cacheDirectory,
options.name,
);
}

async build() {
return {
hasteFS: new HasteFS(),
moduleMap: new ModuleMap(),
};
}

static getCacheFilePath(tmpdir, name) {
return path.join(tmpdir, name);
}

getCacheFilePath() {
return this._cachePath;
}

static getModuleMapFromJSON(json) {
if (json === fakeJSON) {
return new ModuleMap();
}
throw new Error('Failed to parse serialized module map');
}
}

module.exports = HasteMap;
7 changes: 7 additions & 0 deletions e2e/custom-haste-map/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"jest": {
"haste": {
"hasteMapModulePath": "<rootDir>/hasteMap.js"
}
}
}
1 change: 1 addition & 0 deletions packages/jest-config/src/ValidConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const initialOptions: Config.InitialOptions = {
enableSymlinks: false,
forceNodeFilesystemAPI: false,
hasteImplModulePath: '<rootDir>/haste_impl.js',
hasteMapModulePath: '',
platforms: ['ios', 'android'],
throwOnModuleCollision: false,
},
Expand Down
15 changes: 3 additions & 12 deletions packages/jest-haste-map/src/ModuleMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,16 @@ import * as fastPath from './lib/fast_path';
import type {
DuplicatesSet,
HTypeValue,
MockData,
ModuleMapData,
IModuleMap,
ModuleMetaData,
RawModuleMap,
SerializableModuleMap,
} from './types';

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

type ValueType<T> = T extends Map<string, infer V> ? V : never;

export type SerializableModuleMap = {
duplicates: ReadonlyArray<[string, [string, [string, [string, number]]]]>;
map: ReadonlyArray<[string, ValueType<ModuleMapData>]>;
mocks: ReadonlyArray<[string, ValueType<MockData>]>;
rootDir: Config.Path;
};

export default class ModuleMap {
export default class ModuleMap implements IModuleMap<SerializableModuleMap> {
static DuplicateHasteCandidatesError: typeof DuplicateHasteCandidatesError;
private readonly _raw: RawModuleMap;
private json: SerializableModuleMap | undefined;
Expand Down
27 changes: 25 additions & 2 deletions packages/jest-haste-map/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ import type {
EventsQueue,
FileData,
FileMetaData,
HasteMapStatic,
HasteRegExp,
InternalHasteMap,
HasteMap as InternalHasteMapObject,
MockData,
ModuleMapData,
ModuleMetaData,
SerializableModuleMap,
WorkerMetadata,
} from './types';
import FSEventsWatcher = require('./watchers/FSEventsWatcher');
Expand All @@ -60,6 +62,7 @@ type Options = {
extensions: Array<string>;
forceNodeFilesystemAPI?: boolean;
hasteImplModulePath?: string;
hasteMapModulePath?: string;
ignorePattern?: HasteRegExp;
maxWorkers: number;
mocksPattern?: string;
Expand Down Expand Up @@ -106,7 +109,8 @@ type Watcher = {
type WorkerInterface = {worker: typeof worker; getSha1: typeof getSha1};

export {default as ModuleMap} from './ModuleMap';
export type {SerializableModuleMap} from './ModuleMap';
export type {SerializableModuleMap} from './types';
export type {IModuleMap} from './types';
export type {default as FS} from './HasteFS';
export type {ChangeEvent, HasteMap as HasteMapObject} from './types';

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

constructor(options: Options) {
static getStatic(config: Config.ProjectConfig): HasteMapStatic {
if (config.haste.hasteMapModulePath) {
return require(config.haste.hasteMapModulePath);
}
return HasteMap;
}

static create(options: Options): HasteMap {
if (options.hasteMapModulePath) {
const CustomHasteMap = require(options.hasteMapModulePath);
return new CustomHasteMap(options);
}
return new HasteMap(options);
}

private constructor(options: Options) {
super();
this._options = {
cacheDirectory: options.cacheDirectory || tmpdir(),
Expand Down Expand Up @@ -324,6 +343,10 @@ export default class HasteMap extends EventEmitter {
);
}

static getModuleMapFromJSON(json: SerializableModuleMap): HasteModuleMap {
return HasteModuleMap.fromJSON(json);
}

getCacheFilePath(): string {
return this._cachePath;
}
Expand Down
45 changes: 45 additions & 0 deletions packages/jest-haste-map/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,45 @@ import type {Config} from '@jest/types';
import type HasteFS from './HasteFS';
import type ModuleMap from './ModuleMap';

type ValueType<T> = T extends Map<string, infer V> ? V : never;

export type SerializableModuleMap = {
duplicates: ReadonlyArray<[string, [string, [string, [string, number]]]]>;
map: ReadonlyArray<[string, ValueType<ModuleMapData>]>;
mocks: ReadonlyArray<[string, ValueType<MockData>]>;
rootDir: Config.Path;
};

export interface IModuleMap<S = SerializableModuleMap> {
getModule(
name: string,
platform?: string | null,
supportsNativePlatform?: boolean | null,
type?: HTypeValue | null,
): Config.Path | null;

getPackage(
name: string,
platform: string | null | undefined,
_supportsNativePlatform: boolean | null,
): Config.Path | null;

getMockModule(name: string): Config.Path | undefined;

getRawModuleMap(): RawModuleMap;

toJSON(): S;
}

export type HasteMapStatic<S = SerializableModuleMap> = {
getCacheFilePath(
tmpdir: Config.Path,
name: string,
...extra: Array<string>
): string;
getModuleMapFromJSON(json: S): IModuleMap<S>;
};

export type IgnoreMatcher = (item: string) => boolean;

export type WorkerMessage = {
Expand Down Expand Up @@ -71,6 +110,12 @@ export type InternalHasteMap = {
mocks: MockData;
};

export type IHasteMap = {
hasteFS: HasteFS;
moduleMap: IModuleMap;
__hasteMapForTest?: InternalHasteMap | null;
};

export type HasteMap = {
hasteFS: HasteFS;
moduleMap: ModuleMap;
Expand Down
Loading

0 comments on commit 4fa3a0b

Please sign in to comment.