babel-plugin-jest-hoist
Advanced tools
Comparing version 9.0.3 to 10.0.0
{ | ||
"name": "babel-plugin-jest-hoist", | ||
"version": "9.0.3", | ||
"version": "10.0.0", | ||
"repository": { | ||
@@ -13,12 +13,11 @@ "type": "git", | ||
"babel-preset-es2015": "*", | ||
"jest-cli": "*", | ||
"react": "^0.14.0" | ||
}, | ||
"jest": { | ||
"testEnvironment": "<rootDir>/node_modules/jest-cli/src/environments/NodeEnvironment" | ||
"testEnvironment": "<rootDir>/../../src/environments/NodeEnvironment" | ||
}, | ||
"scripts": { | ||
"prepublish": "npm test", | ||
"test": "jest" | ||
"test": "../../bin/jest.js" | ||
} | ||
} |
@@ -20,3 +20,3 @@ /** | ||
describe('babel-plugin-jest-unmock', () => { | ||
describe('babel-plugin-jest-hoist', () => { | ||
it('hoists disableAutomock call before imports', () => { | ||
@@ -23,0 +23,0 @@ expect(a._isMockFunction).toBe(undefined); |
@@ -20,2 +20,3 @@ /** | ||
import d from '../__test_modules__/d'; | ||
import e from '../__test_modules__/e'; | ||
@@ -28,3 +29,20 @@ // These will all be hoisted above imports | ||
.unmock('../__test_modules__/d'); | ||
jest.mock('../__test_modules__/e', () => { | ||
if (!global.CALLS) { | ||
global.CALLS = 0; | ||
} | ||
global.CALLS++; | ||
return { | ||
_isMock: true, | ||
fn: () => { | ||
// The `jest.mock` transform will allow require, built-ins and globals. | ||
const path = require('path'); | ||
const array = new Array(3); | ||
array[0] = path.sep; | ||
return jest.fn(() => array); | ||
}, | ||
}; | ||
}); | ||
// These will not be hoisted | ||
@@ -35,4 +53,3 @@ jest.unmock('../__test_modules__/a').dontMock('../__test_modules__/b'); | ||
describe('babel-plugin-jest-unmock', () => { | ||
describe('babel-plugin-jest-hoist', () => { | ||
it('hoists react unmock call before imports', () => { | ||
@@ -54,2 +71,23 @@ expect(typeof React).toEqual('object'); | ||
it('hoists mock call with 2 arguments', () => { | ||
const path = require('path'); | ||
expect(e._isMock).toBe(true); | ||
const mockFn = e.fn(); | ||
expect(mockFn()).toEqual([path.sep, undefined, undefined]); | ||
}); | ||
it('only executes the module factories once', () => { | ||
global.CALLS = 0; | ||
require('../__test_modules__/e'); | ||
expect(global.CALLS).toEqual(1); | ||
require('../__test_modules__/e'); | ||
expect(global.CALLS).toEqual(1); | ||
delete global.CALLS; | ||
}); | ||
it('does not hoist dontMock calls before imports', () => { | ||
@@ -56,0 +94,0 @@ expect(Mocked._isMockFunction).toBe(true); |
121
src/index.js
@@ -11,17 +11,114 @@ /** | ||
function invariant(condition, message) { | ||
if (!condition) { | ||
throw new Error('babel-plugin-jest-hoist: ' + message); | ||
} | ||
} | ||
// We allow `jest`, `require`, all default Node.js globals and all ES2015 | ||
// built-ins to be used inside of a `jest.mock` factory. | ||
const WHITELISTED_IDENTIFIERS = { | ||
jest: true, | ||
require: true, | ||
Infinity: true, | ||
NaN: true, | ||
undefined: true, | ||
Object: true, | ||
Function: true, | ||
Boolean: true, | ||
Symbol: true, | ||
Error: true, | ||
EvalError: true, | ||
InternalError: true, | ||
RangeError: true, | ||
ReferenceError: true, | ||
SyntaxError: true, | ||
TypeError: true, | ||
URIError: true, | ||
Number: true, | ||
Math: true, | ||
Date: true, | ||
String: true, | ||
RegExp: true, | ||
Array: true, | ||
Int8Array: true, | ||
Uint8Array: true, | ||
Uint8ClampedArray: true, | ||
Int16Array: true, | ||
Uint16Array: true, | ||
Int32Array: true, | ||
Uint32Array: true, | ||
Float32Array: true, | ||
Float64Array: true, | ||
Map: true, | ||
Set: true, | ||
WeakMap: true, | ||
WeakSet: true, | ||
ArrayBuffer: true, | ||
DataView: true, | ||
JSON: true, | ||
Promise: true, | ||
Generator: true, | ||
GeneratorFunction: true, | ||
Reflect: true, | ||
Proxy: true, | ||
Intl: true, | ||
arguments: true, | ||
}; | ||
Object.keys(global).forEach(name => WHITELISTED_IDENTIFIERS[name] = true); | ||
const JEST_GLOBAL = {name: 'jest'}; | ||
const IDVisitor = { | ||
ReferencedIdentifier(path) { | ||
this.ids.add(path); | ||
}, | ||
}; | ||
const FUNCTIONS = { | ||
mock: { | ||
checkArgs: args => args.length === 1 && args[0].isStringLiteral(), | ||
mock: args => { | ||
if (args.length === 1) { | ||
return args[0].isStringLiteral(); | ||
} else if (args.length === 2) { | ||
const moduleFactory = args[1]; | ||
invariant( | ||
moduleFactory.isFunction(), | ||
'The second argument of `jest.mock` must be a function.' | ||
); | ||
const ids = new Set(); | ||
const parentScope = moduleFactory.parentPath.scope; | ||
moduleFactory.traverse(IDVisitor, {ids}); | ||
for (const id of ids) { | ||
const name = id.node.name; | ||
let found = false; | ||
let scope = id.scope; | ||
while (scope !== parentScope) { | ||
if (scope.bindings[name]) { | ||
found = true; | ||
break; | ||
} | ||
scope = scope.parent; | ||
} | ||
if (!found) { | ||
invariant( | ||
scope.hasGlobal(name) && WHITELISTED_IDENTIFIERS[name], | ||
'The second argument of `jest.mock()` is not allowed to ' + | ||
'reference any outside variables.\n' + | ||
'Invalid variable access: ' + name + '\n' + | ||
'Whitelisted objects: ' + | ||
Object.keys(WHITELISTED_IDENTIFIERS).join(', ') + '.' | ||
); | ||
} | ||
} | ||
return true; | ||
} | ||
return false; | ||
}, | ||
unmock: { | ||
checkArgs: args => args.length === 1 && args[0].isStringLiteral(), | ||
}, | ||
disableAutomock: { | ||
checkArgs: args => args.length === 0, | ||
}, | ||
enableAutomock: { | ||
checkArgs: args => args.length === 0, | ||
}, | ||
unmock: args => args.length === 1 && args[0].isStringLiteral(), | ||
disableAutomock: args => args.length === 0, | ||
enableAutomock: args => args.length === 0, | ||
}; | ||
@@ -41,3 +138,3 @@ | ||
FUNCTIONS[property.node.name] && | ||
FUNCTIONS[property.node.name].checkArgs(expr.get('arguments')) && | ||
FUNCTIONS[property.node.name](expr.get('arguments')) && | ||
( | ||
@@ -44,0 +141,0 @@ object.isIdentifier(JEST_GLOBAL) || |
11133
3
14
315