Skip to content

Commit

Permalink
feat: implement prefer-expect-assertions rule (#43)
Browse files Browse the repository at this point in the history
* feat: implement missing_expect_assertions_call rule

Intial code for missing_expect_assertions_call rule

* style: lint auto fix

* refactor: enhance missing_expect_assertions_call rule

Report error when expect.assertions() call is present but the argument provided is not a number

* refactor: enhance missing_expect_assertions_call rule

Rule will not report any error, if expect.hasAssertions() call is present. After this change, either expect.hasAssertions() or expect.assertions({number of assertions}) is required to pass the rule

* refactor(readability): improving missing-expect-assertions-call rule msg

* refactor: missing-expect-assertions-call rule rename

Rename missing-expect-assertions-call to prefer-expect-assertions

* refactor: remove unnecessary return

* fix: do not add the rule to recommended

* refactor: update test message

* test: adding missing tests and refactoring
  • Loading branch information
tushardhole authored and SimenB committed Jan 9, 2018
1 parent 439c1f1 commit 4effb3c
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const preferToBeNull = require('./rules/prefer_to_be_null');
const preferToBeUndefined = require('./rules/prefer_to_be_undefined');
const preferToHaveLength = require('./rules/prefer_to_have_length');
const validExpect = require('./rules/valid_expect');
const preferExpectAssertions = require('./rules/prefer_expect_assertions');

const snapshotProcessor = require('./processors/snapshot-processor');

Expand Down Expand Up @@ -62,5 +63,6 @@ module.exports = {
'prefer-to-be-undefined': preferToBeUndefined,
'prefer-to-have-length': preferToHaveLength,
'valid-expect': validExpect,
'prefer-expect-assertions': preferExpectAssertions,
},
};
91 changes: 91 additions & 0 deletions rules/__tests__/prefer_expect_assertions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict';

const RuleTester = require('eslint').RuleTester;
const rules = require('../..').rules;

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
},
});

const expectedMsg =
'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression';

ruleTester.run('prefer-expect-assertions', rules['prefer-expect-assertions'], {
invalid: [
{
code: 'it("it1", () => {})',
errors: [
{
message: expectedMsg,
},
],
},
{
code: 'it("it1", () => { foo()})',
errors: [
{
message: expectedMsg,
},
],
},
{
code:
'it("it1", function() {' +
'\n\t\t\tsomeFunctionToDo();' +
'\n\t\t\tsomeFunctionToDo2();\n' +
'\t\t\t})',
errors: [
{
message: expectedMsg,
},
],
},
{
code: 'it("it1", function() {var a = 2;})',
errors: [
{
message: expectedMsg,
},
],
},
{
code: 'it("it1", function() {expect.assertions();})',
errors: [
{
message: expectedMsg,
},
],
},
{
code: 'it("it1", function() {expect.assertions(1,2);})',
errors: [
{
message: expectedMsg,
},
],
},
{
code: 'it("it1", function() {expect.assertions("1");})',
errors: [
{
message: expectedMsg,
},
],
},
],

valid: [
{
code: 'test("it1", () => {expect.assertions(0);})',
},
'test("it1", function() {expect.assertions(0);})',
'test("it1", function() {expect.hasAssertions();})',
'it("it1", function() {expect.assertions(0);})',
'it("it1", function() {\n\t\t\texpect.assertions(1);' +
'\n\t\t\texpect(someValue).toBe(true)\n' +
'\t\t\t})',
'test("it1")',
],
});
81 changes: 81 additions & 0 deletions rules/prefer_expect_assertions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use strict';

const ruleMsg =
'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression';

const validateArguments = expression => {
return (
expression.arguments &&
expression.arguments.length === 1 &&
Number.isInteger(expression.arguments[0].value)
);
};

const isExpectAssertionsOrHasAssertionsCall = expression => {
try {
const expectAssertionOrHasAssertionCall =
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.object.name === 'expect' &&
(expression.callee.property.name === 'assertions' ||
expression.callee.property.name === 'hasAssertions');

if (expression.callee.property.name === 'assertions') {
return expectAssertionOrHasAssertionCall && validateArguments(expression);
}
return expectAssertionOrHasAssertionCall;
} catch (e) {
return false;
}
};

const isTestOrItFunction = node => {
return (
node.type === 'CallExpression' &&
node.callee &&
(node.callee.name === 'it' || node.callee.name === 'test')
);
};

const getFunctionFirstLine = functionBody => {
return functionBody[0] && functionBody[0].expression;
};

const isFirstLineExprStmt = functionBody => {
return functionBody[0] && functionBody[0].type === 'ExpressionStatement';
};

const getTestFunctionBody = node => {
try {
return node.arguments[1].body.body;
} catch (e) {
return undefined;
}
};

const reportMsg = (context, node) => {
context.report({
message: ruleMsg,
node,
});
};

module.exports = context => {
return {
CallExpression(node) {
if (isTestOrItFunction(node)) {
const testFuncBody = getTestFunctionBody(node);
if (testFuncBody) {
if (!isFirstLineExprStmt(testFuncBody)) {
reportMsg(context, node);
} else {
const testFuncFirstLine = getFunctionFirstLine(testFuncBody);
if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) {
reportMsg(context, node);
}
}
}
}
},
};
};

0 comments on commit 4effb3c

Please # to comment.