UNPKG

jest-mock-external-components

Version:

Mock external React components with jest

270 lines (207 loc) 9.19 kB
"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