UNPKG

eslint-plugin-jest

Version:
144 lines (115 loc) 5.12 kB
'use strict'; function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } const _require = require('./util'), getDocsUrl = _require.getDocsUrl, isFunction = _require.isFunction; const isThenOrCatch = node => { return node.property && (node.property.name === 'then' || node.property.name === 'catch'); }; const isExpectCallPresentInFunction = body => { if (body.type === 'BlockStatement') { return body.body.find(line => { if (line.type === 'ExpressionStatement') return isExpectCall(line.expression); if (line.type === 'ReturnStatement') return isExpectCall(line.argument); }); } else { return isExpectCall(body); } }; const isExpectCall = expression => { return expression && expression.type === 'CallExpression' && expression.callee.type === 'MemberExpression' && expression.callee.object.type === 'CallExpression' && expression.callee.object.callee.name === 'expect'; }; const reportReturnRequired = (context, node) => { context.report({ loc: { end: { column: node.parent.parent.loc.end.column, line: node.parent.parent.loc.end.line }, start: node.parent.parent.loc.start }, messageId: 'returnPromise', node }); }; const isPromiseReturnedLater = (node, testFunctionBody) => { let promiseName; if (node.parent.parent.type === 'ExpressionStatement') { promiseName = node.parent.parent.expression.callee.object.name; } else if (node.parent.parent.type === 'VariableDeclarator') { promiseName = node.parent.parent.id.name; } const lastLineInTestFunc = testFunctionBody[testFunctionBody.length - 1]; return lastLineInTestFunc.type === 'ReturnStatement' && lastLineInTestFunc.argument.name === promiseName; }; const isTestFunc = node => { return node.type === 'CallExpression' && (node.callee.name === 'it' || node.callee.name === 'test'); }; const getFunctionBody = func => { if (func.body.type === 'BlockStatement') return func.body.body; return func.body; //arrow-short-hand-fn }; const getTestFunction = node => { let parent = node.parent; while (parent) { if (isFunction(parent) && isTestFunc(parent.parent)) { return parent; } parent = parent.parent; } }; const isParentThenOrPromiseReturned = (node, testFunctionBody) => { return testFunctionBody.type === 'CallExpression' || testFunctionBody.type === 'NewExpression' || node.parent.parent.type === 'ReturnStatement' || isPromiseReturnedLater(node, testFunctionBody) || isThenOrCatch(node.parent.parent); }; const verifyExpectWithReturn = (promiseCallbacks, node, context, testFunctionBody) => { promiseCallbacks.some(promiseCallback => { if (promiseCallback && isFunction(promiseCallback)) { if (isExpectCallPresentInFunction(promiseCallback.body) && !isParentThenOrPromiseReturned(node, testFunctionBody)) { reportReturnRequired(context, node); return true; } } }); }; const isAwaitExpression = node => { return node.parent.parent && node.parent.parent.type === 'AwaitExpression'; }; const isHavingAsyncCallBackParam = testFunction => { try { return testFunction.params[0].type === 'Identifier'; } catch (e) { return false; } }; module.exports = { meta: { docs: { url: getDocsUrl(__filename) }, messages: { returnPromise: 'Promise should be returned to test its fulfillment or rejection' } }, create(context) { return { MemberExpression(node) { if (node.type === 'MemberExpression' && isThenOrCatch(node) && node.parent.type === 'CallExpression' && !isAwaitExpression(node)) { const testFunction = getTestFunction(node); if (testFunction && !isHavingAsyncCallBackParam(testFunction)) { const testFunctionBody = getFunctionBody(testFunction); const _node$parent$argument = _slicedToArray(node.parent.arguments, 2), fulfillmentCallback = _node$parent$argument[0], rejectionCallback = _node$parent$argument[1]; // then block can have two args, fulfillment & rejection // then block can have one args, fulfillment // catch block can have one args, rejection // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise verifyExpectWithReturn([fulfillmentCallback, rejectionCallback], node, context, testFunctionBody); } } } }; } };