From 047efae5d32d424bbddd5cb35823519dc7aa5d66 Mon Sep 17 00:00:00 2001 From: David Kaufmann Date: Mon, 25 Apr 2022 23:46:21 +0200 Subject: [PATCH 1/5] feat: Add 'no-empty-tests' rule --- index.js | 1 + lib/config/recommended.js | 1 + lib/rules/no-empty-tests.js | 90 +++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 lib/rules/no-empty-tests.js diff --git a/index.js b/index.js index 76a53172..fea987d4 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ module.exports = { 'require-data-selectors': require('./lib/rules/require-data-selectors'), 'no-force': require('./lib/rules/no-force'), 'no-pause': require('./lib/rules/no-pause'), + 'no-empty-tests': require('./lib/rules/no-empty-tests'), }, configs: { recommended: require('./lib/config/recommended'), diff --git a/lib/config/recommended.js b/lib/config/recommended.js index 1ff0d0f4..4a19e119 100644 --- a/lib/config/recommended.js +++ b/lib/config/recommended.js @@ -9,5 +9,6 @@ module.exports = { 'cypress/no-assigning-return-values': 'error', 'cypress/no-unnecessary-waiting': 'error', 'cypress/no-async-tests': 'error', + 'cypress/no-empty-tests': 'error', }, } diff --git a/lib/rules/no-empty-tests.js b/lib/rules/no-empty-tests.js new file mode 100644 index 00000000..de9ab611 --- /dev/null +++ b/lib/rules/no-empty-tests.js @@ -0,0 +1,90 @@ +'use strict' + +module.exports = { + meta: { + docs: { + description: 'Checks for empty tests', + category: 'Possibly Errors', + recommended: 'error', + }, + fixable: 'code', + hasSuggestions: true, + messages: { + unexpected: 'Do not keep empty tests', + skipTest: 'Skip the test', + removeTest: 'Remove the test', + }, + }, + + create (context) { + + function addSkip (node) { + return (fixer) => fixer.replaceText(node.callee, 'it.skip') + } + + function removeTest (node) { + return (fixer) => fixer.remove(node) + } + + return { + CallExpression (node) { + if (isEmptyTest(node) && !areParentsSkipped(node)) { + return context.report({ + node, + messageId: 'unexpected', + fix: addSkip(node), + suggest: [ + { + messageId: 'skipTest', + fix: addSkip(node), + }, + { + messageId: 'removeTest', + fix: removeTest(node), + }, + ], + }) + } + }, + } + }, +} + +const checkNode = (names) => { + return (node) => { + return node.type === 'CallExpression' && ( + (node.callee.type === 'MemberExpression' && + node.callee.object.type === 'Identifier' && + names.includes(node.callee.object.name)) || + (node.callee.type === 'Identifier' && names.includes(node.callee.name))) + } +} + +const isTest = checkNode(['test', 'it']) +const isGroup = checkNode(['describe', 'context']) + +function isEmptyTest (node) { + return isTest(node) && !isSkipped(node) && isFunctionEmpty(node) +} + +function isFunctionEmpty (node) { + return node.arguments[1].body.body.length === 0 +} + +function isSkipped (node) { + return node.type === 'CallExpression' && + node.callee.type === 'MemberExpression' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'skip' +} + +function areParentsSkipped (node) { + while (node.parent) { + node = node.parent + if (isGroup(node) && isSkipped(node)) { + return true + } + } + + return false +} From 1454e31c7c6a9e7387a4ed3a100d3743773f45e8 Mon Sep 17 00:00:00 2001 From: David Kaufmann Date: Sat, 23 Apr 2022 11:02:28 +0200 Subject: [PATCH 2/5] test: Implement tests for 'no-empty-tests' rule --- tests/lib/rules/no-empty-tests.js | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/lib/rules/no-empty-tests.js diff --git a/tests/lib/rules/no-empty-tests.js b/tests/lib/rules/no-empty-tests.js new file mode 100644 index 00000000..4bea5f4c --- /dev/null +++ b/tests/lib/rules/no-empty-tests.js @@ -0,0 +1,34 @@ +'use strict' + +const rule = require('../../../lib/rules/no-empty-tests') +const RuleTester = require('eslint').RuleTester + +const ruleTester = new RuleTester() + +const errors = [{ messageId: 'unexpected' }] +const parserOptions = { ecmaVersion: 6 } + +ruleTester.run('no-empty-tests', rule, { + valid: [ + { code: 'foo.bar(\'random empty function\', () => {})', parserOptions }, + { code: 'it.skip(\'a skipped empty test\', () => {} )', parserOptions }, + { code: 'it(\'do something\', () => { cy.dataCy(\'getter\') } )', parserOptions }, + { code: 'describe.skip(\'nested skip\', () => { it(\'empty test\', () => { } ) })', parserOptions }, + { code: 'context.skip(\'nested skip\', () => { it(\'empty test\', () => { } ) })', parserOptions }, + { code: 'describe.skip(\'nested skip\', () => { context(\'nested\', () => { it(\'empty test\', () => { } ) }) })', parserOptions }, + { code: 'test.skip(\'a skipped empty test\', () => {} )', parserOptions }, + { code: 'test(\'do something\', () => { cy.dataCy(\'getter\') } )', parserOptions }, + { code: 'describe.skip(\'nested skip\', () => { test(\'empty test\', () => { } ) })', parserOptions }, + { code: 'context.skip(\'nested skip\', () => { test(\'empty test\', () => { } ) })', parserOptions }, + { code: 'describe.skip(\'nested skip\', () => { context(\'nested\', () => { test(\'empty test\', () => { } ) }) })', parserOptions }, + ], + + invalid: [ + { code: 'it(\'an empty test\', () => {} )', parserOptions, errors }, + { code: 'describe(\'nested\', () => { it(\'empty test\', () => { } ) })', parserOptions, errors }, + { code: 'context(\'nested\', () => { it(\'empty test\', () => { } ) })', parserOptions, errors }, + { code: 'test(\'an empty test\', () => {} )', parserOptions, errors }, + { code: 'describe(\'nested\', () => { test(\'empty test\', () => { } ) })', parserOptions, errors }, + { code: 'context(\'nested\', () => { test(\'empty test\', () => { } ) })', parserOptions, errors }, + ], +}) From 37c17a0181bd543d0e1d8887a3c5b875ad6e6dca Mon Sep 17 00:00:00 2001 From: David Kaufmann Date: Tue, 26 Apr 2022 00:04:20 +0200 Subject: [PATCH 3/5] docs: Update docs for 'no-empty-tests' rule --- README.md | 4 +++- docs/rules/no-empty-tests.md | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 docs/rules/no-empty-tests.md diff --git a/README.md b/README.md index 4ad2b5e6..e4b1561b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ You can add rules: "cypress/assertion-before-screenshot": "warn", "cypress/no-force": "warn", "cypress/no-async-tests": "error", - "cypress/no-pause": "error" + "cypress/no-pause": "error", + "cypress/no-empty-tests": "error" } } ``` @@ -121,6 +122,7 @@ Rules with a check mark (✅) are enabled by default while using the `plugin:cyp | ✅ | [no-assigning-return-values](./docs/rules/no-assigning-return-values.md) | Prevent assigning return values of cy calls | | ✅ | [no-unnecessary-waiting](./docs/rules/no-unnecessary-waiting.md) | Prevent waiting for arbitrary time periods | | ✅ | [no-async-tests](./docs/rules/no-async-tests.md) | Prevent using async/await in Cypress test case | +| ✅ | [no-empty-tests](./docs/rules/no-empty-tests.md) | Prevent the execution of empty tests | | | [no-force](./docs/rules/no-force.md) | Disallow using `force: true` with action commands | | | [assertion-before-screenshot](./docs/rules/assertion-before-screenshot.md) | Ensure screenshots are preceded by an assertion | | | [require-data-selectors](./docs/rules/require-data-selectors.md) | Only allow data-\* attribute selectors (require-data-selectors) | diff --git a/docs/rules/no-empty-tests.md b/docs/rules/no-empty-tests.md new file mode 100644 index 00000000..b6b68d26 --- /dev/null +++ b/docs/rules/no-empty-tests.md @@ -0,0 +1,38 @@ +# Checks if tests are empty (no-empty-tests) + +Empty tests will always pass. +In large testsuites tests might have been initialized but not yet written. +Passing but nevertheless empty tests will suggest functionality has been tested, although it hasn't. +Having empty tests might seem convenient for reasons, it isn't on execution though. +Empty tests should always be deleted or skipped. + +## Rule Details + +This rule aims to prevent the execution of empty tests. + +Examples of **incorrect** code for this rule: + +```js +it('an empty test', () => {} ) +describe('nested', () => { it('empty test', () => { } ) }) +context('nested', () => { it('empty test', () => { } ) }) +test('an empty test', () => {} ) +describe('nested', () => { test('empty test', () => { } ) }) +context('nested', () => { test('empty test', () => { } ) }) +``` + +Examples of **correct** code for this rule: + +```js + foo.bar('random empty function', () => {}) + it.skip('a skipped empty test', () => {} ) + it('do something', () => { cy.dataCy('getter') } ) + describe.skip('nested skip', () => { it('empty test', () => { } ) }) + context.skip('nested skip', () => { it('empty test', () => { } ) }) + describe.skip('nested skip', () => { context('nested', () => { it('empty test', () => { } ) }) }) + test.skip('a skipped empty test', () => {} ) + test('do something', () => { cy.dataCy('getter') } ) + describe.skip('nested skip', () => { test('empty test', () => { } ) }) + context.skip('nested skip', () => { test('empty test', () => { } ) }) + describe.skip('nested skip', () => { context('nested', () => { test('empty test', () => { } ) }) }) +``` \ No newline at end of file From ce3358f6a4b625772ca9aa81f90b07d784a1ad51 Mon Sep 17 00:00:00 2001 From: David Kaufmann Date: Thu, 5 May 2022 23:21:54 +0200 Subject: [PATCH 4/5] refactor: Add node type check --- lib/rules/no-empty-tests.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-empty-tests.js b/lib/rules/no-empty-tests.js index de9ab611..e5cab4f2 100644 --- a/lib/rules/no-empty-tests.js +++ b/lib/rules/no-empty-tests.js @@ -68,7 +68,9 @@ function isEmptyTest (node) { } function isFunctionEmpty (node) { - return node.arguments[1].body.body.length === 0 + return ['ArrowFunctionExpression', 'FunctionExpression', 'Identifier'] + .includes(node.arguments[1].type) && + node.arguments[1].body.body.length === 0 } function isSkipped (node) { From ddffe7afe203a9caabe346b7d12eff6edbbe23ed Mon Sep 17 00:00:00 2001 From: David Kaufmann Date: Thu, 5 May 2022 23:22:50 +0200 Subject: [PATCH 5/5] refactor: Append `.skip` instead of replacement --- lib/rules/no-empty-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-empty-tests.js b/lib/rules/no-empty-tests.js index e5cab4f2..d6b79264 100644 --- a/lib/rules/no-empty-tests.js +++ b/lib/rules/no-empty-tests.js @@ -19,7 +19,7 @@ module.exports = { create (context) { function addSkip (node) { - return (fixer) => fixer.replaceText(node.callee, 'it.skip') + return (fixer) => fixer.insertTextAfter(node.callee, '.skip') } function removeTest (node) {