UNPKG

jest-mock-action-creators

Version:

Easy mock & test redux action creators with dispatch() in your components

180 lines 7.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const defaultConfig = { jestIdentifier: "jest", requireIdentifier: "require", mockIgnoreExpressions: ["mock", "doMock"], }; function plugin({ types: t }, options) { const finalOptions = Object.assign({}, defaultConfig, options); /** * Check if given call expression is jest mocking call * * @param node Node to check * @returns True if node is jest mock call expression */ const isJestMockCallExpression = (node) => { if (!t.isMemberExpression(node.callee)) { return false; } const { object, property } = node.callee; if (!t.isIdentifier(object) || !t.isIdentifier(property)) { return false; } if (object.name !== finalOptions.jestIdentifier || !finalOptions.mockIgnoreExpressions.includes(property.name)) { return false; } return true; }; /** * Check if given call expression is node's require() call * * @param node Node to check * @returns True if node is node's require() call */ const isRequireCallExpression = (node) => { if (!t.isIdentifier(node.callee)) { return false; } return node.callee.name === finalOptions.requireIdentifier; }; /** * Build jest.mock() expression * * @param source Source name to embed into jest.mock() * @returns New jest.mock() expression */ const buildJestMock = (source) => { return t.expressionStatement(t.callExpression(t.memberExpression(t.identifier(finalOptions.jestIdentifier), t.identifier("mock")), [t.stringLiteral(source)])); }; /** * Check if given expression is plugin's mockActionCreators extension or similar * * @param node Node to check * @param funcName Callee name to check * * @returns True if it's mockActionCreators() like call */ const isMockActionCreatorsCallExpression = (node, funcName) => { if (t.isIdentifier(node.callee)) { // mockActionCreators() return node.callee.name === funcName; } else if (t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.property)) { // possiblity: // import * as m from "jest-mock-action-creators"; // m.mockActionCreators(); return node.callee.property.name === funcName; } return false; }; return { pre() { this.mockActionCreatorsName = "mockActionCreators"; this.importIdentifiers = new Map(); this.existingJestMocks = []; }, visitor: { Program(path) { // store program to avoid parent lookup later this.program = path; }, ImportDeclaration(path) { // process import declarations, easiest const specifiers = path.node.specifiers; for (const s of specifiers) { if (t.isImportSpecifier(s)) { // We may rename our function here if (s.imported.name === this.mockActionCreatorsName && s.local.name !== s.imported.name) { this.mockActionCreatorsName = s.local.name; } } this.importIdentifiers.set(s.local.name, path.node.source.value); } }, CallExpression(path) { const node = path.node; if (isRequireCallExpression(node)) { // node's require() stuff const parentNode = path.parent; // not interested with simple require() without any assignment if (!t.isVariableDeclarator(parentNode)) { return; } // Drop non string literals if (!node.arguments[0] || !t.isStringLiteral(node.arguments[0])) { return; } const sourceName = node.arguments[0].value; // const a = require("b"); // Identifier // const { b } = require("c"); // Object pattern const variableId = parentNode.id; if (t.isIdentifier(variableId)) { this.importIdentifiers.set(variableId.name, sourceName); } else if (t.isObjectPattern(variableId)) { const props = variableId.properties; // const { a, b, c, d } = require("a"); for (const prop of props) { if (t.isProperty(prop) && t.isIdentifier(prop.value)) { this.importIdentifiers.set(prop.value.name, sourceName); } } } } else if (isJestMockCallExpression(node)) { // need to remember existing jest.mock() and jest.doMock() to prevent mocking them again const firstArg = node.arguments[0]; // jest.mock() requires string literal as first arg if (!firstArg || !t.isStringLiteral(firstArg)) { return; } this.existingJestMocks.push(firstArg.value); } else if (isMockActionCreatorsCallExpression(node, this.mockActionCreatorsName)) { // Process actually our function const mockIdentifiers = []; for (const arg of node.arguments) { // we're interested in identifiers and member expressions only let name; if (t.isIdentifier(arg)) { name = arg.name; } else if (t.isMemberExpression(arg)) { if (t.isIdentifier(arg.object)) { name = arg.object.name; } } if (!name) { continue; } const importSourceForIdentifier = this.importIdentifiers.get(name); // skip identifiers which don't have import or if import for this identifier was already mocked if (!importSourceForIdentifier || this.existingJestMocks.includes(importSourceForIdentifier)) { continue; } // filter same identifiers if (!mockIdentifiers.includes(name)) { mockIdentifiers.push(name); } } if (mockIdentifiers.length > 0) { // get all sources for identifiers, exclude duplicates const allSourcesToMock = [...this.importIdentifiers] .filter(([identifier,]) => mockIdentifiers.includes(identifier)) .map(([, source]) => source) .filter((s, idx, arr) => !arr.includes(s, idx + 1)); if (this.program) { // create mock records allSourcesToMock.forEach(source => this.program.unshiftContainer("body", buildJestMock(source))); // store new mocks this.existingJestMocks.push(...allSourcesToMock); } } } } } }; } exports.default = plugin; //# sourceMappingURL=babel.js.map