From 420ee4c9d3fae67370a2d277fa860d245d72dfda Mon Sep 17 00:00:00 2001 From: Mark Skelton Date: Mon, 19 Jun 2023 13:24:12 -0500 Subject: [PATCH] feat: Add `expect-expect` rule --- README.md | 1 + docs/rules/expect-expect.md | 29 ++++++++++++++++++++ src/index.ts | 3 +++ src/rules/expect-expect.ts | 48 +++++++++++++++++++++++++++++++++ test/spec/expect-expect.spec.ts | 21 +++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 docs/rules/expect-expect.md create mode 100644 src/rules/expect-expect.ts create mode 100644 test/spec/expect-expect.spec.ts diff --git a/README.md b/README.md index 9a0e65e3..feb32c5b 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ command line option.\ | ✔ | 🔧 | 💡 | Rule | Description | | :-: | :-: | :-: | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | +| ✔ | | | [expect-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/expect-expect.md) | Enforce assertion to be made in a test body | | ✔ | | | [max-nested-describe](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/max-nested-describe.md) | Enforces a maximum depth to nested describe calls | | ✔ | 🔧 | | [missing-playwright-await](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/missing-playwright-await.md) | Enforce Playwright APIs to be awaited | | ✔ | | | [no-conditional-in-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-in-test.md) | Disallow conditional logic in tests | diff --git a/docs/rules/expect-expect.md b/docs/rules/expect-expect.md new file mode 100644 index 00000000..9536baf2 --- /dev/null +++ b/docs/rules/expect-expect.md @@ -0,0 +1,29 @@ +# Enforce assertion to be made in a test body (`expect-expect`) + +Ensure that there is at least one `expect` call made in a test. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```javascript +test('should be a test', () => { + console.log('no assertion'); +}); + +test('should assert something', () => {}); +``` + +Examples of **correct** code for this rule: + +```javascript +test('should be a test', async () => { + await expect(page).toHaveTitle('foo'); +}); + +test('should work with callbacks/async', async () => { + await test.step('step 1', async () => { + await expect(page).toHaveTitle('foo'); + }); +}); +``` diff --git a/src/index.ts b/src/index.ts index 073c3a6f..e6d1b160 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import expectExpect from './rules/expect-expect'; import maxNestedDescribe from './rules/max-nested-describe'; import missingPlaywrightAwait from './rules/missing-playwright-await'; import noConditionalInTest from './rules/no-conditional-in-test'; @@ -30,6 +31,7 @@ const recommended = { plugins: ['playwright'], rules: { 'no-empty-pattern': 'off', + 'playwright/expect-expect': 'warn', 'playwright/max-nested-describe': 'warn', 'playwright/missing-playwright-await': 'error', 'playwright/no-conditional-in-test': 'warn', @@ -87,6 +89,7 @@ export = { recommended, }, rules: { + 'expect-expect': expectExpect, 'max-nested-describe': maxNestedDescribe, 'missing-playwright-await': missingPlaywrightAwait, 'no-conditional-in-test': noConditionalInTest, diff --git a/src/rules/expect-expect.ts b/src/rules/expect-expect.ts new file mode 100644 index 00000000..f21c1bd1 --- /dev/null +++ b/src/rules/expect-expect.ts @@ -0,0 +1,48 @@ +import { Rule } from 'eslint'; +import * as ESTree from 'estree'; +import { isExpectCall, isTest } from '../utils/ast'; + +export default { + create(context) { + const unchecked: ESTree.CallExpression[] = []; + + function checkExpressions(nodes: ESTree.Node[]) { + for (const node of nodes) { + const index = + node.type === 'CallExpression' ? unchecked.indexOf(node) : -1; + + if (index !== -1) { + unchecked.splice(index, 1); + break; + } + } + } + + return { + CallExpression(node) { + if (isTest(node)) { + unchecked.push(node); + } else if (isExpectCall(node)) { + checkExpressions(context.getAncestors()); + } + }, + 'Program:exit'() { + unchecked.forEach((node) => { + context.report({ messageId: 'noAssertions', node }); + }); + }, + }; + }, + meta: { + docs: { + category: 'Best Practices', + description: 'Enforce assertion to be made in a test body', + recommended: true, + url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/expect-expect.md', + }, + messages: { + noAssertions: 'Test has no assertions', + }, + type: 'problem', + }, +} as Rule.RuleModule; diff --git a/test/spec/expect-expect.spec.ts b/test/spec/expect-expect.spec.ts new file mode 100644 index 00000000..85c1dcf7 --- /dev/null +++ b/test/spec/expect-expect.spec.ts @@ -0,0 +1,21 @@ +import rule from '../../src/rules/expect-expect'; +import { runRuleTester } from '../utils/rule-tester'; + +runRuleTester('expect-expect', rule, { + invalid: [ + { + code: 'test("should fail", () => {});', + errors: [{ messageId: 'noAssertions' }], + }, + { + code: 'test.skip("should fail", () => {});', + errors: [{ messageId: 'noAssertions' }], + }, + ], + valid: [ + 'foo();', + '["bar"]();', + 'testing("will test something eventually", () => {})', + 'test("should pass", () => expect(true).toBeDefined())', + ], +});