Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80629 views
1
/**
2
* Copyright (c) 2014, Facebook, Inc. All rights reserved.
3
*
4
* This source code is licensed under the BSD-style license found in the
5
* LICENSE file in the root directory of this source tree. An additional grant
6
* of patent rights can be found in the PATENTS file in the same directory.
7
*/
8
'use strict';
9
10
/**
11
* TODO: This file has grown into a monster. It really needs to be refactored
12
* into smaller pieces. One of the best places to start would be to move a
13
* bunch of the logic that exists here into node-haste.
14
*
15
* Relatedly: It's time we vastly simplify node-haste.
16
*/
17
18
var fs = require('graceful-fs');
19
var hasteLoaders = require('node-haste/lib/loaders');
20
var moduleMocker = require('../lib/moduleMocker');
21
var NodeHaste = require('node-haste/lib/Haste');
22
var os = require('os');
23
var path = require('path');
24
var Q = require('q');
25
var resolve = require('resolve');
26
var utils = require('../lib/utils');
27
28
var COVERAGE_STORAGE_VAR_NAME = '____JEST_COVERAGE_DATA____';
29
30
var NODE_PATH = process.env.NODE_PATH;
31
32
var IS_PATH_BASED_MODULE_NAME = /^(?:\.\.?\/|\/)/;
33
34
var NODE_CORE_MODULES = {
35
assert: true,
36
buffer: true,
37
child_process: true, // jshint ignore:line
38
cluster: true,
39
console: true,
40
constants: true,
41
crypto: true,
42
dgram: true,
43
dns: true,
44
domain: true,
45
events: true,
46
freelist: true,
47
fs: true,
48
http: true,
49
https: true,
50
module: true,
51
net: true,
52
os: true,
53
path: true,
54
punycode: true,
55
querystring: true,
56
readline: true,
57
repl: true,
58
smalloc: true,
59
stream: true,
60
string_decoder: true, // jshint ignore:line
61
sys: true,
62
timers: true,
63
tls: true,
64
tty: true,
65
url: true,
66
util: true,
67
vm: true,
68
zlib: true
69
};
70
71
var VENDOR_PATH = path.resolve(__dirname, '../../vendor');
72
73
var _configUnmockListRegExpCache = null;
74
75
function _buildLoadersList(config) {
76
return [
77
new hasteLoaders.ProjectConfigurationLoader(),
78
new hasteLoaders.JSTestLoader(config.setupJSTestLoaderOptions),
79
new hasteLoaders.JSMockLoader(config.setupJSMockLoaderOptions),
80
new hasteLoaders.JSLoader(config.setupJSLoaderOptions),
81
new hasteLoaders.ResourceLoader()
82
];
83
}
84
85
function _constructHasteInst(config, options) {
86
var HASTE_IGNORE_REGEX = new RegExp(
87
config.modulePathIgnorePatterns.length > 0
88
? config.modulePathIgnorePatterns.join('|')
89
: '$.' // never matches
90
);
91
92
if (!fs.existsSync(config.cacheDirectory)) {
93
fs.mkdirSync(config.cacheDirectory);
94
}
95
96
return new NodeHaste(
97
_buildLoadersList(config),
98
(config.testPathDirs || []),
99
{
100
ignorePaths: function(path) {
101
return path.match(HASTE_IGNORE_REGEX);
102
},
103
version: JSON.stringify(config),
104
useNativeFind: true,
105
maxProcesses: os.cpus().length,
106
maxOpenFiles: options.maxOpenFiles || 100
107
}
108
);
109
}
110
111
function _getCacheFilePath(config) {
112
return path.join(config.cacheDirectory, 'cache-' + config.name);
113
}
114
115
function Loader(config, environment, resourceMap) {
116
this._config = config;
117
this._CoverageCollector = require(config.coverageCollector);
118
this._coverageCollectors = {};
119
this._currentlyExecutingModulePath = '';
120
this._environment = environment;
121
this._explicitShouldMock = {};
122
this._explicitlySetMocks = {};
123
this._isCurrentlyExecutingManualMock = null;
124
this._mockMetaDataCache = {};
125
this._nodeModuleProjectConfigNameToResource = null;
126
this._resourceMap = resourceMap;
127
this._reverseDependencyMap = null;
128
this._shouldAutoMock = true;
129
this._configShouldMockModuleNames = {};
130
131
if (_configUnmockListRegExpCache === null) {
132
// Node must have been run with --harmony in order for WeakMap to be
133
// available prior to version 0.12
134
if (typeof WeakMap !== 'function') {
135
throw new Error(
136
'Please run node with the --harmony flag! jest requires WeakMap ' +
137
'which is only available with the --harmony flag in node < v0.12'
138
);
139
}
140
141
_configUnmockListRegExpCache = new WeakMap();
142
}
143
144
if (!config.unmockedModulePathPatterns
145
|| config.unmockedModulePathPatterns.length === 0) {
146
this._unmockListRegExps = [];
147
} else {
148
this._unmockListRegExps = _configUnmockListRegExpCache.get(config);
149
if (!this._unmockListRegExps) {
150
this._unmockListRegExps = config.unmockedModulePathPatterns
151
.map(function(unmockPathRe) {
152
return new RegExp(unmockPathRe);
153
});
154
_configUnmockListRegExpCache.set(config, this._unmockListRegExps);
155
}
156
}
157
158
this.resetModuleRegistry();
159
}
160
161
Loader.loadResourceMap = function(config, options) {
162
options = options || {};
163
164
var deferred = Q.defer();
165
try {
166
_constructHasteInst(config, options).update(
167
_getCacheFilePath(config),
168
function(resourceMap) {
169
deferred.resolve(resourceMap);
170
}
171
);
172
} catch (e) {
173
deferred.reject(e);
174
}
175
176
return deferred.promise;
177
};
178
179
Loader.loadResourceMapFromCacheFile = function(config, options) {
180
options = options || {};
181
182
var deferred = Q.defer();
183
try {
184
var hasteInst = _constructHasteInst(config, options);
185
hasteInst.loadMap(
186
_getCacheFilePath(config),
187
function(err, map) {
188
if (err) {
189
deferred.reject(err);
190
} else {
191
deferred.resolve(map);
192
}
193
}
194
);
195
} catch (e) {
196
deferred.reject(e);
197
}
198
199
return deferred.promise;
200
};
201
202
/**
203
* Given the path to a module: Read it from disk (synchronously) and
204
* evaluate it's constructor function to generate the module and exports
205
* objects.
206
*
207
* @param string modulePath
208
* @return object
209
*/
210
Loader.prototype._execModule = function(moduleObj) {
211
var modulePath = moduleObj.__filename;
212
213
var moduleContent =
214
utils.readAndPreprocessFileContent(modulePath, this._config);
215
216
moduleObj.require = this.constructBoundRequire(modulePath);
217
218
var moduleLocalBindings = {
219
'module': moduleObj,
220
'exports': moduleObj.exports,
221
'require': moduleObj.require,
222
'__dirname': path.dirname(modulePath),
223
'__filename': modulePath,
224
'global': this._environment.global,
225
'jest': this._builtInModules['jest-runtime'](modulePath).exports
226
};
227
228
var onlyCollectFrom = this._config.collectCoverageOnlyFrom;
229
var shouldCollectCoverage =
230
this._config.collectCoverage === true && !onlyCollectFrom
231
|| (onlyCollectFrom && onlyCollectFrom[modulePath] === true);
232
233
if (shouldCollectCoverage) {
234
if (!this._coverageCollectors.hasOwnProperty(modulePath)) {
235
this._coverageCollectors[modulePath] =
236
new this._CoverageCollector(moduleContent, modulePath);
237
}
238
var collector = this._coverageCollectors[modulePath];
239
moduleLocalBindings[COVERAGE_STORAGE_VAR_NAME] =
240
collector.getCoverageDataStore();
241
moduleContent = collector.getInstrumentedSource(COVERAGE_STORAGE_VAR_NAME);
242
}
243
244
var lastExecutingModulePath = this._currentlyExecutingModulePath;
245
this._currentlyExecutingModulePath = modulePath;
246
247
var origCurrExecutingManualMock = this._isCurrentlyExecutingManualMock;
248
this._isCurrentlyExecutingManualMock = modulePath;
249
250
utils.runContentWithLocalBindings(
251
this._environment.runSourceText.bind(this._environment),
252
moduleContent,
253
modulePath,
254
moduleLocalBindings
255
);
256
257
this._isCurrentlyExecutingManualMock = origCurrExecutingManualMock;
258
this._currentlyExecutingModulePath = lastExecutingModulePath;
259
};
260
261
Loader.prototype._generateMock = function(currPath, moduleName) {
262
var modulePath = this._moduleNameToPath(currPath, moduleName);
263
264
if (!this._mockMetaDataCache.hasOwnProperty(modulePath)) {
265
// This allows us to handle circular dependencies while generating an
266
// automock
267
this._mockMetaDataCache[modulePath] = moduleMocker.getMetadata({});
268
269
// In order to avoid it being possible for automocking to potentially cause
270
// side-effects within the module environment, we need to execute the module
271
// in isolation. This accomplishes that by temporarily clearing out the
272
// module and mock registries while the module being analyzed is executed.
273
//
274
// An example scenario where this could cause issue is if the module being
275
// mocked has calls into side-effectful APIs on another module.
276
var origMockRegistry = this._mockRegistry;
277
var origModuleRegistry = this._moduleRegistry;
278
this._mockRegistry = {};
279
this._moduleRegistry = {};
280
281
var moduleExports = this.requireModule(currPath, moduleName);
282
283
// Restore the "real" module/mock registries
284
this._mockRegistry = origMockRegistry;
285
this._moduleRegistry = origModuleRegistry;
286
287
this._mockMetaDataCache[modulePath] = moduleMocker.getMetadata(
288
moduleExports
289
);
290
}
291
292
return moduleMocker.generateFromMetadata(
293
this._mockMetaDataCache[modulePath]
294
);
295
};
296
297
Loader.prototype._getDependencyPathsFromResource = function(resource) {
298
var dependencyPaths = [];
299
for (var i = 0; i < resource.requiredModules.length; i++) {
300
var requiredModule = resource.requiredModules[i];
301
302
// *facepalm* node-haste is pretty clowny
303
if (resource.getModuleIDByOrigin) {
304
requiredModule =
305
resource.getModuleIDByOrigin(requiredModule) || requiredModule;
306
}
307
308
try {
309
var moduleID = this._getNormalizedModuleID(resource.path, requiredModule);
310
} catch(e) {
311
console.warn(
312
'Could not find a `' + requiredModule + '` module while analyzing ' +
313
'dependencies of `' + resource.id + '`'
314
);
315
continue;
316
}
317
318
dependencyPaths.push(this._getRealPathFromNormalizedModuleID(moduleID));
319
}
320
return dependencyPaths;
321
};
322
323
Loader.prototype._getResource = function(resourceType, resourceName) {
324
var resource = this._resourceMap.getResource(resourceType, resourceName);
325
326
// TODO: Fix this properly in node-haste, not here :(
327
if (resource === undefined && resourceType === 'JS' && /\//.test(resourceName)
328
&& !/\.js$/.test(resourceName)) {
329
resource = this._resourceMap.getResource(
330
resourceType,
331
resourceName + '.js'
332
);
333
}
334
335
return resource;
336
};
337
338
Loader.prototype._getNormalizedModuleID = function(currPath, moduleName) {
339
var moduleType;
340
var mockAbsPath = null;
341
var realAbsPath = null;
342
343
if (this._builtInModules.hasOwnProperty(moduleName)) {
344
moduleType = 'builtin';
345
realAbsPath = moduleName;
346
} else if (NODE_CORE_MODULES.hasOwnProperty(moduleName)) {
347
moduleType = 'node';
348
realAbsPath = moduleName;
349
} else {
350
moduleType = 'user';
351
352
// If this is a path-based module name, resolve it to an absolute path and
353
// then see if there's a node-haste resource for it (so that we can extract
354
// info from the resource, like whether its a mock, or a
355
if (IS_PATH_BASED_MODULE_NAME.test(moduleName)
356
|| (this._getResource('JS', moduleName) === undefined
357
&& this._getResource('JSMock', moduleName) === undefined)) {
358
var absolutePath = this._moduleNameToPath(currPath, moduleName);
359
if (absolutePath === undefined) {
360
throw new Error(
361
'Cannot find module \'' + moduleName + '\' from \'' + currPath + '\''
362
);
363
}
364
365
// See if node-haste is already aware of this resource. If so, we need to
366
// look up if it has an associated manual mock.
367
var resource = this._resourceMap.getResourceByPath(absolutePath);
368
if (resource) {
369
if (resource.type === 'JS') {
370
realAbsPath = absolutePath;
371
} else if (resource.type === 'JSMock') {
372
mockAbsPath = absolutePath;
373
}
374
moduleName = resource.id;
375
}
376
}
377
378
if (realAbsPath === null) {
379
var moduleResource = this._getResource('JS', moduleName);
380
if (moduleResource) {
381
realAbsPath = moduleResource.path;
382
}
383
}
384
385
if (mockAbsPath === null) {
386
var mockResource = this._getResource('JSMock', moduleName);
387
if (mockResource) {
388
mockAbsPath = mockResource.path;
389
}
390
}
391
}
392
393
return [moduleType, realAbsPath, mockAbsPath].join(':');
394
};
395
396
Loader.prototype._getRealPathFromNormalizedModuleID = function(moduleID) {
397
return moduleID.split(':')[1];
398
};
399
400
/**
401
* Given a module name and the current file path, returns the normalized
402
* (absolute) module path for said module. Relative-path CommonJS require()s
403
* such as `require('./otherModule')` need to be looked up with context of
404
* the module that's calling require()
405
*
406
* Also contains special case logic for built-in modules, in which it just
407
* returns the module name.
408
*
409
* @param string currPath The path of the file that is attempting to
410
* resolve the module
411
* @param string moduleName The name of the module to be resolved
412
* @return string
413
*/
414
Loader.prototype._moduleNameToPath = function(currPath, moduleName) {
415
if (this._builtInModules.hasOwnProperty(moduleName)) {
416
return moduleName;
417
}
418
419
// Relative-path CommonJS require()s such as `require('./otherModule')`
420
// need to be looked up with context of the module that's calling
421
// require().
422
if (IS_PATH_BASED_MODULE_NAME.test(moduleName)) {
423
// Normalize the relative path to an absolute path
424
var modulePath = path.resolve(currPath, '..', moduleName);
425
426
var ext, i;
427
var extensions = this._config.moduleFileExtensions;
428
429
// http://nodejs.org/docs/v0.10.0/api/all.html#all_all_together
430
// LOAD_AS_FILE #1
431
if (fs.existsSync(modulePath) &&
432
fs.statSync(modulePath).isFile()) {
433
return modulePath;
434
}
435
// LOAD_AS_FILE #2+
436
for (i = 0; i < extensions.length; i++) {
437
ext = '.' + extensions[i];
438
if (fs.existsSync(modulePath + ext) &&
439
fs.statSync(modulePath + ext).isFile()) {
440
return modulePath + ext;
441
}
442
}
443
// LOAD_AS_DIRECTORY
444
if (fs.existsSync(modulePath) &&
445
fs.statSync(modulePath).isDirectory()) {
446
447
// LOAD_AS_DIRECTORY #1
448
var packagePath = path.join(modulePath, 'package.json');
449
if (fs.existsSync(packagePath)) {
450
var mainPath = path.join(modulePath, require(packagePath).main);
451
if (fs.existsSync(mainPath)) {
452
return mainPath;
453
}
454
}
455
456
// The required path is a valid directory, but there's no matching
457
// js file at the same path. So look in the directory for an
458
// index.js file.
459
var indexPath = path.join(modulePath, 'index');
460
for (i = 0; i < extensions.length; i++) {
461
ext = '.' + extensions[i];
462
if (fs.existsSync(indexPath + ext) &&
463
fs.statSync(indexPath + ext).isFile()) {
464
return indexPath + ext;
465
}
466
}
467
}
468
} else {
469
var resource = this._getResource('JS', moduleName);
470
if (!resource) {
471
return this._nodeModuleNameToPath(
472
currPath,
473
moduleName
474
);
475
}
476
return resource.path;
477
}
478
};
479
480
Loader.prototype._nodeModuleNameToPath = function(currPath, moduleName) {
481
// Handle module names like require('jest/lib/util')
482
var subModulePath = null;
483
var moduleProjectPart = moduleName;
484
if (/\//.test(moduleName)) {
485
var projectPathParts = moduleName.split('/');
486
moduleProjectPart = projectPathParts.shift();
487
subModulePath = projectPathParts.join('/');
488
}
489
490
var resolveError = null;
491
var exts = this._config.moduleFileExtensions
492
.map(function(ext){
493
return '.' + ext;
494
});
495
try {
496
if (NODE_PATH) {
497
return resolve.sync(moduleName, {
498
paths: NODE_PATH.split(path.delimiter),
499
basedir: path.dirname(currPath),
500
extensions: exts
501
});
502
} else {
503
return resolve.sync(moduleName, {
504
basedir: path.dirname(currPath),
505
extensions: exts
506
});
507
}
508
} catch (e) {
509
// Facebook has clowny package.json resolution rules that don't apply to
510
// regular Node rules. Until we can make ModuleLoaders more pluggable
511
// (so that FB can have a custom ModuleLoader and all the normal people can
512
// have a normal ModuleLoader), we catch node-resolution exceptions and
513
// fall back to some custom resolution logic before throwing the error.
514
resolveError = e;
515
}
516
517
// Memoize the project name -> package.json resource lookup map
518
if (this._nodeModuleProjectConfigNameToResource === null) {
519
this._nodeModuleProjectConfigNameToResource = {};
520
var resources =
521
this._resourceMap.getAllResourcesByType('ProjectConfiguration');
522
resources.forEach(function(res) {
523
this._nodeModuleProjectConfigNameToResource[res.data.name] = res;
524
}.bind(this));
525
}
526
527
// Get the resource for the package.json file
528
var resource = this._nodeModuleProjectConfigNameToResource[moduleProjectPart];
529
if (!resource) {
530
throw resolveError;
531
}
532
533
// Make sure the resource path is above the currPath in the fs path
534
// tree. If so, just use node's resolve
535
var resourceDirname = path.dirname(resource.path);
536
var currFileDirname = path.dirname(currPath);
537
if (resourceDirname.indexOf(currFileDirname) > 0) {
538
throw resolveError;
539
}
540
541
if (subModulePath === null) {
542
subModulePath =
543
resource.data.hasOwnProperty('main')
544
? resource.data.main
545
: 'index.js';
546
}
547
548
return this._moduleNameToPath(
549
resource.path,
550
'./' + subModulePath
551
);
552
};
553
554
/**
555
* Indicates whether a given module is mocked per the current state of the
556
* module loader. When a module is "mocked", that means calling
557
* `requireModuleOrMock()` for the module will return the mock version
558
* rather than the real version.
559
*
560
* @param string currPath The path of the file that is attempting to
561
* resolve the module
562
* @param string moduleName The name of the module to be resolved
563
* @return bool
564
*/
565
Loader.prototype._shouldMock = function(currPath, moduleName) {
566
var moduleID = this._getNormalizedModuleID(currPath, moduleName);
567
if (this._builtInModules.hasOwnProperty(moduleName)) {
568
return false;
569
} else if (this._explicitShouldMock.hasOwnProperty(moduleID)) {
570
return this._explicitShouldMock[moduleID];
571
} else if (NODE_CORE_MODULES[moduleName]) {
572
return false;
573
} else if (this._shouldAutoMock) {
574
575
// See if the module is specified in the config as a module that should
576
// never be mocked
577
if (this._configShouldMockModuleNames.hasOwnProperty(moduleName)) {
578
return this._configShouldMockModuleNames[moduleName];
579
} else if (this._unmockListRegExps.length > 0) {
580
this._configShouldMockModuleNames[moduleName] = true;
581
582
var manualMockResource =
583
this._getResource('JSMock', moduleName);
584
try {
585
var modulePath = this._moduleNameToPath(currPath, moduleName);
586
} catch(e) {
587
// If there isn't a real module, we don't have a path to match
588
// against the unmockList regexps. If there is also not a manual
589
// mock, then we throw because this module doesn't exist anywhere.
590
//
591
// However, it's possible that someone has a manual mock for a
592
// non-existant real module. In this case, we should mock the module
593
// (because we technically can).
594
//
595
// Ideally this should never happen, but we have some odd
596
// pre-existing edge-cases that rely on it so we need it for now.
597
//
598
// I'd like to eliminate this behavior in favor of requiring that
599
// all module environments are complete (meaning you can't just
600
// write a manual mock as a substitute for a real module).
601
if (manualMockResource) {
602
return true;
603
}
604
throw e;
605
}
606
var unmockRegExp;
607
608
// Never mock the jasmine environment.
609
if (modulePath.indexOf(VENDOR_PATH) === 0) {
610
return false;
611
}
612
613
this._configShouldMockModuleNames[moduleName] = true;
614
for (var i = 0; i < this._unmockListRegExps.length; i++) {
615
unmockRegExp = this._unmockListRegExps[i];
616
if (unmockRegExp.test(modulePath)) {
617
return this._configShouldMockModuleNames[moduleName] = false;
618
}
619
}
620
return this._configShouldMockModuleNames[moduleName];
621
}
622
return true;
623
} else {
624
return false;
625
}
626
};
627
628
Loader.prototype.constructBoundRequire = function(sourceModulePath) {
629
var boundModuleRequire = this.requireModuleOrMock.bind(
630
this,
631
sourceModulePath
632
);
633
634
boundModuleRequire.resolve = function(moduleName) {
635
var ret = this._moduleNameToPath(sourceModulePath, moduleName);
636
if (!ret) {
637
throw new Error('Module(' + moduleName + ') not found!');
638
}
639
return ret;
640
}.bind(this);
641
boundModuleRequire.generateMock = this._generateMock.bind(
642
this,
643
sourceModulePath
644
);
645
boundModuleRequire.requireMock = this.requireMock.bind(
646
this,
647
sourceModulePath
648
);
649
boundModuleRequire.requireActual = this.requireModule.bind(
650
this,
651
sourceModulePath
652
);
653
654
return boundModuleRequire;
655
};
656
657
/**
658
* Returns a map from modulePath -> coverageInfo, where coverageInfo is of the
659
* structure returned By CoverageCollector.extractRuntimeCoverageInfo()
660
*/
661
Loader.prototype.getAllCoverageInfo = function() {
662
if (!this._config.collectCoverage) {
663
throw new Error(
664
'config.collectCoverage was not set, so no coverage info has been ' +
665
'(or will be) collected!'
666
);
667
}
668
669
var coverageInfo = {};
670
for (var filePath in this._coverageCollectors) {
671
coverageInfo[filePath] =
672
this._coverageCollectors[filePath].extractRuntimeCoverageInfo();
673
}
674
return coverageInfo;
675
};
676
677
Loader.prototype.getCoverageForFilePath = function(filePath) {
678
if (!this._config.collectCoverage) {
679
throw new Error(
680
'config.collectCoverage was not set, so no coverage info has been ' +
681
'(or will be) collected!'
682
);
683
}
684
685
return (
686
this._coverageCollectors.hasOwnProperty(filePath)
687
? this._coverageCollectors[filePath].extractRuntimeCoverageInfo()
688
: null
689
);
690
};
691
692
/**
693
* Given the path to some file, find the path to all other files that it
694
* *directly* depends on.
695
*
696
* @param {String} modulePath Absolute path to the module in question
697
* @return {Array<String>} List of paths to files that the given module directly
698
* depends on.
699
*/
700
Loader.prototype.getDependenciesFromPath = function(modulePath) {
701
var resource = this._resourceMap.getResourceByPath(modulePath);
702
if (!resource) {
703
throw new Error('Unknown modulePath: ' + modulePath);
704
}
705
706
if (resource.type === 'ProjectConfiguration'
707
|| resource.type === 'Resource') {
708
throw new Error(
709
'Could not extract dependency information from this type of file!'
710
);
711
}
712
713
return this._getDependencyPathsFromResource(resource);
714
};
715
716
/**
717
* Given the path to some module, find all other files that *directly* depend on
718
* it.
719
*
720
* @param {String} modulePath Absolute path to the module in question
721
* @return {Array<String>} List of paths to files that directly depend on the
722
* given module path.
723
*/
724
Loader.prototype.getDependentsFromPath = function(modulePath) {
725
if (this._reverseDependencyMap === null) {
726
var resourceMap = this._resourceMap;
727
var reverseDepMap = this._reverseDependencyMap = {};
728
var allResources = resourceMap.getAllResources();
729
for (var resourceID in allResources) {
730
var resource = allResources[resourceID];
731
if (resource.type === 'ProjectConfiguration'
732
|| resource.type === 'Resource') {
733
continue;
734
}
735
736
var dependencyPaths = this._getDependencyPathsFromResource(resource);
737
for (var i = 0; i < dependencyPaths.length; i++) {
738
var requiredModulePath = dependencyPaths[i];
739
if (!reverseDepMap.hasOwnProperty(requiredModulePath)) {
740
reverseDepMap[requiredModulePath] = {};
741
}
742
reverseDepMap[requiredModulePath][resource.path] = true;
743
}
744
}
745
}
746
747
var reverseDeps = this._reverseDependencyMap[modulePath];
748
return reverseDeps ? Object.keys(reverseDeps) : [];
749
};
750
751
/**
752
* Given a module name, return the mock version of said module.
753
*
754
* @param string currPath The path of the file that is attempting to
755
* resolve the module
756
* @param string moduleName The name of the module to be resolved
757
* @return object
758
*/
759
Loader.prototype.requireMock = function(currPath, moduleName) {
760
var moduleID = this._getNormalizedModuleID(currPath, moduleName);
761
762
if (this._explicitlySetMocks.hasOwnProperty(moduleID)) {
763
return this._explicitlySetMocks[moduleID];
764
}
765
766
// Look in the node-haste resource map
767
var manualMockResource = this._getResource('JSMock', moduleName);
768
var modulePath;
769
if (manualMockResource) {
770
modulePath = manualMockResource.path;
771
} else {
772
modulePath = this._moduleNameToPath(currPath, moduleName);
773
774
// If the actual module file has a __mocks__ dir sitting immediately next to
775
// it, look to see if there is a manual mock for this file in that dir.
776
//
777
// The reason why node-haste isn't good enough for this is because
778
// node-haste only handles manual mocks for @providesModules well. Otherwise
779
// it's not good enough to disambiguate something like the following
780
// scenario:
781
//
782
// subDir1/MyModule.js
783
// subDir1/__mocks__/MyModule.js
784
// subDir2/MyModule.js
785
// subDir2/__mocks__/MyModule.js
786
//
787
// Where some other module does a relative require into each of the
788
// respective subDir{1,2} directories and expects a manual mock
789
// corresponding to that particular MyModule.js file.
790
var moduleDir = path.dirname(modulePath);
791
var moduleFileName = path.basename(modulePath);
792
var potentialManualMock = path.join(moduleDir, '__mocks__', moduleFileName);
793
if (fs.existsSync(potentialManualMock)) {
794
manualMockResource = true;
795
modulePath = potentialManualMock;
796
}
797
}
798
799
if (this._mockRegistry.hasOwnProperty(modulePath)) {
800
return this._mockRegistry[modulePath];
801
}
802
803
if (manualMockResource) {
804
var moduleObj = {
805
exports: {},
806
__filename: modulePath
807
};
808
this._execModule(moduleObj);
809
this._mockRegistry[modulePath] = moduleObj.exports;
810
} else {
811
// Look for a real module to generate an automock from
812
this._mockRegistry[modulePath] = this._generateMock(
813
currPath,
814
moduleName
815
);
816
}
817
818
return this._mockRegistry[modulePath];
819
};
820
821
/**
822
* Given a module name, return the *real* (un-mocked) version of said
823
* module.
824
*
825
* @param string currPath The path of the file that is attempting to
826
* resolve the module
827
* @param string moduleName The name of the module to be resolved
828
* @param bool bypassRegistryCache Whether we should read from/write to the
829
* module registry. Fuck this arg.
830
* @return object
831
*/
832
Loader.prototype.requireModule = function(currPath, moduleName,
833
bypassRegistryCache) {
834
var modulePath;
835
var moduleID = this._getNormalizedModuleID(currPath, moduleName);
836
837
// I don't like this behavior as it makes the module system's mocking
838
// rules harder to understand. Would much prefer that mock state were
839
// either "on" or "off" -- rather than "automock on", "automock off",
840
// "automock off -- but there's a manual mock, so you get that if you ask
841
// for the module and one doesnt exist", or "automock off -- but theres a
842
// useAutoMock: false entry in the package.json -- and theres a manual
843
// mock -- and the module is listed in the unMockList in the test config
844
// -- soooo...uhh...fuck I lost track".
845
//
846
// To simplify things I'd like to move to a system where tests must
847
// explicitly call .mock() on a module to recieve the mocked version if
848
// automocking is off. If a manual mock exists, that is used. Otherwise
849
// we fall back to the automocking system to generate one for you.
850
//
851
// The only reason we're supporting this in jest for now is because we
852
// have some tests that depend on this behavior. I'd like to clean this
853
// up at some point in the future.
854
var manualMockResource = null;
855
var moduleResource = null;
856
moduleResource = this._getResource('JS', moduleName);
857
manualMockResource = this._getResource('JSMock', moduleName);
858
if (!moduleResource
859
&& manualMockResource
860
&& manualMockResource.path !== this._isCurrentlyExecutingManualMock
861
&& this._explicitShouldMock[moduleID] !== false) {
862
modulePath = manualMockResource.path;
863
}
864
865
if (!modulePath) {
866
modulePath = this._moduleNameToPath(currPath, moduleName);
867
}
868
869
if (NODE_CORE_MODULES[moduleName]) {
870
return require(moduleName);
871
}
872
873
// Always natively require the jasmine runner.
874
if (modulePath.indexOf(VENDOR_PATH) === 0) {
875
return require(modulePath);
876
}
877
878
if (!modulePath) {
879
throw new Error(
880
'Cannot find module \'' + moduleName + '\' from \'' + currPath +
881
'\''
882
);
883
}
884
885
var moduleObj;
886
if (modulePath && this._builtInModules.hasOwnProperty(modulePath)) {
887
moduleObj = this._builtInModules[modulePath](currPath);
888
}
889
890
if (!moduleObj && !bypassRegistryCache) {
891
moduleObj = this._moduleRegistry[modulePath];
892
}
893
if (!moduleObj) {
894
// We must register the pre-allocated module object first so that any
895
// circular dependencies that may arise while evaluating the module can
896
// be satisfied.
897
moduleObj = {
898
__filename: modulePath,
899
exports: {}
900
};
901
902
if (!bypassRegistryCache) {
903
this._moduleRegistry[modulePath] = moduleObj;
904
}
905
906
// Good ole node...
907
if (path.extname(modulePath) === '.json') {
908
moduleObj.exports = this._environment.global.JSON.parse(fs.readFileSync(
909
modulePath,
910
'utf8'
911
));
912
} else if(path.extname(modulePath) === '.node') {
913
// Just do a require if it is a native node module
914
moduleObj.exports = require(modulePath);
915
} else {
916
this._execModule(moduleObj);
917
}
918
}
919
920
return moduleObj.exports;
921
};
922
923
/**
924
* Given a module name, return either the real module or the mock version of
925
* that module -- depending on the mocking state of the loader (and, perhaps
926
* the mocking state for the requested module).
927
*
928
* @param string currPath The path of the file that is attempting to
929
* resolve the module
930
* @param string moduleName The name of the module to be resolved
931
* @return object
932
*/
933
Loader.prototype.requireModuleOrMock = function(currPath, moduleName) {
934
if (this._shouldMock(currPath, moduleName)) {
935
return this.requireMock(currPath, moduleName);
936
} else {
937
return this.requireModule(currPath, moduleName);
938
}
939
};
940
941
Loader.prototype.getJestRuntime = function(dir) {
942
return this._builtInModules['jest-runtime'](dir).exports;
943
};
944
945
/**
946
* Clears all cached module objects. This allows one to reset the state of
947
* all modules in the system. It will reset (read: clear) the export objects
948
* for all evaluated modules and mocks.
949
*
950
* @return void
951
*/
952
Loader.prototype.resetModuleRegistry = function() {
953
this._mockRegistry = {};
954
this._moduleRegistry = {};
955
this._builtInModules = {
956
'jest-runtime': function(currPath) {
957
var jestRuntime = {
958
exports: {
959
addMatchers: function(matchers) {
960
var jasmine = this._environment.global.jasmine;
961
var spec = jasmine.getEnv().currentSpec;
962
spec.addMatchers(matchers);
963
}.bind(this),
964
965
autoMockOff: function() {
966
this._shouldAutoMock = false;
967
return jestRuntime.exports;
968
}.bind(this),
969
970
autoMockOn: function() {
971
this._shouldAutoMock = true;
972
return jestRuntime.exports;
973
}.bind(this),
974
975
clearAllTimers: function() {
976
this._environment.fakeTimers.clearAllTimers();
977
}.bind(this),
978
979
dontMock: function(moduleName) {
980
var moduleID = this._getNormalizedModuleID(currPath, moduleName);
981
this._explicitShouldMock[moduleID] = false;
982
return jestRuntime.exports;
983
}.bind(this),
984
985
getTestEnvData: function() {
986
var frozenCopy = {};
987
// Make a shallow copy only because a deep copy seems like
988
// overkill..
989
Object.keys(this._config.testEnvData).forEach(function(key) {
990
frozenCopy[key] = this._config.testEnvData[key];
991
}, this);
992
Object.freeze(frozenCopy);
993
return frozenCopy;
994
}.bind(this),
995
996
genMockFromModule: function(moduleName) {
997
return this._generateMock(
998
this._currentlyExecutingModulePath,
999
moduleName
1000
);
1001
}.bind(this),
1002
1003
genMockFunction: function() {
1004
return moduleMocker.getMockFunction();
1005
},
1006
1007
mock: function(moduleName) {
1008
var moduleID = this._getNormalizedModuleID(currPath, moduleName);
1009
this._explicitShouldMock[moduleID] = true;
1010
return jestRuntime.exports;
1011
}.bind(this),
1012
1013
resetModuleRegistry: function() {
1014
var globalMock;
1015
for (var key in this._environment.global) {
1016
globalMock = this._environment.global[key];
1017
if ((typeof globalMock === 'object' && globalMock !== null)
1018
|| typeof globalMock === 'function') {
1019
globalMock._isMockFunction && globalMock.mockClear();
1020
}
1021
}
1022
1023
if (this._environment.global.mockClearTimers) {
1024
this._environment.global.mockClearTimers();
1025
}
1026
1027
this.resetModuleRegistry();
1028
1029
return jestRuntime.exports;
1030
}.bind(this),
1031
1032
runAllTicks: function() {
1033
this._environment.fakeTimers.runAllTicks();
1034
}.bind(this),
1035
1036
runAllTimers: function() {
1037
this._environment.fakeTimers.runAllTimers();
1038
}.bind(this),
1039
1040
runOnlyPendingTimers: function() {
1041
this._environment.fakeTimers.runOnlyPendingTimers();
1042
}.bind(this),
1043
1044
setMock: function(moduleName, moduleExports) {
1045
var moduleID = this._getNormalizedModuleID(currPath, moduleName);
1046
this._explicitShouldMock[moduleID] = true;
1047
this._explicitlySetMocks[moduleID] = moduleExports;
1048
return jestRuntime.exports;
1049
}.bind(this),
1050
1051
useFakeTimers: function() {
1052
this._environment.fakeTimers.useFakeTimers();
1053
}.bind(this),
1054
1055
useRealTimers: function() {
1056
this._environment.fakeTimers.useRealTimers();
1057
}.bind(this)
1058
}
1059
};
1060
1061
// This is a pretty common API to use in many tests, so this is just a
1062
// shorter alias to make it less annoying to type out each time.
1063
jestRuntime.exports.genMockFn = jestRuntime.exports.genMockFunction;
1064
1065
return jestRuntime;
1066
}.bind(this),
1067
1068
// This is a legacy API that will soon be deprecated.
1069
// Don't use it for new stuff as it will go away soon!
1070
'node-haste': function() {
1071
return {
1072
exports: {
1073
// Do not use this API -- it is deprecated and will go away very soon!
1074
getResourceMap: function() {
1075
return this._resourceMap;
1076
}.bind(this)
1077
}
1078
};
1079
}.bind(this),
1080
1081
// This is a legacy API that will soon be deprecated.
1082
// Don't use it for new stuff as it will go away soon!
1083
'mocks': function(currPath) {
1084
var mocks = {
1085
exports: {
1086
generateFromMetadata: moduleMocker.generateFromMetadata,
1087
getMetadata: moduleMocker.getMetadata,
1088
getMockFunction: function() {
1089
return this.requireModule(
1090
currPath,
1091
'jest-runtime'
1092
).genMockFn();
1093
}.bind(this),
1094
}
1095
};
1096
mocks.exports.getMockFn = mocks.exports.getMockFunction;
1097
return mocks;
1098
}.bind(this),
1099
1100
// This is a legacy API that will soon be deprecated.
1101
// Don't use it for new stuff as it will go away soon!
1102
'mock-modules': function(currPath) {
1103
var mockModules = {
1104
exports: {
1105
dontMock: function(moduleName) {
1106
this.requireModule(
1107
currPath,
1108
'jest-runtime'
1109
).dontMock(moduleName);
1110
return mockModules.exports;
1111
}.bind(this),
1112
1113
mock: function(moduleName) {
1114
this.requireModule(
1115
currPath,
1116
'jest-runtime'
1117
).mock(moduleName);
1118
return mockModules.exports;
1119
}.bind(this),
1120
1121
autoMockOff: function() {
1122
this.requireModule(
1123
currPath,
1124
'jest-runtime'
1125
).autoMockOff();
1126
return mockModules.exports;
1127
}.bind(this),
1128
1129
autoMockOn: function() {
1130
this.requireModule(
1131
currPath,
1132
'jest-runtime'
1133
).autoMockOn();
1134
return mockModules.exports;
1135
}.bind(this),
1136
1137
// TODO: This is such a bad name, we should rename it to
1138
// `resetModuleRegistry()` -- or anything else, really
1139
dumpCache: function() {
1140
this.requireModule(
1141
currPath,
1142
'jest-runtime'
1143
).resetModuleRegistry();
1144
return mockModules.exports;
1145
}.bind(this),
1146
1147
setMock: function(moduleName, moduleExports) {
1148
this.requireModule(
1149
currPath,
1150
'jest-runtime'
1151
).setMock(moduleName, moduleExports);
1152
return mockModules.exports;
1153
}.bind(this),
1154
1155
// wtf is this shit?
1156
hasDependency: function(moduleAName, moduleBName) {
1157
var traversedModules = {};
1158
1159
var self = this;
1160
function _recurse(moduleAName, moduleBName) {
1161
traversedModules[moduleAName] = true;
1162
if (moduleAName === moduleBName) {
1163
return true;
1164
}
1165
var moduleAResource = self._getResource('JS', moduleAName);
1166
return !!(
1167
moduleAResource
1168
&& moduleAResource.requiredModules
1169
&& moduleAResource.requiredModules.some(function(dep) {
1170
return !traversedModules[dep] && _recurse(dep, moduleBName);
1171
})
1172
);
1173
}
1174
1175
return _recurse(moduleAName, moduleBName);
1176
}.bind(this),
1177
1178
generateMock: function(moduleName) {
1179
return this.requireModule(
1180
currPath,
1181
'jest-runtime'
1182
).genMockFromModule(moduleName);
1183
}.bind(this),
1184
1185
useActualTimers: function() {
1186
this.requireModule(
1187
currPath,
1188
'jest-runtime'
1189
).useActualTimers();
1190
}.bind(this),
1191
1192
/**
1193
* Load actual module without reading from or writing to module
1194
* exports registry. This method's name is devastatingly misleading.
1195
* :(
1196
*/
1197
loadActualModule: function(moduleName) {
1198
return this.requireModule(
1199
this._currentlyExecutingModulePath,
1200
moduleName,
1201
true // yay boolean args!
1202
);
1203
}.bind(this)
1204
}
1205
};
1206
return mockModules;
1207
}.bind(this)
1208
};
1209
};
1210
1211
module.exports = Loader;
1212
1213