jest-mock-external-components
Version:
Mock external React components with jest
270 lines (207 loc) • 9.19 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getMocks = getMocks;
var _parser = require("@babel/parser");
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var t = _interopRequireWildcard(require("@babel/types"));
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const createNewIdentifierOrMemberExp = node => {
if (t.isIdentifier(node) || t.isJSXIdentifier(node)) {
return t.identifier(node.name);
}
return t.memberExpression(createNewIdentifierOrMemberExp(node.object), t.identifier(node.property.name));
};
const getMostLeftJSXIdentifier = exp => {
if (t.isJSXIdentifier(exp.object)) {
return exp.object;
}
return getMostLeftJSXIdentifier(exp.object);
};
const getMostLeftIdentifier = exp => {
if (!t.isMemberExpression(exp)) {
return;
}
if (t.isIdentifier(exp.object)) {
return exp.object;
}
return getMostLeftIdentifier(exp.object);
};
const getFullNameOfIdentifier = exp => {
if (t.isIdentifier(exp) || t.isJSXIdentifier(exp)) {
return exp.name;
}
return getFullNameOfIdentifier(exp.object) + `.${exp.property.name}`;
};
const processHocLikeCallExpression = (exp, scope) => {
if (!exp.arguments.length) {
return [undefined, undefined];
} // get first identifier or member exp, for HOC this is most common case, don't want to bother with anothers
const firstArg = exp.arguments.find(a => t.isIdentifier(a) || t.isMemberExpression(a));
if (!firstArg) {
return [undefined, undefined];
}
const identifier = t.isIdentifier(firstArg) ? firstArg : getMostLeftIdentifier(firstArg);
if (!identifier) {
return [undefined, undefined];
}
const binding = scope.getBinding(identifier.name);
if (!binding) {
return [undefined, undefined];
}
if (binding.kind === "module") {
// direct binding to module, return used binding
return [firstArg, binding];
} else {
// import B;
// const A = HOC(B);
// const C = HOC(A);
// <C />
// need to process again
if (!binding.path.isVariableDeclarator() || !(t.isTaggedTemplateExpression(binding.path.node.init) || t.isCallExpression(binding.path.node.init))) {
return [undefined, undefined];
}
const init = binding.path.node.init;
if (!init) {
return [undefined, undefined];
}
if (t.isTaggedTemplateExpression(init) && !t.isCallExpression(init.tag)) {
return [undefined, undefined];
}
let callExp = t.isCallExpression(init) ? init : init.tag; // const A = (0, _someHOC)(_ImportedComponent)(" styles ") - often as result of styled components preprocessors
if (t.isCallExpression(callExp.callee)) {
callExp = callExp.callee;
}
return processHocLikeCallExpression(callExp, binding.path.scope);
}
};
const visitor = {
CallExpression(path, state) {
const callIdentifier = t.isIdentifier(path.node.callee) ? path.node.callee : t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.property) ? path.node.callee.property : undefined;
if (!callIdentifier || !state.alwaysMockIdentifiers.includes(callIdentifier.name)) {
return;
}
let identifierNode;
let identifierBinding;
if (t.isCallExpression(path.node.callee)) {
[identifierNode, identifierBinding] = processHocLikeCallExpression(path.node.callee, path.scope);
}
if (!identifierNode) {
[identifierNode, identifierBinding] = processHocLikeCallExpression(path.node, path.scope);
}
if (!identifierNode || !identifierBinding || identifierBinding.kind !== "module") {
return;
}
const identifierFullName = getFullNameOfIdentifier(identifierNode);
if (state.mockedIdentifiers.includes(identifierFullName)) {
return;
} // if (!state.mockExpression) {
// state.mockExpression = createEmptyMockExpression(identifierBinding.scope);
// }
// pushMockObjExpression(this.mockExpression, identifierFullName, identifierBinding);
state.mocks.push({
identifier: identifierFullName,
path: identifierBinding.path.parent.source.value,
type: identifierBinding.path.isImportDefaultSpecifier() ? "default" : identifierBinding.path.isImportNamespaceSpecifier() ? "namespace" : "name"
});
state.mockedIdentifiers.push(identifierFullName);
},
JSXOpeningElement(path, state) {
let identifierNode;
let identifierBinding; // <A.B.C /> this can be mapped only to imported module
if (t.isJSXMemberExpression(path.node.name)) {
const mostLeftIdentifier = getMostLeftJSXIdentifier(path.node.name);
const binding = path.scope.getBinding(mostLeftIdentifier.name);
if (!binding || binding.kind !== "module") {
return;
}
identifierNode = path.node.name;
identifierBinding = binding;
} else if (t.isJSXIdentifier(path.node.name)) {
const identifier = path.node.name.name;
const binding = path.scope.getBinding(identifier);
if (!binding) {
return;
}
if (binding.kind === "module") {
// direct use if imported component
identifierNode = path.node.name;
identifierBinding = binding;
} else {
// may be hoc
// 1) const Styled = styled(A)``
// 2) const A = HOC(B);
// 3) const C = A.B.C; // member access shorthand
if (!binding.path.isVariableDeclarator()) {
return;
}
const init = binding.path.node.init;
if (init && (t.isIdentifier(init) || t.isMemberExpression(init))) {
identifierNode = init;
const leftIdentifier = getMostLeftIdentifier(identifierNode);
if (leftIdentifier) {
const newBinding = binding.path.scope.getBinding(leftIdentifier.name);
if (newBinding && newBinding.kind === "module") {
identifierBinding = newBinding;
}
}
} else if (init && (t.isTaggedTemplateExpression(init) || t.isCallExpression(init))) {
if (t.isTaggedTemplateExpression(init) && !t.isCallExpression(init.tag)) {
return;
}
let callExp = t.isTaggedTemplateExpression(init) ? init.tag : init;
if (t.isCallExpression(callExp.callee)) {
// const A = (0, _someHOC)(_ImportedComponent)(" styles ") - often as result of styled components preprocessors
// note: need to first process inner call expression, otherwise it may not work - see "Does not import inside in taggle template" test
[identifierNode, identifierBinding] = processHocLikeCallExpression(callExp.callee, binding.path.scope);
} // otherwise process normally
if (!identifierNode && !t.isCallExpression(callExp.callee)) {
[identifierNode, identifierBinding] = processHocLikeCallExpression(callExp, binding.path.scope);
}
}
}
}
if (!identifierNode || !identifierBinding || identifierBinding.kind !== "module") {
return;
}
const identifierFullName = getFullNameOfIdentifier(identifierNode);
if (state.mockedIdentifiers.includes(identifierFullName)) {
return;
} // if (!this.mockExpression) {
// this.mockExpression = createEmptyMockExpression(identifierBinding.scope);
// }
// pushMockObjExpression(this.mockExpression, identifierFullName, identifierBinding);
// this.mockedIdentifiers.push(identifierFullName);
state.mocks.push({
identifier: identifierFullName,
path: identifierBinding.path.parent.source.value,
type: identifierBinding.path.isImportDefaultSpecifier() ? "default" : identifierBinding.path.isImportNamespaceSpecifier() ? "namespace" : "name"
});
state.mockedIdentifiers.push(identifierFullName);
}
};
function getMocks(code, type, alwaysMock = ["styled", "withComponent"]) {
try {
const state = {
mocks: [],
mockedIdentifiers: [],
alwaysMockIdentifiers: alwaysMock
};
const ast = (0, _parser.parse)(code, {
sourceType: "module",
plugins: ["jsx", type, "asyncGenerators", "classProperties", "classPrivateProperties", "classPrivateMethods", ["decorators", {
decoratorsBeforeExport: true
}], "doExpressions", "dynamicImport", "functionBind", "functionSent", "objectRestSpread", "bigInt", "exportDefaultFrom", "exportNamespaceFrom", "importMeta", "optionalCatchBinding", "optionalChaining", "nullishCoalescingOperator"]
});
if (!ast) {
return [];
}
(0, _traverse.default)(ast, visitor, undefined, state);
return state.mocks;
} catch (_unused) {
return [];
}
}
//# sourceMappingURL=get_mocks.js.map