├── .editorconfig ├── .gitattributes ├── .github ├── contributing.md ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── create-ava-rule.js ├── docs ├── new-rule.md └── rules │ ├── assertion-arguments.md │ ├── hooks-order.md │ ├── max-asserts.md │ ├── no-async-fn-without-await.md │ ├── no-duplicate-modifiers.md │ ├── no-identical-title.md │ ├── no-ignored-test-files.md │ ├── no-import-test-files.md │ ├── no-incorrect-deep-equal.md │ ├── no-inline-assertions.md │ ├── no-nested-tests.md │ ├── no-only-test.md │ ├── no-skip-assert.md │ ├── no-skip-test.md │ ├── no-todo-implementation.md │ ├── no-todo-test.md │ ├── no-unknown-modifiers.md │ ├── prefer-async-await.md │ ├── prefer-power-assert.md │ ├── prefer-t-regex.md │ ├── test-title-format.md │ ├── test-title.md │ ├── use-t-throws-async-well.md │ ├── use-t-well.md │ ├── use-t.md │ ├── use-test.md │ └── use-true-false.md ├── index.js ├── license ├── maintaining.md ├── package.json ├── readme.md ├── rules ├── assertion-arguments.js ├── hooks-order.js ├── max-asserts.js ├── no-async-fn-without-await.js ├── no-duplicate-modifiers.js ├── no-identical-title.js ├── no-ignored-test-files.js ├── no-import-test-files.js ├── no-incorrect-deep-equal.js ├── no-inline-assertions.js ├── no-nested-tests.js ├── no-only-test.js ├── no-skip-assert.js ├── no-skip-test.js ├── no-todo-implementation.js ├── no-todo-test.js ├── no-unknown-modifiers.js ├── prefer-async-await.js ├── prefer-power-assert.js ├── prefer-t-regex.js ├── test-title-format.js ├── test-title.js ├── use-t-throws-async-well.js ├── use-t-well.js ├── use-t.js ├── use-test.js └── use-true-false.js ├── test ├── assertion-arguments.js ├── create-ava-rule.js ├── hooks-order.js ├── integration │ ├── eslint-config-ava-tester │ │ ├── .npmrc │ │ ├── index.js │ │ └── package.json │ └── test.js ├── max-asserts.js ├── no-async-fn-without-await.js ├── no-duplicate-modifiers.js ├── no-identical-title.js ├── no-ignored-test-files.js ├── no-import-test-files.js ├── no-incorrect-deep-equal.js ├── no-inline-assertions.js ├── no-nested-tests.js ├── no-only-test.js ├── no-skip-assert.js ├── no-skip-test.js ├── no-todo-implementation.js ├── no-todo-test.js ├── no-unknown-modifiers.js ├── package.js ├── prefer-async-await.js ├── prefer-power-assert.js ├── prefer-t-regex.js ├── test-title-format.js ├── test-title.js ├── use-t-throws-async-well.js ├── use-t-well.js ├── use-t.js ├── use-test.js ├── use-true-false.js └── util.js └── util.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | ## I have an idea for a new rule 4 | 5 | Open an issue with your proposal. Make sure you elaborate on what problem it solves and include fail/pass examples. [(Example)](https://github.com/sindresorhus/eslint-plugin-unicorn/issues/166) 6 | 7 | ## I have an idea for a new rule and I also want to implement it 8 | 9 | First open an issue with your proposal. When the rule is accepted, see the [docs on creating and submitting a new rule](../docs/new-rule.md). 10 | 11 | ## I want to implement a rule from an open issue 12 | 13 | See the [docs on creating and submitting a new rule](../docs/new-rule.md). 14 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | If you're adding a new rule, please follow [these steps](../docs/new-rule.md). 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Install and test eslint-plugin-ava 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | paths-ignore: 8 | - '*.md' 9 | jobs: 10 | nodejs: 11 | name: Node.js 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | node-version: [^18.18] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: npm install --no-audit 23 | - run: npm test 24 | - uses: codecov/codecov-action@v3 25 | 26 | integration: 27 | name: Integration tests 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-node@v4 32 | - run: npm install --no-audit 33 | - run: npm run integration 34 | - uses: codecov/codecov-action@v3 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | yarn.lock 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /create-ava-rule.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isDeepStrictEqual} = require('node:util'); 4 | const espurify = require('espurify'); 5 | const enhance = require('enhance-visitors'); 6 | const util = require('./util'); 7 | 8 | const avaImportDeclarationAsts = [{ 9 | type: 'ImportDeclaration', 10 | specifiers: [ 11 | { 12 | type: 'ImportDefaultSpecifier', 13 | local: { 14 | type: 'Identifier', 15 | name: 'test', 16 | }, 17 | }, 18 | ], 19 | source: { 20 | type: 'Literal', 21 | value: 'ava', 22 | }, 23 | }, { 24 | type: 'ImportDeclaration', 25 | specifiers: [ 26 | { 27 | type: 'ImportSpecifier', 28 | imported: { 29 | type: 'Identifier', 30 | name: 'serial', 31 | }, 32 | local: { 33 | type: 'Identifier', 34 | name: 'test', 35 | }, 36 | }, 37 | ], 38 | source: { 39 | type: 'Literal', 40 | value: 'ava', 41 | }, 42 | }, { 43 | type: 'ImportDeclaration', 44 | specifiers: [ 45 | { 46 | type: 'ImportSpecifier', 47 | imported: { 48 | type: 'Identifier', 49 | name: 'serial', 50 | }, 51 | local: { 52 | type: 'Identifier', 53 | name: 'test', 54 | }, 55 | }, 56 | ], 57 | source: { 58 | type: 'Literal', 59 | value: 'ava', 60 | }, 61 | }, { 62 | type: 'ImportDeclaration', 63 | specifiers: [ 64 | { 65 | type: 'ImportSpecifier', 66 | imported: { 67 | type: 'Identifier', 68 | name: 'serial', 69 | }, 70 | local: { 71 | type: 'Identifier', 72 | name: 'serial', 73 | }, 74 | }, 75 | ], 76 | source: { 77 | type: 'Literal', 78 | value: 'ava', 79 | }, 80 | }]; 81 | 82 | const avaVariableDeclaratorAsts = [{ 83 | type: 'VariableDeclarator', 84 | id: { 85 | type: 'Identifier', 86 | name: 'test', 87 | }, 88 | init: { 89 | type: 'CallExpression', 90 | callee: { 91 | type: 'Identifier', 92 | name: 'require', 93 | }, 94 | arguments: [ 95 | { 96 | type: 'Literal', 97 | value: 'ava', 98 | }, 99 | ], 100 | }, 101 | }, { 102 | type: 'VariableDeclarator', 103 | id: { 104 | type: 'ObjectPattern', 105 | properties: [{ 106 | type: 'Property', 107 | key: { 108 | type: 'Identifier', 109 | name: 'serial', 110 | }, 111 | value: { 112 | type: 'Identifier', 113 | name: 'serial', 114 | }, 115 | kind: 'init', 116 | method: false, 117 | shorthand: true, 118 | computed: false, 119 | }], 120 | }, 121 | init: { 122 | type: 'CallExpression', 123 | callee: { 124 | type: 'Identifier', 125 | name: 'require', 126 | }, 127 | arguments: [ 128 | { 129 | type: 'Literal', 130 | value: 'ava', 131 | }, 132 | ], 133 | }, 134 | }, { 135 | type: 'VariableDeclarator', 136 | id: { 137 | type: 'ObjectPattern', 138 | properties: [{ 139 | type: 'Property', 140 | key: { 141 | type: 'Identifier', 142 | name: 'serial', 143 | }, 144 | value: { 145 | type: 'Identifier', 146 | name: 'test', 147 | }, 148 | kind: 'init', 149 | method: false, 150 | shorthand: false, 151 | computed: false, 152 | }], 153 | }, 154 | init: { 155 | type: 'CallExpression', 156 | callee: { 157 | type: 'Identifier', 158 | name: 'require', 159 | }, 160 | arguments: [ 161 | { 162 | type: 'Literal', 163 | value: 'ava', 164 | }, 165 | ], 166 | }, 167 | }]; 168 | 169 | function isTestFunctionCall(node) { 170 | if (node.type === 'Identifier') { 171 | return node.name === 'test'; 172 | } 173 | 174 | if (node.type === 'MemberExpression') { 175 | return isTestFunctionCall(node.object); 176 | } 177 | 178 | return false; 179 | } 180 | 181 | function getTestModifierNames(node) { 182 | return util.getTestModifiers(node).map(property => property.name); 183 | } 184 | 185 | module.exports = () => { 186 | let isTestFile = false; 187 | let currentTestNode; 188 | 189 | /* eslint quote-props: [2, "as-needed"] */ 190 | const predefinedRules = { 191 | ImportDeclaration(node) { 192 | if (!isTestFile && avaImportDeclarationAsts.some(ast => isDeepStrictEqual(espurify(node), ast))) { 193 | isTestFile = true; 194 | } 195 | }, 196 | VariableDeclarator(node) { 197 | if (!isTestFile && avaVariableDeclaratorAsts.some(ast => isDeepStrictEqual(espurify(node), ast))) { 198 | isTestFile = true; 199 | } 200 | }, 201 | CallExpression(node) { 202 | if (isTestFunctionCall(node.callee)) { 203 | // Entering test function 204 | currentTestNode = node; 205 | } 206 | }, 207 | 'CallExpression:exit'(node) { 208 | if (currentTestNode === node) { 209 | // Leaving test function 210 | currentTestNode = undefined; 211 | } 212 | }, 213 | 'Program:exit'() { 214 | isTestFile = false; 215 | }, 216 | }; 217 | 218 | return { 219 | hasTestModifier: module_ => getTestModifierNames(currentTestNode).includes(module_), 220 | hasNoUtilityModifier() { 221 | const modifiers = getTestModifierNames(currentTestNode); 222 | return !modifiers.includes('before') 223 | && !modifiers.includes('beforeEach') 224 | && !modifiers.includes('after') 225 | && !modifiers.includes('afterEach') 226 | && !modifiers.includes('macro'); 227 | }, 228 | isInTestFile: () => isTestFile, 229 | isInTestNode: () => currentTestNode, 230 | isTestNode: node => currentTestNode === node, 231 | merge: customHandlers => enhance.mergeVisitors([predefinedRules, customHandlers]), 232 | }; 233 | }; 234 | -------------------------------------------------------------------------------- /docs/new-rule.md: -------------------------------------------------------------------------------- 1 | # Creating a new rule 2 | 3 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/new-rule.md) 4 | 5 | ## Prerequisite 6 | 7 | - [Read the ESLint docs on creating a new rule.](https://eslint.org/docs/developer-guide/working-with-rules) 8 | - Look at the commit for how previous rules were added as inspiration. For example, the [`no-async-fn-without-await` rule](https://github.com/avajs/eslint-plugin-ava/commit/a443d7a9c94165f42749938e6b491a7c10749b6c). 9 | 10 | ## Tip 11 | 12 | Use the [`astexplorer` site](https://astexplorer.net) with the `espree` parser and `ESLint v4` transform to interactively create the initial rule implementation. It lets you inspect the full AST as you would get from ESLint and you can even see the result of your auto-fixer implementation. 13 | 14 | ## Steps 15 | 16 | - Go to the `test` directory and duplicate the `no-todo-test.js` file and rename it to the name of your rule. Then write some tests before starting to implement the rule. 17 | - Go to the `rules` directory and duplicate the `no-todo-test.js` file and rename it to the name of your rule. Then start implementing the new rule logic. 18 | - Add the correct [`meta.type`](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics) to the rule. 19 | - Go to the `docs/rules` directory and duplicate the `no-todo-test.md` file and rename it to the name of your rule. Then write some documentation. 20 | - Add the rule in alphabetically sorted order to: 21 | - [The recommended config](https://github.com/avajs/eslint-plugin-ava/blob/0ded4b5c3cd09504e846309760566c9499a24196/index.js#L19) 22 | - [The recommended config in the readme](https://github.com/avajs/eslint-plugin-ava/blame/0ded4b5c3cd09504e846309760566c9499a24196/readme.md#L35) 23 | *(The description should be the same as the heading of the documentation file).* 24 | - Run `$ npm test` to ensure the tests pass. 25 | - Run `$ npm run integration` to run the rules against real projects to ensure your rule does not fail on real-world code. 26 | - Run `$ npm run update:eslint-docs` to update the readme rules list and standardized rule doc title/notices. 27 | - Open a pull request with a title in exactly the format `` Add `rule-name` rule ``, for example, `` Add `no-unused-properties` rule ``. 28 | - The pull request description should include the issue it fixes, for example, `Fixes #123`. 29 | -------------------------------------------------------------------------------- /docs/rules/assertion-arguments.md: -------------------------------------------------------------------------------- 1 | # Enforce passing correct arguments to assertions (`ava/assertion-arguments`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/assertion-arguments.md) 10 | 11 | Enforces passing the right number of arguments to assertion methods like `t.is()`. This rule can optionally also enforce or forbid the use of assertion messages. 12 | 13 | Assertion messages are optional arguments that can be given to any assertion call to improve the error message, should the assertion fail. 14 | 15 | This rule also attempts to enforce passing actual values before expected values. If exactly one of the first two arguments to a two-argument assertion is a static expression such as `{a: 1}`, then the static expression must come second. (`t.regex()` and `t.notRegex()` are excluded from this check, because either their `contents` argument or their `regex` argument could plausibly be the actual or expected value.) If the argument to a one-argument assertion is a binary relation such as `'static' === dynamic`, a similar check is performed on its left- and right-hand sides. Errors of these kinds are usually fixable. 16 | 17 | ## Fail 18 | 19 | ```js 20 | const test = require('ava'); 21 | 22 | test('1', t => { 23 | t.is(value); // Not enough arguments 24 | t.is(value, expected, message, extra); // Too many arguments 25 | t.is(value, expected, false); // Assertion message is not a string 26 | }); 27 | 28 | /* eslint ava/assertion-arguments: ["error", {"message": "always"}] */ 29 | test('2', t => { 30 | t.true(array.includes(value)); 31 | }); 32 | 33 | /* eslint ava/assertion-arguments: ["error", {"message": "never"}] */ 34 | test('3', t => { 35 | t.true(array.includes(value), 'value is not in array'); 36 | }); 37 | ``` 38 | 39 | ## Pass 40 | 41 | ```js 42 | const test = require('ava'); 43 | 44 | test('1', t => { 45 | t.is(value, expected); 46 | t.is(value, expected, message); 47 | }); 48 | 49 | /* eslint ava/assertion-arguments: ["error", {"message": "always"}] */ 50 | test('2', t => { 51 | t.true(array.includes(value), 'value is not in array'); 52 | }); 53 | 54 | /* eslint ava/assertion-arguments: ["error", {"message": "never"}] */ 55 | test('3', t => { 56 | t.true(array.includes(value)); 57 | }); 58 | ``` 59 | 60 | ## Options 61 | 62 | This rule supports the following options: 63 | 64 | `message`: A string which could be either `"always"` or `"never"`. If set to `"always"`, all assertions will need to have an assertion message. If set to `"never"`, no assertion may have an assertion message. If omitted, no reports about the assertion message will be made. 65 | 66 | You can set the option in configuration like this: 67 | 68 | ```js 69 | "ava/assertion-arguments": ["error", {"message": "always"}] 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/rules/hooks-order.md: -------------------------------------------------------------------------------- 1 | # Enforce test hook ordering (`ava/hooks-order`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/hooks-order.md) 10 | 11 | Hooks should be placed before any tests and in the proper semantic order: 12 | 13 | - `test.before(…);` 14 | - `test.after(…);` 15 | - `test.after.always(…);` 16 | - `test.beforeEach(…);` 17 | - `test.afterEach(…);` 18 | - `test.afterEach.always(…);` 19 | - `test(…);` 20 | 21 | This rule is fixable as long as no other code is between the hooks that need to be reordered. 22 | 23 | ## Fail 24 | 25 | ```js 26 | const test = require('ava'); 27 | 28 | test.after(t => { 29 | doFoo(); 30 | }); 31 | 32 | test.before(t => { 33 | doFoo(); 34 | }); 35 | 36 | test('foo', t => { 37 | t.true(true); 38 | }); 39 | ``` 40 | 41 | ```js 42 | const test = require('ava'); 43 | 44 | test('foo', t => { 45 | t.true(true); 46 | }); 47 | 48 | test.before(t => { 49 | doFoo(); 50 | }); 51 | ``` 52 | 53 | ## Pass 54 | 55 | ```js 56 | const test = require('ava'); 57 | 58 | test.before(t => { 59 | doFoo(); 60 | }); 61 | 62 | test.after(t => { 63 | doFoo(); 64 | }); 65 | 66 | test.after.always(t => { 67 | doFoo(); 68 | }); 69 | 70 | test.beforeEach(t => { 71 | doFoo(); 72 | }); 73 | 74 | test.afterEach(t => { 75 | doFoo(); 76 | }); 77 | 78 | test.afterEach.always(t => { 79 | doFoo(); 80 | }); 81 | 82 | test('foo', t => { 83 | t.true(true); 84 | }); 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/rules/max-asserts.md: -------------------------------------------------------------------------------- 1 | # Enforce a limit on the number of assertions in a test (`ava/max-asserts`) 2 | 3 | 🚫 This rule is _disabled_ in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/max-asserts.md) 8 | 9 | Limit the amount of assertions in a test to enforce splitting up large tests into smaller ones. 10 | 11 | Skipped assertions are counted. 12 | 13 | ## Fail 14 | 15 | ```js 16 | /*eslint ava/max-asserts: ["error", 5]*/ 17 | const test = require('ava'); 18 | 19 | test('getSomeObject should define the players\' names', t => { 20 | const object = lib.getSomeObject(); 21 | 22 | t.is(typeof object, 'object'); 23 | t.is(typeof object.player, 'object'); 24 | t.is(object.player.firstName, 'Luke'); 25 | t.is(object.player.lastName, 'Skywalker'); 26 | t.is(typeof object.opponent, 'object'); 27 | t.is(object.opponent.firstName, 'Darth'); 28 | t.is(object.opponent.lastName, 'Vader'); 29 | }); 30 | ``` 31 | 32 | ## Pass 33 | 34 | ```js 35 | const test = require('ava'); 36 | 37 | test('getSomeObject should define the player\'s name', t => { 38 | const object = lib.getSomeObject(); 39 | 40 | t.is(typeof object, 'object'); 41 | t.is(typeof object.player, 'object'); 42 | t.is(object.player.firstName, 'Luke'); 43 | t.is(object.player.lastName, 'Skywalker'); 44 | }); 45 | 46 | test('getSomeObject should define the opponent\'s name', t => { 47 | const object = lib.getSomeObject(); 48 | 49 | t.is(typeof object, 'object'); 50 | t.is(typeof object.opponent, 'object'); 51 | t.is(object.opponent.firstName, 'Darth'); 52 | t.is(object.opponent.lastName, 'Vader'); 53 | }); 54 | ``` 55 | 56 | ## Options 57 | 58 | The rule takes one option, a number, which is the maximum number of assertions for each test. The default is 5. 59 | 60 | You can set the option in configuration like this: 61 | 62 | ```js 63 | "ava/max-asserts": ["error", 5] 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/rules/no-async-fn-without-await.md: -------------------------------------------------------------------------------- 1 | # Ensure that async tests use `await` (`ava/no-async-fn-without-await`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-async-fn-without-await.md) 8 | 9 | AVA comes with built-in support for async functions (async/await). This allows you to write shorter and clearer tests. 10 | 11 | Declaring an async test without using the `await` keyword means that either a Promise is not awaited on as intended, or that the function could have been declared as a regular function, which is confusing and slower. 12 | 13 | This rule will report an error when it finds an async test which does not use the `await` keyword. 14 | 15 | ## Fail 16 | 17 | ```js 18 | const test = require('ava'); 19 | 20 | test('foo', async t => { 21 | return foo().then(res => { 22 | t.is(res, 1); 23 | }); 24 | }); 25 | ``` 26 | 27 | ## Pass 28 | 29 | ```js 30 | const test = require('ava'); 31 | 32 | test('foo', async t => { 33 | t.is(await foo(), 1); 34 | }); 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/rules/no-duplicate-modifiers.md: -------------------------------------------------------------------------------- 1 | # Ensure tests do not have duplicate modifiers (`ava/no-duplicate-modifiers`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-duplicate-modifiers.md) 8 | 9 | Prevent the use of duplicate [test modifiers](https://github.com/avajs/ava/blob/main/docs/01-writing-tests.md). 10 | 11 | ## Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test.only.only(t => {}); 17 | test.serial.serial(t => {}); 18 | test.beforeEach.beforeEach(t => {}); 19 | ``` 20 | 21 | ## Pass 22 | 23 | ```js 24 | const test = require('ava'); 25 | 26 | test.only(t => {}); 27 | test.serial(t => {}); 28 | test.beforeEach(t => {}); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/rules/no-identical-title.md: -------------------------------------------------------------------------------- 1 | # Ensure no tests have the same title (`ava/no-identical-title`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-identical-title.md) 8 | 9 | Disallow tests with identical titles as it makes it hard to differentiate them. 10 | 11 | ## Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test('foo', t => { 17 | t.pass(); 18 | }); 19 | 20 | test('foo', t => { 21 | t.pass(); 22 | }); 23 | ``` 24 | 25 | ## Pass 26 | 27 | ```js 28 | const test = require('ava'); 29 | 30 | test('foo', t => { 31 | t.pass(); 32 | }); 33 | 34 | test('bar', t => { 35 | t.pass(); 36 | }); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/rules/no-ignored-test-files.md: -------------------------------------------------------------------------------- 1 | # Ensure no tests are written in ignored files (`ava/no-ignored-test-files`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-ignored-test-files.md) 8 | 9 | This rule will verify that files which create tests are treated as test files by AVA. It will consider the root of the project to be the closest folder containing a `package.json` file, and will not do anything if it can't find one. Test files in `node_modules` will not be linted as they are ignored by ESLint. 10 | 11 | ## Fail 12 | 13 | ```js 14 | // File: test/_helper.js 15 | // Invalid because a helper. 16 | const test = require('ava'); 17 | 18 | test('foo', t => { 19 | t.pass(); 20 | }); 21 | 22 | // File: lib/foo.js 23 | // Invalid because not a test file. 24 | const test = require('ava'); 25 | 26 | test('foo', t => { 27 | t.pass(); 28 | }); 29 | ``` 30 | 31 | ## Pass 32 | 33 | ```js 34 | // File: test/foo.js 35 | const test = require('ava'); 36 | 37 | test('foo', t => { 38 | t.pass(); 39 | }); 40 | ``` 41 | 42 | ## Options 43 | 44 | This rule supports the following options: 45 | 46 | * `extensions`: an array of extensions of the files that AVA recognizes as test files or helpers. Overrides *both* the `babel.extensions` *and* `extensions` configuration otherwise used by AVA itself. 47 | * `files`: an array of glob patterns to select test files. Overrides the `files` configuration otherwise used by AVA itself. 48 | * `helpers`: an array of glob patterns to select helper files. Overrides the `helpers` configuration otherwise used by AVA itself. 49 | 50 | See also [AVA's configuration](https://github.com/avajs/ava/blob/main/docs/06-configuration.md#options). 51 | 52 | You can set the options like this: 53 | 54 | ```js 55 | "ava/no-ignored-test-files": ["error", {"files": ["lib/**/*.test.js", "utils/**/*.test.js"]}] 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/rules/no-import-test-files.md: -------------------------------------------------------------------------------- 1 | # Ensure no test files are imported anywhere (`ava/no-import-test-files`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-import-test-files.md) 8 | 9 | This rule will verify that you don't import any test files. It will consider the root of the project to be the closest folder containing a `package.json` file, and will not do anything if it can't find one. Test files in `node_modules` will not be linted as they are ignored by ESLint. 10 | 11 | ## Fail 12 | 13 | ```js 14 | // File: src/index.js 15 | // Invalid because *.test.js is considered as a test file. 16 | const tests = require('./index.test.js'); 17 | ``` 18 | 19 | ```js 20 | // File: src/index.js 21 | // Invalid because any files inside __tests__ are considered test files 22 | const tests = require('./__tests__/index.js'); 23 | 24 | test('foo', t => { 25 | t.pass(); 26 | }); 27 | ``` 28 | 29 | ## Pass 30 | 31 | ```js 32 | // File: src/index.js 33 | const sinon = require('sinon'); 34 | 35 | ``` 36 | 37 | ```js 38 | // File: src/index.js 39 | const utils = require('./utils'); 40 | ``` 41 | 42 | ## Options 43 | 44 | This rule supports the following options: 45 | 46 | * `extensions`: an array of extensions of the files that AVA recognizes as test files or helpers. Overrides *both* the `babel.extensions` *and* `extensions` configuration otherwise used by AVA itself. 47 | * `files`: an array of glob patterns to select test files. Overrides the `files` configuration otherwise used by AVA itself. 48 | 49 | See also [AVA's configuration](https://github.com/avajs/ava/blob/main/docs/06-configuration.md#options). 50 | 51 | You can set the options like this: 52 | 53 | ```js 54 | "ava/no-import-test-files": ["error", {"files": ["lib/**/*.test.js", "utils/**/*.test.js"]}] 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/rules/no-incorrect-deep-equal.md: -------------------------------------------------------------------------------- 1 | # Disallow using `deepEqual` with primitives (`ava/no-incorrect-deep-equal`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-incorrect-deep-equal.md) 10 | 11 | The `deepEqual` and `notDeepEqual` assertions are unnecessary when comparing primitives. Use `is` or `not` instead. 12 | 13 | ## Fail 14 | 15 | ```js 16 | t.deepEqual(expression, 'foo'); 17 | t.deepEqual(expression, 1); 18 | t.deepEqual(expression, `foo${bar}`); 19 | t.deepEqual(expression, null); 20 | t.deepEqual(expression, undefined); 21 | t.notDeepEqual(expression, undefined); 22 | ``` 23 | 24 | ## Pass 25 | 26 | ```js 27 | t.is(expression, 'foo'); 28 | 29 | t.deepEqual(expression, otherExpression); 30 | t.deepEqual(expression, {}); 31 | t.deepEqual(expression, []); 32 | t.notDeepEqual(expression, []); 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/rules/no-inline-assertions.md: -------------------------------------------------------------------------------- 1 | # Ensure assertions are not called from inline arrow functions (`ava/no-inline-assertions`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-inline-assertions.md) 10 | 11 | The test implementation should not purely consist of an inline assertion as assertions do not return a value and having them inline also makes the tests less readable. 12 | 13 | This rule is fixable. It will wrap the assertion in braces `{}`. It will not do any whitespace or style changes. 14 | 15 | ## Fail 16 | 17 | ```js 18 | const test = require('ava'); 19 | 20 | test('foo', t => t.true(fn())); 21 | ``` 22 | 23 | ## Pass 24 | 25 | ```js 26 | const test = require('ava'); 27 | 28 | test('foo', t => { 29 | t.true(fn()); 30 | }); 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/rules/no-nested-tests.md: -------------------------------------------------------------------------------- 1 | # Ensure no tests are nested (`ava/no-nested-tests`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-nested-tests.md) 8 | 9 | In AVA, you cannot nest tests, for example, create tests inside of other tests. Doing so will lead to odd behavior. 10 | 11 | ## Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test('foo', t => { 17 | const result = foo(); 18 | t.true(result.foo); 19 | 20 | test('bar', t => { 21 | t.true(result.bar); 22 | }); 23 | }); 24 | ``` 25 | 26 | ## Pass 27 | 28 | ```js 29 | const test = require('ava'); 30 | 31 | test('foo', t => { 32 | const result = foo(); 33 | t.true(result.foo); 34 | }); 35 | 36 | test('bar', t => { 37 | const result = foo(); 38 | t.true(result.bar); 39 | }); 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/rules/no-only-test.md: -------------------------------------------------------------------------------- 1 | # Ensure no `test.only()` are present (`ava/no-only-test`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-only-test.md) 10 | 11 | It's easy to run only one test with `test.only()` and then forget about it. It's visible in the results, but still easily missed. Forgetting to remove `.only`, means only this one test in the whole file will run, and if not caught, can let serious bugs slip into your codebase. 12 | 13 | ## Fail 14 | 15 | ```js 16 | const test = require('ava'); 17 | 18 | test.only('test 1', t => { 19 | t.pass(); 20 | }); 21 | 22 | // test 2 will not run 23 | test('test 2', t => { 24 | t.pass(); 25 | }); 26 | ``` 27 | 28 | ## Pass 29 | 30 | ```js 31 | const test = require('ava'); 32 | 33 | test('test 1', t => { 34 | t.pass(); 35 | }); 36 | 37 | // test 2 will run 38 | test('test 2', t => { 39 | t.pass(); 40 | }); 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/rules/no-skip-assert.md: -------------------------------------------------------------------------------- 1 | # Ensure no assertions are skipped (`ava/no-skip-assert`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-skip-assert.md) 8 | 9 | It's easy to make an assertion skipped with `t.skip.xyz()` and then forget about it. 10 | 11 | ## Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test('some title', t => { 17 | t.skip.is(1, 1); 18 | }); 19 | ``` 20 | 21 | ## Pass 22 | 23 | ```js 24 | const test = require('ava'); 25 | 26 | test('some title', t => { 27 | t.is(1, 1); 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/rules/no-skip-test.md: -------------------------------------------------------------------------------- 1 | # Ensure no tests are skipped (`ava/no-skip-test`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧💡 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 6 | 7 | 8 | 9 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-skip-test.md) 10 | 11 | It's easy to make a test skipped with `test.skip()` and then forget about it. It's visible in the results, but still easily missed. 12 | 13 | ## Fail 14 | 15 | ```js 16 | const test = require('ava'); 17 | 18 | test('foo', t => { 19 | t.pass(); 20 | }); 21 | 22 | test.skip('bar', t => { 23 | t.pass(); 24 | }); 25 | ``` 26 | 27 | ## Pass 28 | 29 | ```js 30 | const test = require('ava'); 31 | 32 | test('foo', t => { 33 | t.pass(); 34 | }); 35 | 36 | test('bar', t => { 37 | t.pass(); 38 | }); 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/rules/no-todo-implementation.md: -------------------------------------------------------------------------------- 1 | # Ensure `test.todo()` is not given an implementation function (`ava/no-todo-implementation`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-todo-implementation.md) 8 | 9 | [`test.todo()`](https://github.com/avajs/ava/blob/main/docs/01-writing-tests.md#test-placeholders-todo) is intended for planning tests. It's not meant to be passed a function to implement the test, and if given one, AVA will throw an error. If you added an implementation, you probably meant to remove the `.todo` modifier. 10 | 11 | ## Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test.todo('title', t => { 17 | // ... 18 | }); 19 | ``` 20 | 21 | ## Pass 22 | 23 | ```js 24 | const test = require('ava'); 25 | 26 | test.todo('title'); 27 | 28 | test('title2', t => { 29 | // ... 30 | }); 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/rules/no-todo-test.md: -------------------------------------------------------------------------------- 1 | # Ensure no `test.todo()` is used (`ava/no-todo-test`) 2 | 3 | ⚠️ This rule _warns_ in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-todo-test.md) 8 | 9 | Disallow the use of `test.todo()`. You might want to do this to only ship features with specs fully written and passing. 10 | 11 | ## Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test.todo('some test'); 17 | ``` 18 | 19 | ## Pass 20 | 21 | ```js 22 | const test = require('ava'); 23 | 24 | test('some test', t => { 25 | // Some implementation 26 | }); 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/rules/no-unknown-modifiers.md: -------------------------------------------------------------------------------- 1 | # Disallow the use of unknown test modifiers (`ava/no-unknown-modifiers`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/no-unknown-modifiers.md) 8 | 9 | Prevent the use of unknown [test modifiers](https://github.com/avajs/ava/blob/main/docs/01-writing-tests.md). 10 | 11 | ## Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test.onlu(t => {}); 17 | test.seril(t => {}); 18 | test.beforeeach(t => {}); 19 | test.unknown(t => {}); 20 | ``` 21 | 22 | ## Pass 23 | 24 | ```js 25 | const test = require('ava'); 26 | 27 | test.only(t => {}); 28 | test.serial(t => {}); 29 | test.beforeEach(t => {}); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/rules/prefer-async-await.md: -------------------------------------------------------------------------------- 1 | # Prefer using async/await instead of returning a Promise (`ava/prefer-async-await`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/prefer-async-await.md) 8 | 9 | AVA comes with built-in support for async functions (async/await). This allows you to write shorter and clearer tests. 10 | 11 | This rule will report an error when it finds a test that returns an expression that looks like a Promise (containing a `.then()` call), which could be simplified by using the async/await syntax. 12 | 13 | ## Fail 14 | 15 | ```js 16 | const test = require('ava'); 17 | 18 | test('foo', t => { 19 | return foo().then(res => { 20 | t.is(res, 1); 21 | }); 22 | }); 23 | ``` 24 | 25 | ## Pass 26 | 27 | ```js 28 | const test = require('ava'); 29 | 30 | test('foo', async t => { 31 | t.is(await foo(), 1); 32 | }); 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/rules/prefer-power-assert.md: -------------------------------------------------------------------------------- 1 | # Enforce the use of the asserts that have no [power-assert](https://github.com/power-assert-js/power-assert) alternative (`ava/prefer-power-assert`) 2 | 3 | 🚫 This rule is _disabled_ in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/prefer-power-assert.md) 8 | 9 | - [`t.assert()`](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#assertvalue-message) 10 | - [`t.deepEqual()`](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#deepequalvalue-expected-message) 11 | - [`t.notDeepEqual()`](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#notdeepequalvalue-expected-message) 12 | - [`t.like()`](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#likevalue-selector-message) 13 | - [`t.throws()`](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#throwsfn-expected-message) 14 | - [`t.notThrows()`](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#notthrowsfn-message) 15 | - [`t.pass()`](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#passmessage) 16 | - [`t.fail()`](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#failmessage) 17 | 18 | Useful for people wanting to fully embrace the power of [power-assert](https://github.com/power-assert-js/power-assert). 19 | 20 | ## Fail 21 | 22 | ```js 23 | const test = require('ava'); 24 | 25 | test('foo', t => { 26 | t.truthy(foo); 27 | t.falsy(foo); 28 | t.true(foo === bar); 29 | t.false(foo === bar); 30 | t.is(foo, bar); 31 | t.not(foo, bar); 32 | t.regex(foo, bar); 33 | t.ifError(error); 34 | }); 35 | ``` 36 | 37 | ## Pass 38 | 39 | ```js 40 | const test = require('ava'); 41 | 42 | test('foo', t => { 43 | t.assert(foo === bar); 44 | t.deepEqual(foo, bar); 45 | t.notDeepEqual(foo, bar); 46 | t.throws(foo); 47 | t.notThrows(bar); 48 | }); 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/rules/prefer-t-regex.md: -------------------------------------------------------------------------------- 1 | # Prefer using `t.regex()` to test regular expressions (`ava/prefer-t-regex`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/prefer-t-regex.md) 10 | 11 | The AVA [`t.regex()` assertion](https://github.com/avajs/ava/blob/main/docs/03-assertions.md#regexcontents-regex-message) can test a string against a regular expression. 12 | 13 | This rule will enforce the use of `t.regex()` instead of manually using `RegExp#test()`, which will make your code look clearer and produce better failure output. 14 | 15 | This rule is fixable. It will replace the use of `RegExp#test()`, `String#match()`, or `String#search()` with `t.regex()`. 16 | 17 | ## Fail 18 | 19 | ```js 20 | const test = require('ava'); 21 | 22 | test('main', t => { 23 | t.true(/\w+/.test('foo')); 24 | }); 25 | ``` 26 | 27 | ```js 28 | const test = require('ava'); 29 | 30 | test('main', t => { 31 | t.truthy('foo'.match(/\w+/)); 32 | }); 33 | ``` 34 | 35 | ## Pass 36 | 37 | ```js 38 | const test = require('ava'); 39 | 40 | test('main', async t => { 41 | t.regex('foo', /\w+/); 42 | }); 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/rules/test-title-format.md: -------------------------------------------------------------------------------- 1 | # Ensure test titles have a certain format (`ava/test-title-format`) 2 | 3 | 🚫 This rule is _disabled_ in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/test-title-format.md) 8 | 9 | This rule is useful when you want to make sure all test titles match a common pattern to increase readability when tests fail. 10 | 11 | For example, titles like `'Should throw when invalid.'`, `'Should fail when called.'` or `'Should pass when using any number.'` could be enforced with the following pattern `'^Should (pass|fail|throw) when [\\w ]+\\.$'` (Note the escaped `\`). 12 | 13 | ## Fail 14 | 15 | ```js 16 | /* eslint ava/test-title-format: ["error", {format: "^Should"}] */ 17 | const test = require('ava'); 18 | 19 | test('Not starting with `Should`', t => { 20 | t.pass(); 21 | }); 22 | ``` 23 | 24 | ```js 25 | /* eslint ava/test-title-format: ["error", {format: "\\.$"}] */ 26 | const test = require('ava'); 27 | 28 | test('Doesn\'t end with a dot', t => { 29 | t.pass(); 30 | }); 31 | ``` 32 | 33 | ## Pass 34 | 35 | ```js 36 | /* eslint ava/test-title-format: ["error", {format: "^Should"}] */ 37 | const test = require('ava'); 38 | 39 | test('Should pass tests', t => { 40 | t.pass(); 41 | }); 42 | 43 | test('Should behave as expected', t => { 44 | t.pass(); 45 | }); 46 | ``` 47 | 48 | ```js 49 | /* eslint ava/test-title-format: ["error", {format: "\\.$"}] */ 50 | const test = require('ava'); 51 | 52 | test('End with a dot.', t => { 53 | t.pass(); 54 | }); 55 | ``` 56 | 57 | ## Options 58 | 59 | This rule supports the following options: 60 | 61 | `format`: A regular expression string to match against the test titles. 62 | 63 | You can set the options like this: 64 | 65 | ```json 66 | "ava/test-title-format": [ 67 | "error", 68 | { 69 | "format": "^Should" 70 | } 71 | ] 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/rules/test-title.md: -------------------------------------------------------------------------------- 1 | # Ensure tests have a title (`ava/test-title`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/test-title.md) 8 | 9 | Tests should have a title. AVA [v1.0.1](https://github.com/avajs/ava/releases/tag/v1.0.1) and later enforces this at runtime. 10 | 11 | ## Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test(t => { 17 | t.pass(); 18 | }); 19 | ``` 20 | 21 | ## Pass 22 | 23 | ```js 24 | const test = require('ava'); 25 | 26 | test('foo', t => { 27 | t.pass(); 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/rules/use-t-throws-async-well.md: -------------------------------------------------------------------------------- 1 | # Ensure that `t.throwsAsync()` and `t.notThrowsAsync()` are awaited (`ava/use-t-throws-async-well`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | When you use the `t.throwsAsync()` and `t.notThrowsAsync()` assertions, you must await the promise they return. If the test function completes before the assertions do, the test will fail. 10 | 11 | This rule is fixable inside `async` functions. It will insert `await` before `t.throwsAsync()` and `t.notThrowsAsync()`. 12 | 13 | ## Fail 14 | 15 | ```js 16 | import test from 'ava'; 17 | 18 | test('main', t => { 19 | t.throwsAsync(somePromise); 20 | t.notThrowsAsync(somePromise); 21 | }); 22 | ``` 23 | 24 | ## Pass 25 | 26 | ```js 27 | import test from 'ava'; 28 | 29 | test('main', async t => { 30 | await t.throwsAsync(somePromise); 31 | await t.notThrowsAsync(somePromise); 32 | const p = t.throwsAsync(somePromise); 33 | t.throwsAsync(somePromise).then(…); 34 | }); 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/rules/use-t-well.md: -------------------------------------------------------------------------------- 1 | # Disallow the incorrect use of `t` (`ava/use-t-well`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/use-t-well.md) 10 | 11 | Prevent the use of unknown assertion methods and the access to members other than the assertion methods and `.context`, as well as some known misuses of `t`. 12 | 13 | This rule is partly fixable. It can fix most misspelled assertion method names and incorrect usages of `.skip`. 14 | 15 | ## Fail 16 | 17 | ```js 18 | const test = require('ava'); 19 | 20 | test('main', t => { 21 | t(value); // `t` is not a function 22 | t.depEqual(value, [2]); // Misspelled `.deepEqual` as `.depEqual`, fixable 23 | t.contxt.foo = 100; // Misspelled `.context` as `.contxt`, fixable 24 | t.deepEqual.skip.skip(); // Too many chained uses of `.skip`, fixable 25 | t.skip.deepEqual(1, 1); // `.skip` modifier should be the last in chain, fixable 26 | t.foo = 1000; // Unknown member `.foo`. Use `.context.foo` instead 27 | t.deepEqual.is(value, value); // Can't chain assertion methods 28 | t.skip(); // Missing assertion method 29 | }); 30 | ``` 31 | 32 | ## Pass 33 | 34 | ```js 35 | const test = require('ava'); 36 | 37 | test('main', t => { 38 | t.deepEqual(value, [2]); 39 | t.context.a = 100; 40 | require(`fixtures/${t.title}`); 41 | t.deepEqual.skip(); 42 | }); 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/rules/use-t.md: -------------------------------------------------------------------------------- 1 | # Ensure test functions use `t` as their parameter (`ava/use-t`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/use-t.md) 8 | 9 | The convention is to have the parameter in AVA's test function be named `t`. Most rules in `eslint-plugin-ava` are based on that assumption. 10 | 11 | ### Fail 12 | 13 | ```js 14 | const test = require('ava'); 15 | 16 | test('foo', foo => { // Incorrect name 17 | t.pass(); 18 | }); 19 | ``` 20 | 21 | ### Pass 22 | 23 | ```js 24 | const test = require('ava'); 25 | 26 | test('foo', () => { 27 | // ... 28 | }); 29 | 30 | test('bar', t => { 31 | t.pass(); 32 | }); 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/rules/use-test.md: -------------------------------------------------------------------------------- 1 | # Ensure that AVA is imported with `test` as the variable name (`ava/use-test`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/use-test.md) 8 | 9 | The convention is to import AVA and assign it to a variable named `test`. Most rules in `eslint-plugin-ava` are based on that assumption. 10 | In a TypeScript file (`.ts` or `.tsx`) AVA can be assigned to a variable named `anyTest` in order to define the types of `t.context` (see [Typing t.context](https://github.com/avajs/ava/blob/main/docs/recipes/typescript.md#typing-tcontext)). Type-only imports (`import type ... from 'ava'`) are not linted. 11 | 12 | ### Fail 13 | 14 | ```js 15 | var ava = require('ava'); 16 | let ava = require('ava'); 17 | const ava = require('ava'); 18 | import ava from 'ava'; 19 | ``` 20 | 21 | ### Pass 22 | 23 | ```js 24 | var test = require('ava'); 25 | let test = require('ava'); 26 | const test = require('ava'); 27 | import test from 'ava'; 28 | 29 | var test = require('foo'); 30 | const test = require('foo'); 31 | ``` 32 | 33 | ```ts 34 | import anyTest from 'ava'; 35 | import type {TestInterface} from 'ava'; 36 | 37 | const test = anyTest as TestInterface<{foo: string}>; 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/rules/use-true-false.md: -------------------------------------------------------------------------------- 1 | # Ensure that `t.true()`/`t.false()` are used instead of `t.truthy()`/`t.falsy()` (`ava/use-true-false`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/avajs/eslint-plugin-ava#recommended-config). 4 | 5 | 6 | 7 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/docs/rules/use-true-false.md) 8 | 9 | `t.true()` and `t.false()` are stricter in their checks than `t.truthy()` and `t.falsy`. 10 | For example: if you have a function `foo()` which normally returns `true`, but suddenly returns `1` instead, `t.truthy(foo())` would not catch the change, but `t.true(foo())` would. 11 | This rule enforces the use of the former when the tested expression is known to result in a boolean value. 12 | 13 | ### Fail 14 | 15 | ```js 16 | const ava = require('ava'); 17 | 18 | test('foo', t => { 19 | t.truthy(value < 2); 20 | t.truthy(value === 1); 21 | t.truthy([1, 2, 3].includes(value)); 22 | t.falsy(!value); 23 | t.truthy(!!value); 24 | t.truthy(Array.isArray(value)); 25 | }); 26 | ``` 27 | 28 | ### Pass 29 | 30 | ```js 31 | const ava = require('ava'); 32 | 33 | test('foo', t => { 34 | t.true(value < 2); 35 | t.true(value === 1); 36 | t.true([1, 2, 3].includes(value)); 37 | t.false(!value); 38 | t.true(!!value); 39 | t.true(Array.isArray(value)); 40 | }); 41 | ``` 42 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('node:path'); 4 | const importModules = require('import-modules'); 5 | const {name, version} = require('./package.json'); 6 | 7 | const recommendedRules = { 8 | 'ava/assertion-arguments': 'error', 9 | 'ava/hooks-order': 'error', 10 | 'ava/max-asserts': [ 11 | 'off', 12 | 5, 13 | ], 14 | 'ava/no-async-fn-without-await': 'error', 15 | 'ava/no-duplicate-modifiers': 'error', 16 | 'ava/no-identical-title': 'error', 17 | 'ava/no-ignored-test-files': 'error', 18 | 'ava/no-import-test-files': 'error', 19 | 'ava/no-incorrect-deep-equal': 'error', 20 | 'ava/no-inline-assertions': 'error', 21 | 'ava/no-nested-tests': 'error', 22 | 'ava/no-only-test': 'error', 23 | 'ava/no-skip-assert': 'error', 24 | 'ava/no-skip-test': 'error', 25 | 'ava/no-todo-implementation': 'error', 26 | 'ava/no-todo-test': 'warn', 27 | 'ava/no-unknown-modifiers': 'error', 28 | 'ava/prefer-async-await': 'error', 29 | 'ava/prefer-power-assert': 'off', 30 | 'ava/prefer-t-regex': 'error', 31 | 'ava/test-title': 'error', 32 | 'ava/test-title-format': 'off', 33 | 'ava/use-t-well': 'error', 34 | 'ava/use-t': 'error', 35 | 'ava/use-t-throws-async-well': 'error', 36 | 'ava/use-test': 'error', 37 | 'ava/use-true-false': 'error', 38 | }; 39 | 40 | const plugin = { 41 | meta: { 42 | name, 43 | version, 44 | }, 45 | rules: importModules(path.resolve(__dirname, 'rules'), {camelize: false}), 46 | configs: {}, 47 | }; 48 | 49 | Object.assign(plugin.configs, { 50 | recommended: { 51 | env: { 52 | es6: true, 53 | }, 54 | parserOptions: { 55 | ecmaVersion: 'latest', 56 | sourceType: 'module', 57 | }, 58 | plugins: [ 59 | 'ava', 60 | ], 61 | rules: { 62 | ...recommendedRules, 63 | }, 64 | }, 65 | 'flat/recommended': { 66 | plugins: { 67 | ava: plugin, 68 | }, 69 | rules: { 70 | ...recommendedRules, 71 | }, 72 | }, 73 | }); 74 | 75 | module.exports = plugin; 76 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /maintaining.md: -------------------------------------------------------------------------------- 1 | # Maintaining 2 | 3 | ## Resources 4 | 5 | - [AST Explorer](https://astexplorer.net) 6 | - [Working with ESLint rules](https://eslint.org/docs/developer-guide/working-with-rules) 7 | 8 | ## Deprecating rules 9 | 10 | - Keep the file where it is. 11 | - Remove the rule from the readme and the `recommended` config. 12 | - Remove its documentation in `docs/rules/`. 13 | - Set `deprecated: true` in `meta` property of the returned rule object. 14 | - Prefix all rule messages with `(DEPRECATED) `. 15 | - Remove the rule and its test sometime far in the future. No point in inconveniencing the user. 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-ava", 3 | "version": "15.0.1", 4 | "description": "ESLint rules for AVA", 5 | "license": "MIT", 6 | "repository": "avajs/eslint-plugin-ava", 7 | "sideEffects": false, 8 | "engines": { 9 | "node": "^18.18 || >=20" 10 | }, 11 | "scripts": { 12 | "integration": "node ./test/integration/test.js", 13 | "lint": "npm-run-all \"lint:*\"", 14 | "lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"", 15 | "lint:js": "xo", 16 | "test": "npm-run-all lint test:unit", 17 | "test:unit": "c8 ava", 18 | "update:eslint-docs": "eslint-doc-generator --rule-doc-section-include Pass --rule-doc-section-include Fail --url-configs \"https://github.com/avajs/eslint-plugin-ava#recommended-config\" --ignore-config=flat/recommended" 19 | }, 20 | "files": [ 21 | "index.js", 22 | "create-ava-rule.js", 23 | "util.js", 24 | "rules" 25 | ], 26 | "keywords": [ 27 | "eslint", 28 | "eslintplugin", 29 | "eslint-plugin", 30 | "ava", 31 | "test", 32 | "runner", 33 | "assert", 34 | "asserts", 35 | "assertion", 36 | "mocha" 37 | ], 38 | "dependencies": { 39 | "enhance-visitors": "^1.0.0", 40 | "eslint-utils": "^3.0.0", 41 | "espree": "^9.0.0", 42 | "espurify": "^2.1.1", 43 | "import-modules": "^2.1.0", 44 | "micro-spelling-correcter": "^1.1.1", 45 | "pkg-dir": "^5.0.0", 46 | "resolve-from": "^5.0.0" 47 | }, 48 | "devDependencies": { 49 | "@typescript-eslint/parser": "^5.9.0", 50 | "ava": "^3.15.0", 51 | "c8": "^7.7.3", 52 | "chalk": "^4.1.1", 53 | "del": "^6.0.0", 54 | "eslint": "^8.26.0", 55 | "eslint-ava-rule-tester": "^4.0.0", 56 | "eslint-doc-generator": "^1.0.0", 57 | "eslint-plugin-eslint-plugin": "^6.1.0", 58 | "execa": "^5.1.1", 59 | "listr": "^0.14.3", 60 | "npm-run-all": "^4.1.5", 61 | "outdent": "^0.8.0", 62 | "pify": "^5.0.0", 63 | "tempy": "^1.0.1", 64 | "xo": "^0.58.0" 65 | }, 66 | "peerDependencies": { 67 | "eslint": ">=9" 68 | }, 69 | "ava": { 70 | "files": [ 71 | "!rules", 72 | "test/*.js" 73 | ] 74 | }, 75 | "xo": { 76 | "plugins": [ 77 | "eslint-plugin" 78 | ], 79 | "parserOptions": { 80 | "sourceType": "script" 81 | }, 82 | "extends": [ 83 | "plugin:eslint-plugin/all" 84 | ], 85 | "overrides": [ 86 | { 87 | "files": "create-ava-rule.js", 88 | "rules": { 89 | "eslint-plugin/require-meta-docs-url": "off" 90 | } 91 | } 92 | ], 93 | "rules": { 94 | "strict": "error", 95 | "ava/no-ignored-test-files": "off", 96 | "eslint-plugin/prefer-message-ids": "off", 97 | "eslint-plugin/require-meta-docs-description": [ 98 | "error", 99 | { 100 | "pattern": "^(Enforce|Ensure|Require|Disallow|Prevent|Prefer)" 101 | } 102 | ], 103 | "eslint-plugin/require-meta-has-suggestions": "off", 104 | "eslint-plugin/prefer-placeholders": "off", 105 | "import/extensions": "off", 106 | "unicorn/prefer-module": "off", 107 | "unicorn/prefer-top-level-await": "off", 108 | "eslint-plugin/require-meta-docs-recommended": "off" 109 | } 110 | }, 111 | "c8": { 112 | "reporter": [ 113 | "html", 114 | "lcov", 115 | "text" 116 | ] 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-ava [![Coverage Status](https://coveralls.io/repos/github/avajs/eslint-plugin-ava/badge.svg?branch=main)](https://coveralls.io/github/avajs/eslint-plugin-ava?branch=main) 2 | 3 | > ESLint rules for [AVA](https://avajs.dev) 4 | 5 | Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/related/eslint-plugin-ava/readme.md) 6 | 7 | This plugin is bundled in [XO](https://github.com/xojs/xo). No need to do anything if you're using it. 8 | 9 | [**Propose or contribute a new rule ➡**](.github/contributing.md) 10 | 11 | ## Install 12 | 13 | ```sh 14 | npm install --save-dev eslint eslint-plugin-ava 15 | ``` 16 | 17 | ## Usage 18 | 19 | Configure it in `package.json`. 20 | 21 | ```json 22 | { 23 | "name": "my-awesome-project", 24 | "eslintConfig": { 25 | "env": { 26 | "es6": true 27 | }, 28 | "parserOptions": { 29 | "ecmaVersion": "latest", 30 | "sourceType": "module" 31 | }, 32 | "plugins": [ 33 | "ava" 34 | ], 35 | "rules": { 36 | "ava/assertion-arguments": "error", 37 | "ava/...": "error" 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | ## Rules 44 | 45 | The rules will only activate in test files. 46 | 47 | 48 | 49 | 💼 [Configurations](https://github.com/avajs/eslint-plugin-ava#recommended-config) enabled in.\ 50 | ⚠️ [Configurations](https://github.com/avajs/eslint-plugin-ava#recommended-config) set to warn in.\ 51 | 🚫 [Configurations](https://github.com/avajs/eslint-plugin-ava#recommended-config) disabled in.\ 52 | ✅ Set in the `recommended` [configuration](https://github.com/avajs/eslint-plugin-ava#recommended-config).\ 53 | 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ 54 | 💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 55 | 56 | | Name                      | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 | 57 | | :------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | 58 | | [assertion-arguments](docs/rules/assertion-arguments.md) | Enforce passing correct arguments to assertions. | ✅ | | | 🔧 | | 59 | | [hooks-order](docs/rules/hooks-order.md) | Enforce test hook ordering. | ✅ | | | 🔧 | | 60 | | [max-asserts](docs/rules/max-asserts.md) | Enforce a limit on the number of assertions in a test. | | | ✅ | | | 61 | | [no-async-fn-without-await](docs/rules/no-async-fn-without-await.md) | Ensure that async tests use `await`. | ✅ | | | | | 62 | | [no-duplicate-modifiers](docs/rules/no-duplicate-modifiers.md) | Ensure tests do not have duplicate modifiers. | ✅ | | | | | 63 | | [no-identical-title](docs/rules/no-identical-title.md) | Ensure no tests have the same title. | ✅ | | | | | 64 | | [no-ignored-test-files](docs/rules/no-ignored-test-files.md) | Ensure no tests are written in ignored files. | ✅ | | | | | 65 | | [no-import-test-files](docs/rules/no-import-test-files.md) | Ensure no test files are imported anywhere. | ✅ | | | | | 66 | | [no-incorrect-deep-equal](docs/rules/no-incorrect-deep-equal.md) | Disallow using `deepEqual` with primitives. | ✅ | | | 🔧 | | 67 | | [no-inline-assertions](docs/rules/no-inline-assertions.md) | Ensure assertions are not called from inline arrow functions. | ✅ | | | 🔧 | | 68 | | [no-nested-tests](docs/rules/no-nested-tests.md) | Ensure no tests are nested. | ✅ | | | | | 69 | | [no-only-test](docs/rules/no-only-test.md) | Ensure no `test.only()` are present. | ✅ | | | 🔧 | 💡 | 70 | | [no-skip-assert](docs/rules/no-skip-assert.md) | Ensure no assertions are skipped. | ✅ | | | | | 71 | | [no-skip-test](docs/rules/no-skip-test.md) | Ensure no tests are skipped. | ✅ | | | 🔧 | 💡 | 72 | | [no-todo-implementation](docs/rules/no-todo-implementation.md) | Ensure `test.todo()` is not given an implementation function. | ✅ | | | | | 73 | | [no-todo-test](docs/rules/no-todo-test.md) | Ensure no `test.todo()` is used. | | ✅ | | | | 74 | | [no-unknown-modifiers](docs/rules/no-unknown-modifiers.md) | Disallow the use of unknown test modifiers. | ✅ | | | | | 75 | | [prefer-async-await](docs/rules/prefer-async-await.md) | Prefer using async/await instead of returning a Promise. | ✅ | | | | | 76 | | [prefer-power-assert](docs/rules/prefer-power-assert.md) | Enforce the use of the asserts that have no [power-assert](https://github.com/power-assert-js/power-assert) alternative. | | | ✅ | | | 77 | | [prefer-t-regex](docs/rules/prefer-t-regex.md) | Prefer using `t.regex()` to test regular expressions. | ✅ | | | 🔧 | | 78 | | [test-title](docs/rules/test-title.md) | Ensure tests have a title. | ✅ | | | | | 79 | | [test-title-format](docs/rules/test-title-format.md) | Ensure test titles have a certain format. | | | ✅ | | | 80 | | [use-t](docs/rules/use-t.md) | Ensure test functions use `t` as their parameter. | ✅ | | | | | 81 | | [use-t-throws-async-well](docs/rules/use-t-throws-async-well.md) | Ensure that `t.throwsAsync()` and `t.notThrowsAsync()` are awaited. | ✅ | | | 🔧 | | 82 | | [use-t-well](docs/rules/use-t-well.md) | Disallow the incorrect use of `t`. | ✅ | | | 🔧 | | 83 | | [use-test](docs/rules/use-test.md) | Ensure that AVA is imported with `test` as the variable name. | ✅ | | | | | 84 | | [use-true-false](docs/rules/use-true-false.md) | Ensure that `t.true()`/`t.false()` are used instead of `t.truthy()`/`t.falsy()`. | ✅ | | | | | 85 | 86 | 87 | 88 | ## Recommended config 89 | 90 | This plugin exports a [`recommended` config](index.js) that enforces good practices. 91 | 92 | Enable it in your `package.json` with the `extends` option: 93 | 94 | ```json 95 | { 96 | "name": "my-awesome-project", 97 | "eslintConfig": { 98 | "extends": "plugin:ava/recommended" 99 | } 100 | } 101 | ``` 102 | 103 | See the [ESLint docs](https://eslint.org/docs/user-guide/configuring#extending-configuration-files) for more information about extending config files. 104 | 105 | **Note**: This config will also enable the correct [parser options](https://eslint.org/docs/user-guide/configuring#specifying-parser-options) and [environment](https://eslint.org/docs/user-guide/configuring#specifying-environments). 106 | -------------------------------------------------------------------------------- /rules/assertion-arguments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const {getStaticValue, isOpeningParenToken, isCommaToken, findVariable} = require('eslint-utils'); 5 | const util = require('../util'); 6 | const createAvaRule = require('../create-ava-rule'); 7 | 8 | const expectedNbArguments = { 9 | assert: { 10 | min: 1, 11 | max: 2, 12 | }, 13 | deepEqual: { 14 | min: 2, 15 | max: 3, 16 | }, 17 | fail: { 18 | min: 0, 19 | max: 1, 20 | }, 21 | false: { 22 | min: 1, 23 | max: 2, 24 | }, 25 | falsy: { 26 | min: 1, 27 | max: 2, 28 | }, 29 | ifError: { 30 | min: 1, 31 | max: 2, 32 | }, 33 | is: { 34 | min: 2, 35 | max: 3, 36 | }, 37 | like: { 38 | min: 2, 39 | max: 3, 40 | }, 41 | not: { 42 | min: 2, 43 | max: 3, 44 | }, 45 | notDeepEqual: { 46 | min: 2, 47 | max: 3, 48 | }, 49 | notThrows: { 50 | min: 1, 51 | max: 2, 52 | }, 53 | notThrowsAsync: { 54 | min: 1, 55 | max: 2, 56 | }, 57 | pass: { 58 | min: 0, 59 | max: 1, 60 | }, 61 | plan: { 62 | min: 1, 63 | max: 1, 64 | }, 65 | regex: { 66 | min: 2, 67 | max: 3, 68 | }, 69 | notRegex: { 70 | min: 2, 71 | max: 3, 72 | }, 73 | snapshot: { 74 | min: 1, 75 | max: 2, 76 | }, 77 | teardown: { 78 | min: 1, 79 | max: 1, 80 | }, 81 | throws: { 82 | min: 1, 83 | max: 3, 84 | }, 85 | throwsAsync: { 86 | min: 1, 87 | max: 3, 88 | }, 89 | true: { 90 | min: 1, 91 | max: 2, 92 | }, 93 | truthy: { 94 | min: 1, 95 | max: 2, 96 | }, 97 | timeout: { 98 | min: 1, 99 | max: 2, 100 | }, 101 | }; 102 | 103 | const actualExpectedAssertions = new Set([ 104 | 'deepEqual', 105 | 'is', 106 | 'like', 107 | 'not', 108 | 'notDeepEqual', 109 | 'throws', 110 | 'throwsAsync', 111 | ]); 112 | 113 | const relationalActualExpectedAssertions = new Set([ 114 | 'assert', 115 | 'truthy', 116 | 'falsy', 117 | 'true', 118 | 'false', 119 | ]); 120 | 121 | const comparisonOperators = new Map([ 122 | ['>', '<'], 123 | ['>=', '<='], 124 | ['==', '=='], 125 | ['===', '==='], 126 | ['!=', '!='], 127 | ['!==', '!=='], 128 | ['<=', '>='], 129 | ['<', '>'], 130 | ]); 131 | 132 | const flipOperator = operator => comparisonOperators.get(operator); 133 | 134 | function isStatic(node) { 135 | const staticValue = getStaticValue(node); 136 | return staticValue !== null && typeof staticValue.value !== 'function'; 137 | } 138 | 139 | function * sourceRangesOfArguments(sourceCode, callExpression) { 140 | const openingParen = sourceCode.getTokenAfter( 141 | callExpression.callee, 142 | {filter: token => isOpeningParenToken(token)}, 143 | ); 144 | 145 | const closingParen = sourceCode.getLastToken(callExpression); 146 | 147 | for (const [index, argument] of callExpression.arguments.entries()) { 148 | const previousToken = index === 0 149 | ? openingParen 150 | : sourceCode.getTokenBefore( 151 | argument, 152 | {filter: token => isCommaToken(token)}, 153 | ); 154 | 155 | const nextToken = index === callExpression.arguments.length - 1 156 | ? closingParen 157 | : sourceCode.getTokenAfter( 158 | argument, 159 | {filter: token => isCommaToken(token)}, 160 | ); 161 | 162 | const firstToken = sourceCode.getTokenAfter( 163 | previousToken, 164 | {includeComments: true}, 165 | ); 166 | 167 | const lastToken = sourceCode.getTokenBefore( 168 | nextToken, 169 | {includeComments: true}, 170 | ); 171 | 172 | yield [firstToken.range[0], lastToken.range[1]]; 173 | } 174 | } 175 | 176 | function sourceOfBinaryExpressionComponents(sourceCode, node) { 177 | const {operator, left, right} = node; 178 | 179 | const operatorToken = sourceCode.getFirstTokenBetween( 180 | left, 181 | right, 182 | {filter: token => token.value === operator}, 183 | ); 184 | 185 | const previousToken = sourceCode.getTokenBefore(node); 186 | const nextToken = sourceCode.getTokenAfter(node); 187 | 188 | const leftRange = [ 189 | sourceCode.getTokenAfter(previousToken, {includeComments: true}).range[0], 190 | sourceCode.getTokenBefore(operatorToken, {includeComments: true}).range[1], 191 | ]; 192 | 193 | const rightRange = [ 194 | sourceCode.getTokenAfter(operatorToken, {includeComments: true}).range[0], 195 | sourceCode.getTokenBefore(nextToken, {includeComments: true}).range[1], 196 | ]; 197 | 198 | return [leftRange, operatorToken, rightRange]; 199 | } 200 | 201 | function noComments(sourceCode, ...nodes) { 202 | return nodes.every(node => sourceCode.getCommentsBefore(node).length === 0 && sourceCode.getCommentsAfter(node).length === 0); 203 | } 204 | 205 | function isString(node) { 206 | const {type} = node; 207 | return type === 'TemplateLiteral' 208 | || type === 'TaggedTemplateExpression' 209 | || (type === 'Literal' && typeof node.value === 'string'); 210 | } 211 | 212 | const create = context => { 213 | const ava = createAvaRule(); 214 | const options = context.options[0] ?? {}; 215 | const enforcesMessage = Boolean(options.message); 216 | const shouldHaveMessage = options.message !== 'never'; 217 | 218 | function report(node, message) { 219 | context.report({node, message}); 220 | } 221 | 222 | return ava.merge({ 223 | CallExpression: visitIf([ 224 | ava.isInTestFile, 225 | ava.isInTestNode, 226 | ])(node => { 227 | const {callee} = node; 228 | 229 | if ( 230 | callee.type !== 'MemberExpression' 231 | || !callee.property 232 | || util.getNameOfRootNodeObject(callee) !== 't' 233 | || util.isPropertyUnderContext(callee) 234 | ) { 235 | return; 236 | } 237 | 238 | const gottenArguments = node.arguments.length; 239 | const firstNonSkipMember = util.getMembers(callee).find(name => name !== 'skip'); 240 | 241 | if (firstNonSkipMember === 'end') { 242 | if (gottenArguments > 1) { 243 | report(node, 'Too many arguments. Expected at most 1.'); 244 | } 245 | 246 | return; 247 | } 248 | 249 | if (firstNonSkipMember === 'try') { 250 | if (gottenArguments < 1) { 251 | report(node, 'Not enough arguments. Expected at least 1.'); 252 | } 253 | 254 | return; 255 | } 256 | 257 | const nArguments = expectedNbArguments[firstNonSkipMember]; 258 | 259 | if (!nArguments) { 260 | return; 261 | } 262 | 263 | if (gottenArguments < nArguments.min) { 264 | report(node, `Not enough arguments. Expected at least ${nArguments.min}.`); 265 | } else if (node.arguments.length > nArguments.max) { 266 | report(node, `Too many arguments. Expected at most ${nArguments.max}.`); 267 | } else { 268 | if (enforcesMessage && nArguments.min !== nArguments.max) { 269 | const hasMessage = gottenArguments === nArguments.max; 270 | 271 | if (!hasMessage && shouldHaveMessage) { 272 | report(node, 'Expected an assertion message, but found none.'); 273 | } else if (hasMessage && !shouldHaveMessage) { 274 | report(node, 'Expected no assertion message, but found one.'); 275 | } 276 | } 277 | 278 | checkArgumentOrder({node, assertion: firstNonSkipMember, context}); 279 | } 280 | 281 | if (gottenArguments === nArguments.max && nArguments.min !== nArguments.max) { 282 | let lastArgument = node.arguments.at(-1); 283 | 284 | if (lastArgument.type === 'Identifier') { 285 | const variable = findVariable(context.sourceCode.getScope(node), lastArgument); 286 | let value; 287 | for (const reference of variable.references) { 288 | value = reference.writeExpr ?? value; 289 | } 290 | 291 | lastArgument = value; 292 | } 293 | 294 | if (!isString(lastArgument)) { 295 | report(node, 'Assertion message should be a string.'); 296 | } 297 | } 298 | }), 299 | }); 300 | }; 301 | 302 | function checkArgumentOrder({node, assertion, context}) { 303 | const [first, second] = node.arguments; 304 | if (actualExpectedAssertions.has(assertion) && second) { 305 | const [leftNode, rightNode] = [first, second]; 306 | if (isStatic(leftNode) && !isStatic(rightNode)) { 307 | context.report( 308 | makeOutOfOrder2ArgumentReport({ 309 | node, 310 | leftNode, 311 | rightNode, 312 | context, 313 | }), 314 | ); 315 | } 316 | } else if ( 317 | relationalActualExpectedAssertions.has(assertion) 318 | && first 319 | && first.type === 'BinaryExpression' 320 | && comparisonOperators.has(first.operator) 321 | ) { 322 | const [leftNode, rightNode] = [first.left, first.right]; 323 | if (isStatic(leftNode) && !isStatic(rightNode)) { 324 | context.report( 325 | makeOutOfOrder1ArgumentReport({ 326 | node: first, 327 | leftNode, 328 | rightNode, 329 | context, 330 | }), 331 | ); 332 | } 333 | } 334 | } 335 | 336 | function makeOutOfOrder2ArgumentReport({node, leftNode, rightNode, context}) { 337 | const sourceCode = context.getSourceCode(); 338 | const [leftRange, rightRange] = sourceRangesOfArguments(sourceCode, node); 339 | const report = { 340 | message: 'Expected values should come after actual values.', 341 | loc: { 342 | start: sourceCode.getLocFromIndex(leftRange[0]), 343 | end: sourceCode.getLocFromIndex(rightRange[1]), 344 | }, 345 | }; 346 | 347 | if (noComments(sourceCode, leftNode, rightNode)) { 348 | report.fix = fixer => { 349 | const leftText = sourceCode.getText().slice(...leftRange); 350 | const rightText = sourceCode.getText().slice(...rightRange); 351 | return [ 352 | fixer.replaceTextRange(leftRange, rightText), 353 | fixer.replaceTextRange(rightRange, leftText), 354 | ]; 355 | }; 356 | } 357 | 358 | return report; 359 | } 360 | 361 | function makeOutOfOrder1ArgumentReport({node, leftNode, rightNode, context}) { 362 | const sourceCode = context.getSourceCode(); 363 | const [ 364 | leftRange, 365 | operatorToken, 366 | rightRange, 367 | ] = sourceOfBinaryExpressionComponents(sourceCode, node); 368 | const report = { 369 | message: 'Expected values should come after actual values.', 370 | loc: { 371 | start: sourceCode.getLocFromIndex(leftRange[0]), 372 | end: sourceCode.getLocFromIndex(rightRange[1]), 373 | }, 374 | }; 375 | 376 | if (noComments(sourceCode, leftNode, rightNode, node)) { 377 | report.fix = fixer => { 378 | const leftText = sourceCode.getText().slice(...leftRange); 379 | const rightText = sourceCode.getText().slice(...rightRange); 380 | return [ 381 | fixer.replaceTextRange(leftRange, rightText), 382 | fixer.replaceText(operatorToken, flipOperator(node.operator)), 383 | fixer.replaceTextRange(rightRange, leftText), 384 | ]; 385 | }; 386 | } 387 | 388 | return report; 389 | } 390 | 391 | const schema = [{ 392 | type: 'object', 393 | properties: { 394 | message: { 395 | enum: [ 396 | 'always', 397 | 'never', 398 | ], 399 | default: undefined, 400 | }, 401 | }, 402 | }]; 403 | 404 | module.exports = { 405 | create, 406 | meta: { 407 | type: 'problem', 408 | docs: { 409 | description: 'Enforce passing correct arguments to assertions.', 410 | url: util.getDocsUrl(__filename), 411 | }, 412 | fixable: 'code', 413 | schema, 414 | }, 415 | }; 416 | -------------------------------------------------------------------------------- /rules/hooks-order.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const MESSAGE_ID = 'hooks-order'; 8 | 9 | const buildOrders = names => { 10 | const orders = {}; 11 | for (const nameLater of names) { 12 | for (const nameEarlier in orders) { 13 | if (orders[nameEarlier]) { 14 | orders[nameEarlier].push(nameLater); 15 | } 16 | } 17 | 18 | orders[nameLater] = []; 19 | } 20 | 21 | return orders; 22 | }; 23 | 24 | const buildMessage = (name, orders, visited) => { 25 | const checks = orders[name] ?? []; 26 | 27 | for (const check of checks) { 28 | const nodeEarlier = visited[check]; 29 | if (nodeEarlier) { 30 | return { 31 | messageId: MESSAGE_ID, 32 | data: { 33 | current: name, 34 | invalid: check, 35 | }, 36 | node: nodeEarlier, 37 | }; 38 | } 39 | } 40 | 41 | return null; 42 | }; 43 | 44 | const create = context => { 45 | const ava = createAvaRule(); 46 | 47 | const orders = buildOrders([ 48 | 'before', 49 | 'after', 50 | 'after.always', 51 | 'beforeEach', 52 | 'afterEach', 53 | 'afterEach.always', 54 | 'test', 55 | ]); 56 | 57 | const visited = {}; 58 | 59 | const checks = [ 60 | { 61 | selector: 'CallExpression[callee.object.name="test"][callee.property.name="before"]', 62 | name: 'before', 63 | }, 64 | { 65 | selector: 'CallExpression[callee.object.name="test"][callee.property.name="after"]', 66 | name: 'after', 67 | }, 68 | { 69 | selector: 'CallExpression[callee.object.object.name="test"][callee.object.property.name="after"][callee.property.name="always"]', 70 | name: 'after.always', 71 | }, 72 | { 73 | selector: 'CallExpression[callee.object.name="test"][callee.property.name="beforeEach"]', 74 | name: 'beforeEach', 75 | }, 76 | { 77 | selector: 'CallExpression[callee.object.name="test"][callee.property.name="afterEach"]', 78 | name: 'afterEach', 79 | }, 80 | { 81 | selector: 'CallExpression[callee.object.object.name="test"][callee.object.property.name="afterEach"][callee.property.name="always"]', 82 | name: 'afterEach.always', 83 | }, 84 | { 85 | selector: 'CallExpression[callee.name="test"]', 86 | name: 'test', 87 | }, 88 | ]; 89 | 90 | const sourceCode = context.getSourceCode(); 91 | 92 | // TODO: Remove `.reduce()` usage. 93 | // eslint-disable-next-line unicorn/no-array-reduce 94 | const selectors = checks.reduce((result, check) => { 95 | result[check.selector] = visitIf([ 96 | ava.isInTestFile, 97 | ava.isTestNode, 98 | ])(node => { 99 | visited[check.name] = node; 100 | 101 | const message = buildMessage(check.name, orders, visited); 102 | if (message) { 103 | const nodeEarlier = message.node; 104 | 105 | context.report({ 106 | node, 107 | messageId: message.messageId, 108 | data: message.data, 109 | fix(fixer) { 110 | const tokensBetween = sourceCode.getTokensBetween(nodeEarlier.parent, node.parent); 111 | 112 | if (tokensBetween?.length > 0) { 113 | return; 114 | } 115 | 116 | const source = sourceCode.getText(); 117 | let [insertStart, insertEnd] = nodeEarlier.parent.range; 118 | 119 | // Grab the node and all comments and whitespace before the node 120 | const start = nodeEarlier.parent.range[1]; 121 | const end = node.parent.range[1]; 122 | 123 | let text = sourceCode.getText().slice(start, end); 124 | 125 | // Preserve newline previously between hooks 126 | if (source.length >= (start + 1) && source[start + 1] === '\n') { 127 | text = text.slice(1) + '\n'; 128 | } 129 | 130 | // Preserve newline that was previously before hooks 131 | if ((insertStart - 1) > 0 && source[insertStart - 1] === '\n') { 132 | insertStart -= 1; 133 | } 134 | 135 | return [ 136 | fixer.insertTextBeforeRange([insertStart, insertEnd], text), 137 | fixer.removeRange([start, end]), 138 | ]; 139 | }, 140 | }); 141 | } 142 | }); 143 | return result; 144 | }, {}); 145 | 146 | return ava.merge(selectors); 147 | }; 148 | 149 | module.exports = { 150 | create, 151 | meta: { 152 | type: 'suggestion', 153 | docs: { 154 | description: 'Enforce test hook ordering.', 155 | url: util.getDocsUrl(__filename), 156 | }, 157 | fixable: 'code', 158 | schema: [], 159 | messages: { 160 | [MESSAGE_ID]: '`{{current}}` hook must come before `{{invalid}}`', 161 | }, 162 | }, 163 | }; 164 | -------------------------------------------------------------------------------- /rules/max-asserts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const util = require('../util'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | const MAX_ASSERTIONS_DEFAULT = 5; 8 | 9 | const notAssertionMethods = new Set(['plan', 'end']); 10 | 11 | const create = context => { 12 | const ava = createAvaRule(); 13 | // TODO: Convert options to object JSON Schema default works properly 14 | // https://github.com/avajs/eslint-plugin-ava/issues/260 15 | const maxAssertions = context.options[0] ?? MAX_ASSERTIONS_DEFAULT; 16 | let assertionCount = 0; 17 | let nodeToReport; 18 | 19 | return ava.merge({ 20 | CallExpression: visitIf([ 21 | ava.isInTestFile, 22 | ava.isInTestNode, 23 | ])(node => { 24 | const {callee} = node; 25 | 26 | if (callee.type !== 'MemberExpression') { 27 | return; 28 | } 29 | 30 | if ( 31 | callee.property 32 | && !notAssertionMethods.has(callee.property.name) 33 | && util.getNameOfRootNodeObject(callee) === 't' 34 | ) { 35 | const firstNonSkipMember = util.getMembers(callee).find(name => name !== 'skip'); 36 | 37 | if (!util.assertionMethods.has(firstNonSkipMember)) { 38 | return; 39 | } 40 | 41 | assertionCount++; 42 | 43 | if (assertionCount === maxAssertions + 1) { 44 | nodeToReport = node; 45 | } 46 | } 47 | }), 48 | 'CallExpression:exit': visitIf([ava.isTestNode])(() => { 49 | // Leaving test function 50 | if (assertionCount > maxAssertions) { 51 | context.report({ 52 | node: nodeToReport, 53 | message: `Expected at most ${maxAssertions} assertions, but found ${assertionCount}.`, 54 | }); 55 | } 56 | 57 | assertionCount = 0; 58 | nodeToReport = undefined; 59 | }), 60 | }); 61 | }; 62 | 63 | const schema = [ 64 | { 65 | type: 'integer', 66 | default: MAX_ASSERTIONS_DEFAULT, 67 | }, 68 | ]; 69 | 70 | module.exports = { 71 | create, 72 | meta: { 73 | type: 'suggestion', 74 | docs: { 75 | description: 'Enforce a limit on the number of assertions in a test.', 76 | url: util.getDocsUrl(__filename), 77 | }, 78 | schema, 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /rules/no-async-fn-without-await.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | let testUsed = false; 10 | let asyncTest; 11 | 12 | const registerUseOfAwait = () => { 13 | if (asyncTest) { 14 | testUsed = true; 15 | } 16 | }; 17 | 18 | const isAsync = node => Boolean(node?.async); 19 | 20 | return ava.merge({ 21 | CallExpression: visitIf([ 22 | ava.isInTestFile, 23 | ava.isTestNode, 24 | ])(node => { 25 | asyncTest = (isAsync(node.arguments[0]) && node.arguments[0]) 26 | || (isAsync(node.arguments[1]) && node.arguments[1]); 27 | }), 28 | AwaitExpression: registerUseOfAwait, 29 | YieldExpression: registerUseOfAwait, 30 | 'ForOfStatement[await=true]': registerUseOfAwait, 31 | 'CallExpression:exit': visitIf([ 32 | ava.isInTestFile, 33 | ava.isTestNode, 34 | ])(() => { 35 | if (asyncTest && !testUsed) { 36 | context.report({ 37 | node: asyncTest, 38 | loc: { 39 | start: asyncTest.loc.start, 40 | end: asyncTest.loc.start + 5, 41 | }, 42 | message: 'Function was declared as `async` but doesn\'t use `await`.', 43 | }); 44 | } 45 | 46 | asyncTest = undefined; 47 | testUsed = false; 48 | }), 49 | }); 50 | }; 51 | 52 | module.exports = { 53 | create, 54 | meta: { 55 | type: 'suggestion', 56 | docs: { 57 | description: 'Ensure that async tests use `await`.', 58 | url: util.getDocsUrl(__filename), 59 | }, 60 | schema: [], 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /rules/no-duplicate-modifiers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const util = require('../util'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | function sortByName(a, b) { 8 | if (a.name < b.name) { 9 | return -1; 10 | } 11 | 12 | if (a.name > b.name) { 13 | return 1; 14 | } 15 | 16 | return 0; 17 | } 18 | 19 | const create = context => { 20 | const ava = createAvaRule(); 21 | 22 | return ava.merge({ 23 | CallExpression: visitIf([ 24 | ava.isInTestFile, 25 | ava.isTestNode, 26 | ])(node => { 27 | const testModifiers = util.getTestModifiers(node).sort(sortByName); 28 | 29 | if (testModifiers.length === 0) { 30 | return; 31 | } 32 | 33 | // TODO: Remove `.reduce()` usage. 34 | // eslint-disable-next-line unicorn/no-array-reduce 35 | testModifiers.reduce((previous, current) => { 36 | if (previous.name === current.name) { 37 | context.report({ 38 | node: current, 39 | message: `Duplicate test modifier \`.${current.name}\`.`, 40 | }); 41 | } 42 | 43 | return current; 44 | }); 45 | }), 46 | }); 47 | }; 48 | 49 | module.exports = { 50 | create, 51 | meta: { 52 | type: 'problem', 53 | docs: { 54 | description: 'Ensure tests do not have duplicate modifiers.', 55 | url: util.getDocsUrl(__filename), 56 | }, 57 | schema: [], 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /rules/no-identical-title.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isDeepStrictEqual} = require('node:util'); 4 | const espurify = require('espurify'); 5 | const {visitIf} = require('enhance-visitors'); 6 | const util = require('../util'); 7 | const createAvaRule = require('../create-ava-rule'); 8 | 9 | const purify = node => node && espurify(node); 10 | 11 | const isStaticTemplateLiteral = node => node.expressions.every(expression => isStatic(expression)); 12 | 13 | const isStatic = node => node.type === 'Literal' 14 | || (node.type === 'TemplateLiteral' && isStaticTemplateLiteral(node)) 15 | || (node.type === 'BinaryExpression' && isStatic(node.left) && isStatic(node.right)); 16 | 17 | function isTitleUsed(usedTitleNodes, titleNode) { 18 | const purifiedNode = purify(titleNode); 19 | return usedTitleNodes.some(usedTitle => isDeepStrictEqual(purifiedNode, usedTitle)); 20 | } 21 | 22 | const create = context => { 23 | const ava = createAvaRule(); 24 | let usedTitleNodes = []; 25 | 26 | return ava.merge({ 27 | CallExpression: visitIf([ 28 | ava.isInTestFile, 29 | ava.isTestNode, 30 | ava.hasNoUtilityModifier, 31 | ])(node => { 32 | const arguments_ = node.arguments; 33 | const titleNode = arguments_.length > 1 || ava.hasTestModifier('todo') ? arguments_[0] : undefined; 34 | 35 | // Don't flag computed titles 36 | if (!titleNode || !isStatic(titleNode)) { 37 | return; 38 | } 39 | 40 | // Don't flag what look to be macros 41 | if (arguments_.length > 2 && !util.isFunctionExpression(arguments_[1])) { 42 | return; 43 | } 44 | 45 | if (isTitleUsed(usedTitleNodes, titleNode)) { 46 | context.report({ 47 | node: titleNode, 48 | message: 'Test title is used multiple times in the same file.', 49 | }); 50 | return; 51 | } 52 | 53 | usedTitleNodes.push(purify(titleNode)); 54 | }), 55 | 'Program:exit'() { 56 | usedTitleNodes = []; 57 | }, 58 | }); 59 | }; 60 | 61 | module.exports = { 62 | create, 63 | meta: { 64 | type: 'problem', 65 | docs: { 66 | description: 'Ensure no tests have the same title.', 67 | url: util.getDocsUrl(__filename), 68 | }, 69 | schema: [], 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /rules/no-ignored-test-files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const util = require('../util'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | const create = context => { 8 | const filename = context.getFilename(); 9 | const [overrides] = context.options; 10 | 11 | if (filename === '' || filename === '') { 12 | return {}; 13 | } 14 | 15 | let hasTestCall = false; 16 | 17 | const ava = createAvaRule(); 18 | return ava.merge({ 19 | CallExpression: visitIf([ 20 | ava.isInTestFile, 21 | ava.isTestNode, 22 | ])(() => { 23 | hasTestCall = true; 24 | }), 25 | 'Program:exit'(node) { 26 | if (!hasTestCall) { 27 | return; 28 | } 29 | 30 | const avaHelper = util.loadAvaHelper(filename, overrides); 31 | if (!avaHelper) { 32 | return {}; 33 | } 34 | 35 | const {isHelper, isTest} = avaHelper.classifyFile(filename); 36 | 37 | if (!isTest) { 38 | if (isHelper) { 39 | context.report({node, message: 'AVA treats this as a helper file.'}); 40 | } else { 41 | context.report({node, message: 'AVA ignores this file.'}); 42 | } 43 | } 44 | 45 | hasTestCall = false; 46 | }, 47 | }); 48 | }; 49 | 50 | const schema = [{ 51 | type: 'object', 52 | properties: { 53 | extensions: { 54 | type: 'array', 55 | }, 56 | files: { 57 | type: 'array', 58 | }, 59 | helpers: { 60 | type: 'array', 61 | }, 62 | }, 63 | }]; 64 | 65 | module.exports = { 66 | create, 67 | meta: { 68 | type: 'suggestion', 69 | docs: { 70 | description: 'Ensure no tests are written in ignored files.', 71 | url: util.getDocsUrl(__filename), 72 | }, 73 | schema, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /rules/no-import-test-files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('node:path'); 4 | const util = require('../util'); 5 | 6 | // Assume absolute paths can be classified by AVA. 7 | const isFileImport = name => path.isAbsolute(name) || name.startsWith('./') || name.startsWith('../'); 8 | 9 | const create = context => { 10 | const filename = context.getFilename(); 11 | const [overrides] = context.options; 12 | 13 | if (filename === '' || filename === '') { 14 | return {}; 15 | } 16 | 17 | const resolveFrom = path.dirname(filename); 18 | 19 | let loadedAvaHelper = false; 20 | let avaHelper; 21 | 22 | const validateImportPath = (node, importPath) => { 23 | if (!importPath || typeof importPath !== 'string') { 24 | return; 25 | } 26 | 27 | if (!isFileImport(importPath)) { 28 | return; 29 | } 30 | 31 | if (!loadedAvaHelper) { 32 | avaHelper = util.loadAvaHelper(filename, overrides); 33 | loadedAvaHelper = true; 34 | } 35 | 36 | if (!avaHelper) { 37 | return {}; 38 | } 39 | 40 | const {isTest} = avaHelper.classifyImport(path.resolve(resolveFrom, importPath)); 41 | if (isTest) { 42 | context.report({ 43 | node, 44 | message: 'Test files should not be imported.', 45 | }); 46 | } 47 | }; 48 | 49 | return { 50 | ImportDeclaration(node) { 51 | validateImportPath(node, node.source.value); 52 | }, 53 | CallExpression(node) { 54 | if (!(node.callee.type === 'Identifier' && node.callee.name === 'require')) { 55 | return; 56 | } 57 | 58 | if (node.arguments[0]) { 59 | validateImportPath(node, node.arguments[0].value); 60 | } 61 | }, 62 | }; 63 | }; 64 | 65 | const schema = [{ 66 | type: 'object', 67 | properties: { 68 | extensions: { 69 | type: 'array', 70 | }, 71 | files: { 72 | type: 'array', 73 | }, 74 | }, 75 | }]; 76 | 77 | module.exports = { 78 | create, 79 | meta: { 80 | type: 'suggestion', 81 | docs: { 82 | description: 'Ensure no test files are imported anywhere.', 83 | url: util.getDocsUrl(__filename), 84 | }, 85 | schema, 86 | }, 87 | }; 88 | -------------------------------------------------------------------------------- /rules/no-incorrect-deep-equal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const util = require('../util'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | const MESSAGE_ID = 'no-deep-equal-with-primative'; 8 | 9 | const buildDeepEqualMessage = (context, node) => { 10 | context.report({ 11 | node, 12 | messageId: MESSAGE_ID, 13 | data: { 14 | callee: node.callee.property.name, 15 | }, 16 | fix: fixer => fixer.replaceText(node.callee.property, 'is'), 17 | }); 18 | }; 19 | 20 | const buildNotDeepEqualMessage = (context, node) => { 21 | context.report({ 22 | node, 23 | messageId: MESSAGE_ID, 24 | data: { 25 | callee: node.callee.property.name, 26 | }, 27 | fix: fixer => fixer.replaceText(node.callee.property, 'not'), 28 | }); 29 | }; 30 | 31 | const create = context => { 32 | const ava = createAvaRule(); 33 | 34 | const callExpression = 'CallExpression'; 35 | const deepEqual = '[callee.property.name="deepEqual"]'; 36 | const notDeepEqual = '[callee.property.name="notDeepEqual"]'; 37 | 38 | const argumentsLiteral = ':matches([arguments.0.type="Literal"][arguments.0.regex="undefined"],[arguments.1.type="Literal"][arguments.1.regex="undefined"])'; 39 | const argumentsUndefined = ':matches([arguments.0.type="Identifier"][arguments.0.name="undefined"],[arguments.1.type="Identifier"][arguments.1.name="undefined"])'; 40 | const argumentsTemplate = ':matches([arguments.0.type="TemplateLiteral"],[arguments.1.type="TemplateLiteral"])'; 41 | 42 | return ava.merge({ 43 | [`${callExpression}${deepEqual}${argumentsLiteral}`]: visitIf([ 44 | ava.isInTestFile, 45 | ava.isInTestNode, 46 | ])(node => { 47 | buildDeepEqualMessage(context, node); 48 | }), 49 | [`${callExpression}${deepEqual}${argumentsUndefined}`]: visitIf([ 50 | ava.isInTestFile, 51 | ava.isInTestNode, 52 | ])(node => { 53 | buildDeepEqualMessage(context, node); 54 | }), 55 | [`${callExpression}${deepEqual}${argumentsTemplate}`]: visitIf([ 56 | ava.isInTestFile, 57 | ava.isInTestNode, 58 | ])(node => { 59 | buildDeepEqualMessage(context, node); 60 | }), 61 | [`${callExpression}${notDeepEqual}${argumentsLiteral}`]: visitIf([ 62 | ava.isInTestFile, 63 | ava.isInTestNode, 64 | ])(node => { 65 | buildNotDeepEqualMessage(context, node); 66 | }), 67 | [`${callExpression}${notDeepEqual}${argumentsUndefined}`]: visitIf([ 68 | ava.isInTestFile, 69 | ava.isInTestNode, 70 | ])(node => { 71 | buildNotDeepEqualMessage(context, node); 72 | }), 73 | [`${callExpression}${notDeepEqual}${argumentsTemplate}`]: visitIf([ 74 | ava.isInTestFile, 75 | ava.isInTestNode, 76 | ])(node => { 77 | buildNotDeepEqualMessage(context, node); 78 | }), 79 | }); 80 | }; 81 | 82 | module.exports = { 83 | create, 84 | meta: { 85 | type: 'suggestion', 86 | docs: { 87 | description: 'Disallow using `deepEqual` with primitives.', 88 | url: util.getDocsUrl(__filename), 89 | }, 90 | fixable: 'code', 91 | schema: [], 92 | messages: { 93 | [MESSAGE_ID]: 'Avoid using `{{callee}}` with literal primitives', 94 | }, 95 | }, 96 | }; 97 | -------------------------------------------------------------------------------- /rules/no-inline-assertions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | CallExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isTestNode, 14 | ])(node => { 15 | const functionArgumentIndex = node.arguments.length - 1; 16 | if (functionArgumentIndex > 1) { 17 | return; 18 | } 19 | 20 | const functionArgument = node.arguments[functionArgumentIndex]; 21 | 22 | if (!util.isFunctionExpression(functionArgument)) { 23 | return; 24 | } 25 | 26 | const {body} = functionArgument; 27 | if (body.type === 'CallExpression') { 28 | context.report({ 29 | node, 30 | message: 'The test implementation should not be an inline arrow function.', 31 | fix: fixer => [fixer.insertTextBefore(body, '{'), fixer.insertTextAfter(body, '}')], 32 | }); 33 | } 34 | }), 35 | }); 36 | }; 37 | 38 | module.exports = { 39 | create, 40 | meta: { 41 | type: 'suggestion', 42 | docs: { 43 | description: 'Ensure assertions are not called from inline arrow functions.', 44 | url: util.getDocsUrl(__filename), 45 | }, 46 | fixable: 'code', 47 | schema: [], 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /rules/no-nested-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | let nestedCount = 0; 10 | 11 | return ava.merge({ 12 | CallExpression: visitIf([ 13 | ava.isInTestFile, 14 | ava.isTestNode, 15 | ])(node => { 16 | nestedCount++; 17 | if (nestedCount >= 2) { 18 | context.report({ 19 | node, 20 | message: 'Tests should not be nested.', 21 | }); 22 | } 23 | }), 24 | 25 | 'CallExpression:exit': visitIf([ 26 | ava.isInTestFile, 27 | ava.isTestNode, 28 | ])(() => { 29 | nestedCount--; 30 | }), 31 | }); 32 | }; 33 | 34 | module.exports = { 35 | create, 36 | meta: { 37 | type: 'problem', 38 | docs: { 39 | description: 'Ensure no tests are nested.', 40 | url: util.getDocsUrl(__filename), 41 | }, 42 | schema: [], 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /rules/no-only-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | CallExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isTestNode, 14 | ])(node => { 15 | const propertyNode = util.getTestModifier(node, 'only'); 16 | if (propertyNode) { 17 | context.report({ 18 | node: propertyNode, 19 | message: '`test.only()` should not be used.', 20 | suggest: [{ 21 | desc: 'Remove the `.only`', 22 | fix: fixer => fixer.replaceTextRange.apply(null, util.removeTestModifier({ 23 | modifier: 'only', 24 | node, 25 | context, 26 | })), 27 | }], 28 | }); 29 | } 30 | }), 31 | }); 32 | }; 33 | 34 | module.exports = { 35 | create, 36 | meta: { 37 | type: 'suggestion', 38 | docs: { 39 | description: 'Ensure no `test.only()` are present.', 40 | url: util.getDocsUrl(__filename), 41 | }, 42 | fixable: 'code', 43 | hasSuggestions: true, 44 | schema: [], 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /rules/no-skip-assert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const util = require('../util'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | MemberExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isInTestNode, 14 | ])(node => { 15 | if (node.property.name === 'skip') { 16 | const root = util.getRootNode(node); 17 | if (root.object.name === 't' && util.assertionMethods.has(root.property.name)) { 18 | context.report({ 19 | node, 20 | message: 'No assertions should be skipped.', 21 | }); 22 | } 23 | } 24 | }), 25 | }); 26 | }; 27 | 28 | module.exports = { 29 | create, 30 | meta: { 31 | type: 'suggestion', 32 | docs: { 33 | description: 'Ensure no assertions are skipped.', 34 | url: util.getDocsUrl(__filename), 35 | }, 36 | schema: [], 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /rules/no-skip-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | CallExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isTestNode, 14 | ])(node => { 15 | const propertyNode = util.getTestModifier(node, 'skip'); 16 | if (propertyNode) { 17 | context.report({ 18 | node: propertyNode, 19 | message: 'No tests should be skipped.', 20 | suggest: [{ 21 | desc: 'Remove the `.skip`', 22 | fix: fixer => fixer.replaceTextRange.apply(null, util.removeTestModifier({ 23 | modifier: 'skip', 24 | node, 25 | context, 26 | })), 27 | }], 28 | }); 29 | } 30 | }), 31 | }); 32 | }; 33 | 34 | module.exports = { 35 | create, 36 | meta: { 37 | type: 'suggestion', 38 | docs: { 39 | description: 'Ensure no tests are skipped.', 40 | url: util.getDocsUrl(__filename), 41 | }, 42 | fixable: 'code', 43 | hasSuggestions: true, 44 | schema: [], 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /rules/no-todo-implementation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const util = require('../util'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | CallExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isTestNode, 14 | ])(node => { 15 | if (ava.hasTestModifier('todo') && node.arguments.some(argument => util.isFunctionExpression(argument))) { 16 | context.report({ 17 | node, 18 | message: '`test.todo()` should not be passed an implementation function.', 19 | }); 20 | } 21 | }), 22 | }); 23 | }; 24 | 25 | module.exports = { 26 | create, 27 | meta: { 28 | type: 'suggestion', 29 | docs: { 30 | description: 'Ensure `test.todo()` is not given an implementation function.', 31 | url: util.getDocsUrl(__filename), 32 | }, 33 | schema: [], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /rules/no-todo-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | CallExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isTestNode, 14 | ])(node => { 15 | if (ava.hasTestModifier('todo')) { 16 | context.report({ 17 | node, 18 | message: '`test.todo()` should not be used.', 19 | }); 20 | } 21 | }), 22 | }); 23 | }; 24 | 25 | module.exports = { 26 | create, 27 | meta: { 28 | type: 'suggestion', 29 | docs: { 30 | description: 'Ensure no `test.todo()` is used.', 31 | url: util.getDocsUrl(__filename), 32 | }, 33 | schema: [], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /rules/no-unknown-modifiers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const util = require('../util'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | const modifiers = new Set([ 8 | 'after', 9 | 'afterEach', 10 | 'always', 11 | 'before', 12 | 'beforeEach', 13 | 'default', 14 | 'only', 15 | 'serial', 16 | 'skip', 17 | 'todo', 18 | 'failing', 19 | 'macro', 20 | ]); 21 | 22 | const unknownModifiers = node => util.getTestModifiers(node) 23 | .filter(modifier => !modifiers.has(modifier.name)); 24 | 25 | const create = context => { 26 | const ava = createAvaRule(); 27 | 28 | return ava.merge({ 29 | CallExpression: visitIf([ 30 | ava.isInTestFile, 31 | ava.isTestNode, 32 | ])(node => { 33 | const unknown = unknownModifiers(node); 34 | 35 | if (unknown.length > 0) { 36 | context.report({ 37 | node: unknown[0], 38 | message: `Unknown test modifier \`.${unknown[0].name}\`.`, 39 | }); 40 | } 41 | }), 42 | }); 43 | }; 44 | 45 | module.exports = { 46 | create, 47 | meta: { 48 | type: 'problem', 49 | docs: { 50 | description: 'Disallow the use of unknown test modifiers.', 51 | url: util.getDocsUrl(__filename), 52 | }, 53 | schema: [], 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /rules/prefer-async-await.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | function containsThen(node) { 8 | if (!node 9 | || node.type !== 'CallExpression' 10 | || node.callee.type !== 'MemberExpression' 11 | ) { 12 | return false; 13 | } 14 | 15 | const {callee} = node; 16 | if (callee.property.type === 'Identifier' 17 | && callee.property.name === 'then' 18 | ) { 19 | return true; 20 | } 21 | 22 | return containsThen(callee.object); 23 | } 24 | 25 | const create = context => { 26 | const ava = createAvaRule(); 27 | 28 | const check = visitIf([ 29 | ava.isInTestFile, 30 | ava.isInTestNode, 31 | ])(node => { 32 | if (node.body.type !== 'BlockStatement') { 33 | return; 34 | } 35 | 36 | const statements = node.body.body; 37 | const returnStatement = statements.find(statement => statement.type === 'ReturnStatement'); 38 | if (returnStatement && containsThen(returnStatement.argument)) { 39 | context.report({ 40 | node, 41 | message: 'Prefer using async/await instead of returning a Promise.', 42 | }); 43 | } 44 | }); 45 | 46 | return ava.merge({ 47 | ArrowFunctionExpression: check, 48 | FunctionExpression: check, 49 | }); 50 | }; 51 | 52 | module.exports = { 53 | create, 54 | meta: { 55 | type: 'suggestion', 56 | docs: { 57 | description: 'Prefer using async/await instead of returning a Promise.', 58 | url: util.getDocsUrl(__filename), 59 | }, 60 | schema: [], 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /rules/prefer-power-assert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isDeepStrictEqual} = require('node:util'); 4 | const espurify = require('espurify'); 5 | const {visitIf} = require('enhance-visitors'); 6 | const createAvaRule = require('../create-ava-rule'); 7 | const util = require('../util'); 8 | 9 | const notAllowed = [ 10 | 'truthy', 11 | 'true', 12 | 'falsy', 13 | 'false', 14 | 'is', 15 | 'not', 16 | 'regex', 17 | 'notRegex', 18 | 'ifError', 19 | ]; 20 | 21 | const assertionCalleeAst = methodName => ({ 22 | type: 'MemberExpression', 23 | object: { 24 | type: 'Identifier', 25 | name: 't', 26 | }, 27 | property: { 28 | type: 'Identifier', 29 | name: methodName, 30 | }, 31 | computed: false, 32 | }); 33 | 34 | const skippedAssertionCalleeAst = methodName => ({ 35 | type: 'MemberExpression', 36 | object: { 37 | type: 'MemberExpression', 38 | object: { 39 | type: 'Identifier', 40 | name: 't', 41 | }, 42 | property: { 43 | type: 'Identifier', 44 | name: 'skip', 45 | }, 46 | computed: false, 47 | }, 48 | property: { 49 | type: 'Identifier', 50 | name: methodName, 51 | }, 52 | computed: false, 53 | }); 54 | 55 | const isCalleeMatched = (callee, methodName) => 56 | isDeepStrictEqual(callee, assertionCalleeAst(methodName)) 57 | || isDeepStrictEqual(callee, skippedAssertionCalleeAst(methodName)); 58 | 59 | const create = context => { 60 | const ava = createAvaRule(); 61 | 62 | return ava.merge({ 63 | CallExpression: visitIf([ 64 | ava.isInTestFile, 65 | ava.isInTestNode, 66 | ])(node => { 67 | const callee = espurify(node.callee); 68 | 69 | if (callee.type === 'MemberExpression') { 70 | for (const methodName of notAllowed) { 71 | if (isCalleeMatched(callee, methodName)) { 72 | context.report({ 73 | node, 74 | message: 'Only asserts with no power-assert alternative are allowed.', 75 | }); 76 | } 77 | } 78 | } 79 | }), 80 | }); 81 | }; 82 | 83 | module.exports = { 84 | create, 85 | meta: { 86 | type: 'suggestion', 87 | docs: { 88 | description: 'Enforce the use of the asserts that have no [power-assert](https://github.com/power-assert-js/power-assert) alternative.', 89 | url: util.getDocsUrl(__filename), 90 | }, 91 | schema: [], 92 | }, 93 | }; 94 | -------------------------------------------------------------------------------- /rules/prefer-t-regex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | const booleanTests = new Set([ 11 | 'true', 12 | 'false', 13 | 'truthy', 14 | 'falsy', 15 | ]); 16 | 17 | const equalityTests = new Set([ 18 | 'is', 19 | 'deepEqual', 20 | ]); 21 | 22 | // Find the latest reference to the given identifier's name. 23 | const findReference = node => { 24 | const {sourceCode} = context; 25 | const reference = sourceCode.getScope(node).references.find(reference => reference.identifier.name === node.name); 26 | 27 | if (reference?.resolved) { 28 | const definitions = reference.resolved.defs; 29 | 30 | if (definitions.length === 0) { 31 | return; 32 | } 33 | 34 | return definitions.at(-1).node; 35 | } 36 | }; 37 | 38 | /* 39 | Recursively find the "origin" node of the given node. 40 | 41 | Note: `sourceCode.getScope()` doesn't contain information about the outer scope so in most cases this function will only find the reference directly above the current scope. So the following code will only find the reference in this order: y -> x, and it will have no knowledge of the number `0`. (assuming we run this function on the identifier `y`) 42 | 43 | ``` 44 | const test = require('ava'); 45 | 46 | let x = 0; 47 | let y = x; 48 | 49 | test(t => { 50 | t.is(y, 0); 51 | }); 52 | ``` 53 | */ 54 | const findRootReference = node => { 55 | if (!node) { 56 | return; 57 | } 58 | 59 | if (node.type === 'Identifier') { 60 | const reference = findReference(node); 61 | 62 | if (reference?.init) { 63 | return findRootReference(reference.init); 64 | } 65 | 66 | return node; 67 | } 68 | 69 | if (node.type === 'CallExpression' || node.type === 'NewExpression') { 70 | return findRootReference(node.callee); 71 | } 72 | 73 | if (node.type === 'MemberExpression') { 74 | return findRootReference(node.object); 75 | } 76 | 77 | return node; 78 | }; 79 | 80 | /* 81 | Determine if the given node is a regex expression. 82 | 83 | There are two ways to create regex expressions in JavaScript: Regex literal and `RegExp` class. 84 | 1. Regex literal can be easily looked up using the `.regex` property on the node. 85 | 2. `RegExp` class can't be looked up so the function just checks for the name `RegExp`. 86 | */ 87 | const isRegExp = lookup => { 88 | if (!lookup) { 89 | return false; 90 | } 91 | 92 | if (lookup.regex) { 93 | return true; 94 | } 95 | 96 | // Look up references in case it's a variable or RegExp declaration. 97 | const reference = findRootReference(lookup); 98 | 99 | if (!reference) { 100 | return false; 101 | } 102 | 103 | return reference.regex ?? reference.name === 'RegExp'; 104 | }; 105 | 106 | const booleanHandler = node => { 107 | const firstArgument = node.arguments[0]; 108 | 109 | if (!firstArgument) { 110 | return; 111 | } 112 | 113 | const isFunctionCall = firstArgument.type === 'CallExpression'; 114 | if (!isFunctionCall || !firstArgument.callee.property) { 115 | return; 116 | } 117 | 118 | const {name} = firstArgument.callee.property; 119 | let lookup = {}; 120 | let variable = {}; 121 | 122 | if (name === 'test') { 123 | // Represent: `lookup.test(variable)` 124 | lookup = firstArgument.callee.object; 125 | variable = firstArgument.arguments[0]; 126 | } else if (['search', 'match'].includes(name)) { 127 | // Represent: `variable.match(lookup)` 128 | lookup = firstArgument.arguments[0]; 129 | variable = firstArgument.callee.object; 130 | } 131 | 132 | if (!isRegExp(lookup)) { 133 | return; 134 | } 135 | 136 | const assertion = ['true', 'truthy'].includes(node.callee.property.name) ? 'regex' : 'notRegex'; 137 | 138 | const fix = fixer => { 139 | const source = context.getSourceCode(); 140 | return [ 141 | fixer.replaceText(node.callee.property, assertion), 142 | fixer.replaceText(firstArgument, `${source.getText(variable)}, ${source.getText(lookup)}`), 143 | ]; 144 | }; 145 | 146 | context.report({ 147 | node, 148 | message: `Prefer using the \`t.${assertion}()\` assertion.`, 149 | fix, 150 | }); 151 | }; 152 | 153 | const equalityHandler = node => { 154 | const [firstArgument, secondArgument] = node.arguments; 155 | 156 | const firstArgumentIsRegex = isRegExp(firstArgument); 157 | const secondArgumentIsRegex = isRegExp(secondArgument); 158 | 159 | // If both are regex, or neither are, the expression is ok. 160 | if (firstArgumentIsRegex === secondArgumentIsRegex) { 161 | return; 162 | } 163 | 164 | const matchee = secondArgumentIsRegex ? firstArgument : secondArgument; 165 | 166 | if (!matchee) { 167 | return; 168 | } 169 | 170 | const regex = secondArgumentIsRegex ? secondArgument : firstArgument; 171 | 172 | const booleanFixer = assertion => fixer => { 173 | const source = context.getSourceCode(); 174 | return [ 175 | fixer.replaceText(node.callee.property, assertion), 176 | fixer.replaceText(firstArgument, `${source.getText(regex.arguments[0])}`), 177 | fixer.replaceText(secondArgument, `${source.getText(regex.callee.object)}`), 178 | ]; 179 | }; 180 | 181 | // Only fix a statically verifiable equality. 182 | if (regex && matchee.type === 'Literal') { 183 | let assertion; 184 | 185 | if (matchee.raw === 'true') { 186 | assertion = 'regex'; 187 | } else if (matchee.raw === 'false') { 188 | assertion = 'notRegex'; 189 | } else { 190 | return; 191 | } 192 | 193 | context.report({ 194 | node, 195 | message: `Prefer using the \`t.${assertion}()\` assertion.`, 196 | fix: booleanFixer(assertion), 197 | }); 198 | } 199 | }; 200 | 201 | return ava.merge({ 202 | CallExpression: visitIf([ 203 | ava.isInTestFile, 204 | ava.isInTestNode, 205 | ], 206 | )(node => { 207 | if (!node?.callee?.property) { 208 | return; 209 | } 210 | 211 | const isAssertion = node.callee.type === 'MemberExpression' 212 | && util.getNameOfRootNodeObject(node.callee) === 't'; 213 | 214 | const isBooleanAssertion = isAssertion 215 | && booleanTests.has(node.callee.property.name); 216 | 217 | const isEqualityAssertion = isAssertion 218 | && equalityTests.has(node.callee.property.name); 219 | 220 | if (isBooleanAssertion) { 221 | booleanHandler(node); 222 | } else if (isEqualityAssertion) { 223 | equalityHandler(node); 224 | } 225 | }), 226 | }); 227 | }; 228 | 229 | module.exports = { 230 | create, 231 | meta: { 232 | type: 'suggestion', 233 | docs: { 234 | description: 'Prefer using `t.regex()` to test regular expressions.', 235 | url: util.getDocsUrl(__filename), 236 | }, 237 | fixable: 'code', 238 | schema: [], 239 | }, 240 | }; 241 | -------------------------------------------------------------------------------- /rules/test-title-format.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | let titleRegExp; 11 | if (context.options[0]?.format) { 12 | titleRegExp = new RegExp(context.options[0].format); 13 | } else { 14 | return {}; 15 | } 16 | 17 | return ava.merge({ 18 | CallExpression: visitIf([ 19 | ava.isInTestFile, 20 | ava.isTestNode, 21 | ava.hasNoUtilityModifier, 22 | ])(node => { 23 | const requiredLength = ava.hasTestModifier('todo') ? 1 : 2; 24 | const hasTitle = node.arguments.length >= requiredLength; 25 | 26 | if (hasTitle) { 27 | const title = node.arguments[0]; 28 | if (title.type === 'Literal' && !titleRegExp.test(title.value)) { 29 | context.report({ 30 | node, 31 | message: `The test title doesn't match the required format: \`${titleRegExp}\`.`, 32 | }); 33 | } 34 | } 35 | }), 36 | }); 37 | }; 38 | 39 | const schema = [ 40 | { 41 | type: 'object', 42 | properties: { 43 | format: { 44 | type: 'string', 45 | default: undefined, 46 | }, 47 | }, 48 | }, 49 | ]; 50 | 51 | module.exports = { 52 | create, 53 | meta: { 54 | type: 'suggestion', 55 | docs: { 56 | description: 'Ensure test titles have a certain format.', 57 | url: util.getDocsUrl(__filename), 58 | }, 59 | schema, 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /rules/test-title.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | CallExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isTestNode, 14 | ava.hasNoUtilityModifier, 15 | ])(node => { 16 | const firstArgumentIsFunction = node.arguments.length === 0 || util.isFunctionExpression(node.arguments[0]); 17 | 18 | if (firstArgumentIsFunction) { 19 | context.report({ 20 | node, 21 | message: 'Test should have a title.', 22 | }); 23 | } 24 | }), 25 | }); 26 | }; 27 | 28 | module.exports = { 29 | create, 30 | meta: { 31 | type: 'problem', 32 | docs: { 33 | description: 'Ensure tests have a title.', 34 | url: util.getDocsUrl(__filename), 35 | }, 36 | schema: [], 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /rules/use-t-throws-async-well.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const util = require('../util'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | CallExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isInTestNode, 14 | ])(node => { 15 | if ( 16 | node.parent.type === 'ExpressionStatement' 17 | && node.callee.type === 'MemberExpression' 18 | && (node.callee.property.name === 'throwsAsync' || node.callee.property.name === 'notThrowsAsync') 19 | && node.callee.object.name === 't' 20 | ) { 21 | const message = `Use \`await\` with \`t.${node.callee.property.name}()\`.`; 22 | if (ava.isInTestNode().arguments[0].async) { 23 | context.report({ 24 | node, 25 | message, 26 | fix: fixer => fixer.replaceText(node.callee, `await ${context.getSourceCode().getText(node.callee)}`), 27 | }); 28 | } else { 29 | context.report({ 30 | node, 31 | message, 32 | }); 33 | } 34 | } 35 | }), 36 | }); 37 | }; 38 | 39 | module.exports = { 40 | create, 41 | meta: { 42 | type: 'problem', 43 | docs: { 44 | description: 'Ensure that `t.throwsAsync()` and `t.notThrowsAsync()` are awaited.', 45 | url: util.getDocsUrl(__filename), 46 | }, 47 | fixable: 'code', 48 | schema: [], 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /rules/use-t-well.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const MicroSpellingCorrecter = require('micro-spelling-correcter'); 5 | const util = require('../util'); 6 | const createAvaRule = require('../create-ava-rule'); 7 | 8 | const properties = new Set([ 9 | ...util.executionMethods, 10 | 'context', 11 | 'title', 12 | 'skip', 13 | ]); 14 | 15 | const correcter = new MicroSpellingCorrecter([...properties]); 16 | 17 | const isCallExpression = node => 18 | node.parent.type === 'CallExpression' 19 | && node.parent.callee === node; 20 | 21 | const getMemberNodes = node => { 22 | if (node.object.type === 'MemberExpression') { 23 | return [...getMemberNodes(node.object), node.property]; 24 | } 25 | 26 | return [node.property]; 27 | }; 28 | 29 | const create = context => { 30 | const ava = createAvaRule(); 31 | 32 | return ava.merge({ 33 | CallExpression: visitIf([ 34 | ava.isInTestFile, 35 | ava.isInTestNode, 36 | ])(node => { 37 | if (node.callee.type !== 'MemberExpression' 38 | && node.callee.name === 't') { 39 | context.report({ 40 | node, 41 | message: '`t` is not a function.', 42 | }); 43 | } 44 | }), 45 | MemberExpression: visitIf([ 46 | ava.isInTestFile, 47 | ava.isInTestNode, 48 | ])(node => { 49 | if (node.parent.type === 'MemberExpression' 50 | || util.getNameOfRootNodeObject(node) !== 't') { 51 | return; 52 | } 53 | 54 | const members = getMemberNodes(node); 55 | 56 | const skipPositions = []; 57 | let hadCall = false; 58 | for (const [i, member] of members.entries()) { 59 | const {name} = member; 60 | 61 | let corrected = correcter.correct(name); 62 | 63 | if (i !== 0 && (corrected === 'context' || corrected === 'title')) { // `context` and `title` can only be first 64 | corrected = undefined; 65 | } 66 | 67 | if (corrected !== name) { 68 | if (corrected === undefined) { 69 | if (isCallExpression(node)) { 70 | context.report({ 71 | node, 72 | message: `Unknown assertion method \`.${name}\`.`, 73 | }); 74 | } else { 75 | context.report({ 76 | node, 77 | message: `Unknown member \`.${name}\`. Use \`.context.${name}\` instead.`, 78 | }); 79 | } 80 | } else { 81 | context.report({ 82 | node, 83 | message: `Misspelled \`.${corrected}\` as \`.${name}\`.`, 84 | fix: fixer => fixer.replaceText(member, corrected), 85 | }); 86 | } 87 | 88 | return; // Don't check further 89 | } 90 | 91 | if (name === 'context' || name === 'title') { 92 | if (members.length === 1 && isCallExpression(node)) { 93 | context.report({ 94 | node, 95 | message: `Unknown assertion method \`.${name}\`.`, 96 | }); 97 | } 98 | 99 | return; // Don't check further 100 | } 101 | 102 | if (name === 'skip') { 103 | skipPositions.push(i); 104 | } else { 105 | if (hadCall) { 106 | context.report({ 107 | node, 108 | message: 'Can\'t chain assertion methods.', 109 | }); 110 | } 111 | 112 | hadCall = true; 113 | } 114 | } 115 | 116 | if (!hadCall) { 117 | context.report({ 118 | node, 119 | message: 'Missing assertion method.', 120 | }); 121 | } 122 | 123 | if (skipPositions.length > 1) { 124 | context.report({ 125 | node, 126 | message: 'Too many chained uses of `.skip`.', 127 | fix(fixer) { 128 | const chain = ['t', ...members.map(member => member.name).filter(name => name !== 'skip'), 'skip']; 129 | return fixer.replaceText(node, chain.join('.')); 130 | }, 131 | }); 132 | } 133 | 134 | if (skipPositions.length === 1 && skipPositions[0] !== members.length - 1) { 135 | context.report({ 136 | node, 137 | message: '`.skip` modifier should be the last in chain.', 138 | fix(fixer) { 139 | const chain = ['t', ...members.map(member => member.name).filter(name => name !== 'skip'), 'skip']; 140 | return fixer.replaceText(node, chain.join('.')); 141 | }, 142 | }); 143 | } 144 | }), 145 | }); 146 | }; 147 | 148 | module.exports = { 149 | create, 150 | meta: { 151 | type: 'problem', 152 | docs: { 153 | description: 'Disallow the incorrect use of `t`.', 154 | url: util.getDocsUrl(__filename), 155 | }, 156 | fixable: 'code', 157 | schema: [], 158 | }, 159 | }; 160 | -------------------------------------------------------------------------------- /rules/use-t.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {visitIf} = require('enhance-visitors'); 4 | const createAvaRule = require('../create-ava-rule'); 5 | const util = require('../util'); 6 | 7 | const create = context => { 8 | const ava = createAvaRule(); 9 | 10 | return ava.merge({ 11 | CallExpression: visitIf([ 12 | ava.isInTestFile, 13 | ava.isTestNode, 14 | ])(node => { 15 | const index = node.arguments.length - 1; 16 | if (index > 1) { 17 | return; 18 | } 19 | 20 | let implementationArgument = node.arguments[index]; 21 | if (ava.hasTestModifier('macro') && implementationArgument.type === 'ObjectExpression') { 22 | const execProperty = implementationArgument.properties.find(p => p.key.name === 'exec'); 23 | implementationArgument = execProperty?.value; 24 | } 25 | 26 | if (!implementationArgument || !implementationArgument.params || implementationArgument.params.length === 0) { 27 | return; 28 | } 29 | 30 | if (implementationArgument.params[0].name !== 't') { 31 | context.report({ 32 | node, 33 | message: 'Test parameter should be named `t`.', 34 | }); 35 | } 36 | }), 37 | }); 38 | }; 39 | 40 | module.exports = { 41 | create, 42 | meta: { 43 | type: 'suggestion', 44 | docs: { 45 | description: 'Ensure test functions use `t` as their parameter.', 46 | url: util.getDocsUrl(__filename), 47 | }, 48 | schema: [], 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /rules/use-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('node:path'); 4 | const {isDeepStrictEqual} = require('node:util'); 5 | const espurify = require('espurify'); 6 | const util = require('../util'); 7 | 8 | const avaVariableDeclaratorInitAst = { 9 | type: 'CallExpression', 10 | callee: { 11 | type: 'Identifier', 12 | name: 'require', 13 | }, 14 | arguments: [ 15 | { 16 | type: 'Literal', 17 | value: 'ava', 18 | }, 19 | ], 20 | }; 21 | 22 | function report(context, node) { 23 | context.report({ 24 | node, 25 | message: 'AVA should be imported as `test`.', 26 | }); 27 | } 28 | 29 | const create = context => { 30 | const extension = path.extname(context.getFilename()); 31 | const isTypeScript = extension === '.ts' || extension === '.tsx'; 32 | 33 | return { 34 | 'ImportDeclaration[importKind!="type"]'(node) { 35 | if (node.source.value === 'ava') { 36 | const {name} = node.specifiers[0].local; 37 | if (name !== 'test' && (!isTypeScript || name !== 'anyTest')) { 38 | report(context, node); 39 | } 40 | } 41 | }, 42 | VariableDeclarator(node) { 43 | if (node.init && isDeepStrictEqual(espurify(node.init), avaVariableDeclaratorInitAst)) { 44 | const {name} = node.id; 45 | if (name !== 'test' && (!isTypeScript || name !== 'anyTest')) { 46 | report(context, node); 47 | } 48 | } 49 | }, 50 | }; 51 | }; 52 | 53 | module.exports = { 54 | create, 55 | meta: { 56 | type: 'suggestion', 57 | docs: { 58 | description: 'Ensure that AVA is imported with `test` as the variable name.', 59 | url: util.getDocsUrl(__filename), 60 | }, 61 | schema: [], 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /rules/use-true-false.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {isDeepStrictEqual} = require('node:util'); 4 | const espree = require('espree'); 5 | const espurify = require('espurify'); 6 | const {visitIf} = require('enhance-visitors'); 7 | const util = require('../util'); 8 | const createAvaRule = require('../create-ava-rule'); 9 | 10 | const booleanBinaryOperators = new Set([ 11 | '==', 12 | '===', 13 | '!=', 14 | '!==', 15 | '<', 16 | '<=', 17 | '>', 18 | '>=', 19 | ]); 20 | 21 | const knownBooleanSignatures = [ 22 | 'isFinite()', 23 | 'isNaN()', 24 | 'Object.is()', 25 | 'Object.isExtensible()', 26 | 'Object.isFrozen()', 27 | 'Object.isSealed()', 28 | 'Boolean()', 29 | 'Number.isNaN()', 30 | 'Number.isFinite()', 31 | 'Number.isInteger()', 32 | 'Number.isSafeInteger()', 33 | 'Array.isArray()', 34 | 'ArrayBuffer.isView()', 35 | 'SharedArrayBuffer.isView()', 36 | 'Reflect.has()', 37 | 'Reflect.isExtensible()', 38 | ].map(signature => espurify(espree.parse(signature).body[0].expression.callee)); 39 | 40 | function matchesKnownBooleanExpression(argument) { 41 | if (argument.type !== 'CallExpression') { 42 | return false; 43 | } 44 | 45 | const callee = espurify(argument.callee); 46 | 47 | return knownBooleanSignatures.some(signature => isDeepStrictEqual(callee, signature)); 48 | } 49 | 50 | const create = context => { 51 | const ava = createAvaRule(); 52 | 53 | return ava.merge({ 54 | CallExpression: visitIf([ 55 | ava.isInTestFile, 56 | ava.isInTestNode, 57 | ])(node => { 58 | if ( 59 | node.callee.type === 'MemberExpression' 60 | && (node.callee.property.name === 'truthy' || node.callee.property.name === 'falsy') 61 | && node.callee.object.name === 't' 62 | ) { 63 | const argument = node.arguments[0]; 64 | 65 | if (argument 66 | && ((argument.type === 'BinaryExpression' && booleanBinaryOperators.has(argument.operator)) 67 | || (argument.type === 'UnaryExpression' && argument.operator === '!') 68 | || (argument.type === 'Literal' && argument.value === Boolean(argument.value)) 69 | || (matchesKnownBooleanExpression(argument))) 70 | ) { 71 | if (node.callee.property.name === 'falsy') { 72 | context.report({ 73 | node, 74 | message: '`t.false()` should be used instead of `t.falsy()`.', 75 | }); 76 | } else { 77 | context.report({ 78 | node, 79 | message: '`t.true()` should be used instead of `t.truthy()`.', 80 | }); 81 | } 82 | } 83 | } 84 | }), 85 | }); 86 | }; 87 | 88 | module.exports = { 89 | create, 90 | meta: { 91 | type: 'suggestion', 92 | docs: { 93 | description: 'Ensure that `t.true()`/`t.false()` are used instead of `t.truthy()`/`t.falsy()`.', 94 | url: util.getDocsUrl(__filename), 95 | }, 96 | schema: [], 97 | }, 98 | }; 99 | -------------------------------------------------------------------------------- /test/create-ava-rule.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const createAvaRule = require('../create-ava-rule'); 6 | 7 | const rule = { 8 | create(context) { 9 | const ava = createAvaRule(); 10 | 11 | return ava.merge({ 12 | 'Program:exit'(node) { 13 | if (!ava.isInTestFile()) { 14 | context.report({node, message: 'not a test file'}); 15 | } 16 | }, 17 | }); 18 | }, 19 | }; 20 | 21 | const ruleTester = avaRuleTester(test, { 22 | env: { 23 | es6: true, 24 | }, 25 | parserOptions: { 26 | sourceType: 'module', 27 | }, 28 | }); 29 | 30 | const errors = [ 31 | { 32 | message: 'not a test file', 33 | }, 34 | ]; 35 | 36 | ruleTester.run('rule-fixture', rule, { 37 | valid: [ 38 | 'const test = require(\'ava\');', 39 | 'const {serial} = require(\'ava\');', 40 | 'const {serial: test} = require(\'ava\');', 41 | 'import test from \'ava\';', 42 | 'import {serial} from \'ava\';', 43 | 'import {serial as test} from \'ava\';', 44 | ], 45 | 46 | invalid: [ 47 | { 48 | code: 'const test2 = require(\'ava\');', 49 | errors, 50 | }, 51 | { 52 | code: 'const {serial2} = require(\'ava\');', 53 | errors, 54 | }, 55 | { 56 | code: 'const {serial2: test} = require(\'ava\');', 57 | errors, 58 | }, 59 | { 60 | code: 'import test2 from \'ava\';', 61 | errors, 62 | }, 63 | { 64 | code: 'import {serial2} from \'ava\';', 65 | errors, 66 | }, 67 | { 68 | code: 'import {serial2 as test} from \'ava\';', 69 | errors, 70 | }, 71 | ], 72 | }); 73 | -------------------------------------------------------------------------------- /test/hooks-order.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const {outdent} = require('outdent'); 6 | const rule = require('../rules/hooks-order'); 7 | 8 | const ruleTester = avaRuleTester(test, { 9 | env: { 10 | es6: true, 11 | }, 12 | }); 13 | 14 | const errors = [{}]; 15 | const header = 'const test = require(\'ava\');'; 16 | 17 | ruleTester.run('no-todo-test', rule, { 18 | valid: [ 19 | outdent` 20 | ${header} 21 | 22 | test.before(t => { 23 | doFoo(); 24 | }); 25 | 26 | test.after(t => { 27 | doFoo(); 28 | }); 29 | 30 | test.after.always(t => { 31 | doFoo(); 32 | }); 33 | 34 | test.beforeEach(t => { 35 | doFoo(); 36 | }); 37 | 38 | test.afterEach(t => { 39 | doFoo(); 40 | }); 41 | 42 | test.afterEach.always(t => { 43 | doFoo(); 44 | }); 45 | 46 | test('foo', t => { 47 | t.true(true); 48 | }); 49 | `, 50 | outdent` 51 | ${header} 52 | 53 | test.before(t => { 54 | doFoo(); 55 | }); 56 | 57 | console.log('foo'); 58 | 59 | test.after.always(t => { 60 | doFoo(); 61 | }); 62 | 63 | const foo = 'foo'; 64 | 65 | test.afterEach(t => { 66 | doFoo(); 67 | }); 68 | 69 | test('foo', t => { 70 | t.true(true); 71 | }); 72 | `, 73 | outdent` 74 | test.before(t => { 75 | doFoo(); 76 | }); 77 | 78 | test.after(t => { 79 | doFoo(); 80 | }); 81 | 82 | test.after.always(t => { 83 | doFoo(); 84 | }); 85 | 86 | test.beforeEach(t => { 87 | doFoo(); 88 | }); 89 | 90 | test.afterEach(t => { 91 | doFoo(); 92 | }); 93 | 94 | test.afterEach.always(t => { 95 | doFoo(); 96 | }); 97 | 98 | test('foo', t => { 99 | t.true(true); 100 | }); 101 | `, 102 | outdent` 103 | ${header} 104 | 105 | test.before(t => { 106 | doFoo(); 107 | }); 108 | 109 | test.after(t => { 110 | doFoo(); 111 | }); 112 | `, 113 | outdent` 114 | test.after(t => { 115 | doFoo(); 116 | }); 117 | 118 | test.before(t => { 119 | doFoo(); 120 | }); 121 | `, 122 | outdent` 123 | ${header} 124 | 125 | test('foo', t => { 126 | t.true(true); 127 | }); 128 | `, 129 | ], 130 | invalid: [ 131 | { 132 | code: outdent` 133 | ${header} 134 | 135 | test.after(t => { 136 | doFoo(); 137 | }); 138 | 139 | test.before(t => { 140 | doFoo(); 141 | }); 142 | `, 143 | output: outdent` 144 | ${header} 145 | 146 | test.before(t => { 147 | doFoo(); 148 | }); 149 | 150 | test.after(t => { 151 | doFoo(); 152 | }); 153 | `, 154 | errors, 155 | }, 156 | { 157 | code: outdent` 158 | ${header} 159 | 160 | test.after(t => { 161 | doFoo(); 162 | }); 163 | 164 | test.before(t => { 165 | doFoo(); 166 | }); 167 | 168 | test('foo', t => { 169 | t.true(true); 170 | }); 171 | `, 172 | output: outdent` 173 | ${header} 174 | 175 | test.before(t => { 176 | doFoo(); 177 | }); 178 | 179 | test.after(t => { 180 | doFoo(); 181 | }); 182 | 183 | test('foo', t => { 184 | t.true(true); 185 | }); 186 | `, 187 | errors, 188 | }, 189 | { 190 | code: outdent` 191 | ${header} 192 | 193 | test.after.always(t => { 194 | doFoo(); 195 | }); 196 | 197 | test.before(t => { 198 | doFoo(); 199 | }); 200 | 201 | test('foo', t => { 202 | t.true(true); 203 | }); 204 | `, 205 | output: outdent` 206 | ${header} 207 | 208 | test.before(t => { 209 | doFoo(); 210 | }); 211 | 212 | test.after.always(t => { 213 | doFoo(); 214 | }); 215 | 216 | test('foo', t => { 217 | t.true(true); 218 | }); 219 | `, 220 | errors, 221 | }, 222 | { 223 | code: outdent` 224 | ${header} 225 | 226 | test('foo', t => { 227 | t.true(true); 228 | }); 229 | 230 | test.after.always(t => { 231 | doFoo(); 232 | }); 233 | `, 234 | output: outdent` 235 | ${header} 236 | 237 | test.after.always(t => { 238 | doFoo(); 239 | }); 240 | 241 | test('foo', t => { 242 | t.true(true); 243 | }); 244 | `, 245 | errors, 246 | }, 247 | { 248 | code: outdent` 249 | ${header} 250 | 251 | test.beforeEach(t => { 252 | doFoo(); 253 | }); 254 | 255 | test.before(t => { 256 | doFoo(); 257 | }); 258 | 259 | test('foo', t => { 260 | t.true(true); 261 | }); 262 | `, 263 | output: outdent` 264 | ${header} 265 | 266 | test.before(t => { 267 | doFoo(); 268 | }); 269 | 270 | test.beforeEach(t => { 271 | doFoo(); 272 | }); 273 | 274 | test('foo', t => { 275 | t.true(true); 276 | }); 277 | `, 278 | errors, 279 | }, 280 | { 281 | code: outdent` 282 | ${header} 283 | 284 | test.afterEach(t => { 285 | doFoo(); 286 | }); 287 | 288 | test.before(t => { 289 | doFoo(); 290 | }); 291 | 292 | test('foo', t => { 293 | t.true(true); 294 | }); 295 | `, 296 | output: outdent` 297 | ${header} 298 | 299 | test.before(t => { 300 | doFoo(); 301 | }); 302 | 303 | test.afterEach(t => { 304 | doFoo(); 305 | }); 306 | 307 | test('foo', t => { 308 | t.true(true); 309 | }); 310 | `, 311 | errors, 312 | }, 313 | { 314 | code: outdent` 315 | ${header} 316 | 317 | test.afterEach.always(t => { 318 | doFoo(); 319 | }); 320 | 321 | test.before(t => { 322 | doFoo(); 323 | }); 324 | 325 | test('foo', t => { 326 | t.true(true); 327 | }); 328 | `, 329 | output: outdent` 330 | ${header} 331 | 332 | test.before(t => { 333 | doFoo(); 334 | }); 335 | 336 | test.afterEach.always(t => { 337 | doFoo(); 338 | }); 339 | 340 | test('foo', t => { 341 | t.true(true); 342 | }); 343 | `, 344 | errors, 345 | }, 346 | { 347 | code: outdent` 348 | ${header} 349 | 350 | test('foo', t => { 351 | t.true(true); 352 | }); 353 | 354 | test.before(t => { 355 | doFoo(); 356 | }); 357 | `, 358 | output: outdent` 359 | ${header} 360 | 361 | test.before(t => { 362 | doFoo(); 363 | }); 364 | 365 | test('foo', t => { 366 | t.true(true); 367 | }); 368 | `, 369 | errors, 370 | }, 371 | 372 | { 373 | code: outdent` 374 | ${header} 375 | 376 | test.after.always(t => { 377 | doFoo(); 378 | }); 379 | 380 | test.after(t => { 381 | doFoo(); 382 | }); 383 | 384 | test('foo', t => { 385 | t.true(true); 386 | }); 387 | `, 388 | output: outdent` 389 | ${header} 390 | 391 | test.after(t => { 392 | doFoo(); 393 | }); 394 | 395 | test.after.always(t => { 396 | doFoo(); 397 | }); 398 | 399 | test('foo', t => { 400 | t.true(true); 401 | }); 402 | `, 403 | errors, 404 | }, 405 | { 406 | code: outdent` 407 | ${header} 408 | 409 | test.beforeEach(t => { 410 | doFoo(); 411 | }); 412 | 413 | test.after(t => { 414 | doFoo(); 415 | }); 416 | 417 | test('foo', t => { 418 | t.true(true); 419 | }); 420 | `, 421 | output: outdent` 422 | ${header} 423 | 424 | test.after(t => { 425 | doFoo(); 426 | }); 427 | 428 | test.beforeEach(t => { 429 | doFoo(); 430 | }); 431 | 432 | test('foo', t => { 433 | t.true(true); 434 | }); 435 | `, 436 | errors, 437 | }, 438 | { 439 | code: outdent` 440 | ${header} 441 | 442 | test.afterEach(t => { 443 | doFoo(); 444 | }); 445 | 446 | test.after(t => { 447 | doFoo(); 448 | }); 449 | 450 | test('foo', t => { 451 | t.true(true); 452 | }); 453 | `, 454 | output: outdent` 455 | ${header} 456 | 457 | test.after(t => { 458 | doFoo(); 459 | }); 460 | 461 | test.afterEach(t => { 462 | doFoo(); 463 | }); 464 | 465 | test('foo', t => { 466 | t.true(true); 467 | }); 468 | `, 469 | errors, 470 | }, 471 | { 472 | code: outdent` 473 | ${header} 474 | 475 | test.afterEach.always(t => { 476 | doFoo(); 477 | }); 478 | 479 | test.after(t => { 480 | doFoo(); 481 | }); 482 | 483 | test('foo', t => { 484 | t.true(true); 485 | }); 486 | `, 487 | output: outdent` 488 | ${header} 489 | 490 | test.after(t => { 491 | doFoo(); 492 | }); 493 | 494 | test.afterEach.always(t => { 495 | doFoo(); 496 | }); 497 | 498 | test('foo', t => { 499 | t.true(true); 500 | }); 501 | `, 502 | errors, 503 | }, 504 | { 505 | code: outdent` 506 | ${header} 507 | 508 | test('foo', t => { 509 | t.true(true); 510 | }); 511 | 512 | test.after(t => { 513 | doFoo(); 514 | }); 515 | `, 516 | output: outdent` 517 | ${header} 518 | 519 | test.after(t => { 520 | doFoo(); 521 | }); 522 | 523 | test('foo', t => { 524 | t.true(true); 525 | }); 526 | `, 527 | errors, 528 | }, 529 | 530 | { 531 | code: outdent` 532 | ${header} 533 | 534 | test.afterEach(t => { 535 | doFoo(); 536 | }); 537 | 538 | test.beforeEach(t => { 539 | doFoo(); 540 | }); 541 | 542 | test('foo', t => { 543 | t.true(true); 544 | }); 545 | `, 546 | output: outdent` 547 | ${header} 548 | 549 | test.beforeEach(t => { 550 | doFoo(); 551 | }); 552 | 553 | test.afterEach(t => { 554 | doFoo(); 555 | }); 556 | 557 | test('foo', t => { 558 | t.true(true); 559 | }); 560 | `, 561 | errors, 562 | }, 563 | { 564 | code: outdent` 565 | ${header} 566 | 567 | test('foo', t => { 568 | t.true(true); 569 | }); 570 | 571 | test.beforeEach(t => { 572 | doFoo(); 573 | }); 574 | `, 575 | output: outdent` 576 | ${header} 577 | 578 | test.beforeEach(t => { 579 | doFoo(); 580 | }); 581 | 582 | test('foo', t => { 583 | t.true(true); 584 | }); 585 | `, 586 | errors, 587 | }, 588 | 589 | { 590 | code: outdent` 591 | ${header} 592 | 593 | test('foo', t => { 594 | t.true(true); 595 | }); 596 | 597 | test.afterEach(t => { 598 | doFoo(); 599 | }); 600 | `, 601 | output: outdent` 602 | ${header} 603 | 604 | test.afterEach(t => { 605 | doFoo(); 606 | }); 607 | 608 | test('foo', t => { 609 | t.true(true); 610 | }); 611 | `, 612 | errors, 613 | }, 614 | 615 | { 616 | code: outdent` 617 | ${header} 618 | 619 | test('foo', t => { 620 | t.true(true); 621 | }); 622 | 623 | test.afterEach.always(t => { 624 | doFoo(); 625 | }); 626 | `, 627 | output: outdent` 628 | ${header} 629 | 630 | test.afterEach.always(t => { 631 | doFoo(); 632 | }); 633 | 634 | test('foo', t => { 635 | t.true(true); 636 | }); 637 | `, 638 | errors, 639 | }, 640 | 641 | { 642 | code: outdent` 643 | ${header} 644 | 645 | test.after(t => { 646 | doFoo(); 647 | }); 648 | 649 | console.log('foo'); 650 | 651 | test.before(t => { 652 | doFoo(); 653 | }); 654 | `, 655 | output: outdent` 656 | ${header} 657 | 658 | test.after(t => { 659 | doFoo(); 660 | }); 661 | 662 | console.log('foo'); 663 | 664 | test.before(t => { 665 | doFoo(); 666 | }); 667 | `, 668 | errors, 669 | }, 670 | { 671 | code: outdent` 672 | ${header} 673 | 674 | test.after(t => { 675 | doFoo(); 676 | }); 677 | 678 | // comments 679 | test.before(t => { 680 | doFoo(); 681 | }); 682 | `, 683 | output: outdent` 684 | ${header} 685 | 686 | // comments 687 | test.before(t => { 688 | doFoo(); 689 | }); 690 | 691 | test.after(t => { 692 | doFoo(); 693 | }); 694 | `, 695 | errors, 696 | }, 697 | { 698 | code: outdent` 699 | ${header} 700 | 701 | test.after(t => { 702 | doFoo(); 703 | }); 704 | 705 | /* comments */ 706 | test.before(t => { 707 | doFoo(); 708 | }); 709 | `, 710 | output: outdent` 711 | ${header} 712 | 713 | /* comments */ 714 | test.before(t => { 715 | doFoo(); 716 | }); 717 | 718 | test.after(t => { 719 | doFoo(); 720 | }); 721 | `, 722 | errors, 723 | }, 724 | ], 725 | }); 726 | -------------------------------------------------------------------------------- /test/integration/eslint-config-ava-tester/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /test/integration/eslint-config-ava-tester/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@babel/eslint-parser', 6 | parserOptions: { 7 | requireConfigFile: false, 8 | babelOptions: { 9 | babelrc: false, 10 | configFile: false, 11 | }, 12 | }, 13 | plugins: [ 14 | 'ava', 15 | ], 16 | extends: 'plugin:ava/recommended', 17 | overrides: [ 18 | { 19 | files: ['*.ts'], 20 | parser: '@typescript-eslint/parser', 21 | }, 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /test/integration/eslint-config-ava-tester/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-ava-tester", 3 | "dependencies": { 4 | "@babel/eslint-parser": "^7.16.0", 5 | "@typescript-eslint/parser": "^5.3.0", 6 | "eslint": "^8.1.0", 7 | "eslint-plugin-ava": "file:../../..", 8 | "typescript": "^4.4.4" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/integration/test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const path = require('node:path'); 5 | const process = require('node:process'); 6 | const Listr = require('listr'); 7 | const tempy = require('tempy'); 8 | const execa = require('execa'); 9 | const del = require('del'); 10 | const chalk = require('chalk'); 11 | 12 | const packages = new Map([ 13 | ['chalk', 'https://github.com/chalk/chalk'], 14 | ['wrap-ansi', 'https://github.com/chalk/wrap-ansi'], 15 | ['np', 'https://github.com/sindresorhus/np'], 16 | ['ora', 'https://github.com/sindresorhus/ora'], 17 | ['p-map', 'https://github.com/sindresorhus/p-map'], 18 | ['os-locale', 'https://github.com/sindresorhus/os-locale'], 19 | ['execa', 'https://github.com/sindresorhus/execa'], 20 | ['pify', 'https://github.com/sindresorhus/pify'], 21 | ['boxen', 'https://github.com/sindresorhus/boxen'], 22 | ['make-dir', 'https://github.com/sindresorhus/make-dir'], 23 | ['listr', 'https://github.com/SamVerschueren/listr'], 24 | ['listr-update-renderer', 'https://github.com/SamVerschueren/listr-update-renderer'], 25 | ['bragg', 'https://github.com/SamVerschueren/bragg'], 26 | ['bragg-router', 'https://github.com/SamVerschueren/bragg-router'], 27 | ['dev-time', 'https://github.com/SamVerschueren/dev-time'], 28 | ['decode-uri-component', 'https://github.com/SamVerschueren/decode-uri-component'], 29 | ['to-ico', 'https://github.com/kevva/to-ico'], 30 | ['download', 'https://github.com/kevva/download'], 31 | ['brightness', 'https://github.com/kevva/brightness'], 32 | ['decompress', 'https://github.com/kevva/decompress'], 33 | ['npm-conf', 'https://github.com/kevva/npm-conf'], 34 | ['imagemin', 'https://github.com/imagemin/imagemin'], 35 | ['color-convert', 'https://github.com/qix-/color-convert'], 36 | ['eslint-plugin-unicorn', 'https://github.com/sindresorhus/eslint-plugin-unicorn'], 37 | ['ky', 'https://github.com/sindresorhus/ky'], 38 | ['query-string', 'https://github.com/sindresorhus/query-string'], 39 | ['meow', 'https://github.com/sindresorhus/meow'], 40 | ['emittery', 'https://github.com/sindresorhus/emittery'], 41 | ['p-queue', 'https://github.com/sindresorhus/p-queue'], 42 | ['pretty-bytes', 'https://github.com/sindresorhus/pretty-bytes'], 43 | ['normalize-url', 'https://github.com/sindresorhus/normalize-url'], 44 | ['pageres', 'https://github.com/sindresorhus/pageres'], 45 | ['got', 'https://github.com/sindresorhus/got'], 46 | ]); 47 | 48 | const cwd = path.join(__dirname, 'eslint-config-ava-tester'); 49 | 50 | const enrichErrors = (packageName, cliArguments, f) => async (...arguments_) => { 51 | try { 52 | return await f(...arguments_); 53 | } catch (error) { 54 | error.packageName = packageName; 55 | error.cliArgs = cliArguments; 56 | throw error; 57 | } 58 | }; 59 | 60 | const makeEslintTask = (packageName, destination, extraArguments = []) => { 61 | const arguments_ = [ 62 | 'eslint', 63 | '--config', 64 | path.join(cwd, 'index.js'), 65 | '--no-eslintrc', 66 | '--ext', 67 | '.js,.ts', 68 | destination, 69 | '--format', 70 | 'json', 71 | ...extraArguments, 72 | ]; 73 | 74 | return enrichErrors(packageName, arguments_, async () => { 75 | let stdout; 76 | let processError; 77 | try { 78 | ({stdout} = await execa('npx', arguments_, {cwd, localDir: __dirname})); 79 | } catch (error) { 80 | ({stdout} = error); 81 | processError = error; 82 | 83 | if (!stdout) { 84 | throw error; 85 | } 86 | } 87 | 88 | let files; 89 | try { 90 | files = JSON.parse(stdout); 91 | } catch (error) { 92 | console.error('Error while parsing eslint output:', error); 93 | 94 | if (processError) { 95 | throw processError; 96 | } 97 | 98 | throw error; 99 | } 100 | 101 | for (const file of files) { 102 | for (const message of file.messages) { 103 | if (message.fatal) { 104 | const error = new Error(message.message); 105 | error.eslintFile = file; 106 | error.eslintMessage = message; 107 | throw error; 108 | } 109 | } 110 | } 111 | }); 112 | }; 113 | 114 | const execute = name => { 115 | const destination = tempy.directory(); 116 | 117 | return new Listr([ 118 | { 119 | title: 'Cloning', 120 | task: () => execa('git', ['clone', packages.get(name), '--single-branch', destination]), 121 | }, 122 | { 123 | title: 'Running eslint', 124 | task: makeEslintTask(name, destination), 125 | }, 126 | { 127 | title: 'Running eslint --fix', 128 | task: makeEslintTask(name, destination, ['--fix-dry-run']), 129 | }, 130 | { 131 | title: 'Clean up', 132 | task: () => del(destination, {force: true}), 133 | }, 134 | ].map(({title, task}) => ({ 135 | title: `${name} / ${title}`, 136 | task, 137 | })), { 138 | exitOnError: false, 139 | }); 140 | }; 141 | 142 | const list = new Listr([ 143 | { 144 | title: 'Setup', 145 | task: () => execa('npm', ['install', '../../..', 'eslint', 'babel-eslint', 'typescript', '@typescript-eslint/parser'], {cwd}), 146 | }, 147 | { 148 | title: 'Integration tests', 149 | task() { 150 | const tests = new Listr({concurrent: true}); 151 | 152 | for (const [name] of packages) { 153 | tests.add([ 154 | { 155 | title: name, 156 | task: () => execute(name), 157 | }, 158 | ]); 159 | } 160 | 161 | return tests; 162 | }, 163 | }, 164 | ], { 165 | renderer: process.env.INTEGRATION ? 'verbose' : 'default', 166 | }); 167 | 168 | list.run() 169 | .catch(error => { 170 | if (error.errors) { 171 | for (const error2 of error.errors) { 172 | console.error('\n', chalk.red.bold.underline(error2.packageName), chalk.gray('(' + error2.cliArgs.join(' ') + ')')); 173 | console.error(error2.message); 174 | 175 | if (error2.stderr) { 176 | console.error(chalk.gray(error2.stderr)); 177 | } 178 | 179 | if (error2.eslintMessage) { 180 | console.error(chalk.gray(error2.eslintFile.filePath), chalk.gray(JSON.stringify(error2.eslintMessage, null, 2))); 181 | } 182 | } 183 | } else { 184 | console.error(error); 185 | } 186 | 187 | process.exit(1); 188 | }); 189 | -------------------------------------------------------------------------------- /test/max-asserts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/max-asserts'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const errors = [{}]; 14 | const header = 'const test = require(\'ava\');\n'; 15 | 16 | function nbAssertions(n) { 17 | return Array.from({length: n}).map(() => 't.is(1, 1);').join('\n'); 18 | } 19 | 20 | ruleTester.run('max-asserts', rule, { 21 | valid: [ 22 | `${header} test(t => { ${nbAssertions(3)} });`, 23 | `${header} 24 | test(t => { ${nbAssertions(3)} }); 25 | test(t => { ${nbAssertions(3)} }); 26 | `, 27 | `${header} test(t => { t.plan(5); ${nbAssertions(5)} });`, 28 | `${header} test(t => { t.is.skip(1, 1); ${nbAssertions(4)} });`, 29 | { 30 | code: `${header} test(t => { ${nbAssertions(3)} });`, 31 | options: [3], 32 | }, 33 | { 34 | code: `${header} test(t => { notT.is(1, 1); notT.is(1, 1); notT.is(1, 1); });`, 35 | options: [2], 36 | }, 37 | `${header} test(t => { t.context.bar(); ${nbAssertions(5)} });`, 38 | `${header} test(t => { ${'t.context.is(1, 1); '.repeat(6)}});`, 39 | `${header} test(t => { ${'foo.t.is(1, 1); '.repeat(6)}});`, 40 | // Shouldn't be triggered since it's not a test file 41 | `test(t => { ${nbAssertions(10)} });`, 42 | ], 43 | invalid: [ 44 | { 45 | code: `${header} test(t => { ${nbAssertions(6)} });`, 46 | errors, 47 | }, 48 | { 49 | code: `${header} 50 | test(t => { ${nbAssertions(3)} }); 51 | test(t => { ${nbAssertions(6)} }); 52 | `, 53 | errors, 54 | }, 55 | { 56 | code: `${header} test(t => { t.plan(5); ${nbAssertions(6)} });`, 57 | errors, 58 | }, 59 | { 60 | code: `${header} test(t => { t.skip.is(1, 1); ${nbAssertions(5)} });`, 61 | errors, 62 | }, 63 | { 64 | code: `${header} test(t => { ${nbAssertions(4)} });`, 65 | options: [3], 66 | errors, 67 | }, 68 | { 69 | code: `${header} test(t => { ${nbAssertions(10)} });`, 70 | errors, 71 | }, 72 | { 73 | code: `${header} test(t => { ${nbAssertions(10)} }); test(t => { ${nbAssertions(10)} });`, 74 | errors: [...errors, ...errors], // Should have two errors, one per test 75 | }, 76 | ], 77 | }); 78 | -------------------------------------------------------------------------------- /test/no-async-fn-without-await.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-async-fn-without-await'); 6 | 7 | const message = 'Function was declared as `async` but doesn\'t use `await`.'; 8 | const header = 'const test = require(\'ava\');\n'; 9 | 10 | const ruleTesterOptions = [ 11 | { 12 | parserOptions: { 13 | ecmaVersion: 'latest', 14 | }, 15 | }, 16 | // Disabled for now because of `eslint-ava-rule-tester` problem 17 | // { 18 | // parser: require.resolve('babel-eslint'), 19 | // env: { 20 | // es6: true 21 | // } 22 | // } 23 | ]; 24 | 25 | for (const options of ruleTesterOptions) { 26 | const ruleTester = avaRuleTester(test, options); 27 | 28 | ruleTester.run(`no-async-fn-without-await - parser:${options.parser ?? 'default'}`, rule, { 29 | valid: [ 30 | `${header}test(fn);`, 31 | `${header}test(t => {});`, 32 | `${header}test(function(t) {});`, 33 | `${header}test(async t => { await foo(); });`, 34 | `${header}test(async t => { t.is(await foo(), 1); });`, 35 | `${header}test(async function(t) { await foo(); });`, 36 | `${header}test(async t => { if (bar) { await foo(); } });`, 37 | `${header}test(async t => { if (bar) {} else { await foo(); } });`, 38 | `${header}test(async t => { for await (const foo of bar) {} });`, 39 | `${header}test.after(async () => { await foo(); });`, 40 | `${header}test('title', fn);`, 41 | `${header}test('title', function(t) {});`, 42 | `${header}test('title', async t => { await foo(); });`, 43 | // Shouldn't be triggered since it's not a test file 44 | 'test(async t => {});', 45 | ], 46 | invalid: [ 47 | { 48 | code: `${header}test(async t => {});`, 49 | errors: [{ 50 | message, 51 | type: 'ArrowFunctionExpression', 52 | line: 2, 53 | column: 6, 54 | }], 55 | }, 56 | { 57 | code: `${header}test(async function(t) {});`, 58 | errors: [{ 59 | message, 60 | type: 'FunctionExpression', 61 | line: 2, 62 | column: 6, 63 | }], 64 | }, 65 | { 66 | code: `${header}test(async t => {}); test(async t => {});`, 67 | errors: [{ 68 | message, 69 | type: 'ArrowFunctionExpression', 70 | line: 2, 71 | column: 6, 72 | }, { 73 | message, 74 | type: 'ArrowFunctionExpression', 75 | line: 2, 76 | column: 27, 77 | }], 78 | }, 79 | { 80 | code: `${header}test(async t => {}); test(async t => { await foo(); });`, 81 | errors: [{ 82 | message, 83 | type: 'ArrowFunctionExpression', 84 | line: 2, 85 | column: 6, 86 | }], 87 | }, 88 | { 89 | code: `${header}test(async t => { await foo(); }); test(async t => {});`, 90 | errors: [{ 91 | message, 92 | type: 'ArrowFunctionExpression', 93 | line: 2, 94 | column: 41, 95 | }], 96 | }, 97 | { 98 | code: `${header}test('title', async t => {});`, 99 | errors: [{ 100 | message, 101 | type: 'ArrowFunctionExpression', 102 | line: 2, 103 | column: 15, 104 | }], 105 | }, 106 | ], 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /test/no-duplicate-modifiers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-duplicate-modifiers'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const header = 'const test = require(\'ava\');\n'; 14 | 15 | const modifiers = [ 16 | 'after', 17 | 'afterEach', 18 | 'always', 19 | 'before', 20 | 'beforeEach', 21 | 'failing', 22 | 'only', 23 | 'serial', 24 | 'skip', 25 | 'todo', 26 | ]; 27 | 28 | const valid = modifiers.map(modifier => `${header}test.${modifier}(t => {});`); 29 | const invalid = modifiers.map(modifier => ({ 30 | code: `${header}test.${modifier}.${modifier}(t => {});`, 31 | errors: [ 32 | { 33 | message: `Duplicate test modifier \`.${modifier}\`.`, 34 | type: 'Identifier', 35 | line: 2, 36 | column: 7 + modifier.length, 37 | }, 38 | ], 39 | })); 40 | 41 | ruleTester.run('no-duplicate-modifiers', rule, { 42 | valid: [...valid, 43 | `${header}test(t => {});`, 44 | `${header}test.after.always(t => {});`, 45 | `${header}test.afterEach.always(t => {});`, 46 | // Shouldn't be triggered since it's not a test file 47 | 'test.serial.serial(t => {});'], 48 | invalid, 49 | }); 50 | -------------------------------------------------------------------------------- /test/no-identical-title.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-identical-title'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const message = 'Test title is used multiple times in the same file.'; 14 | 15 | const header = 'const test = require(\'ava\');\n'; 16 | 17 | ruleTester.run('no-identical-title', rule, { 18 | valid: [ 19 | header + 'test("my test name", t => {});', 20 | header + 'test("a", t => {}); test("b", t => {});', 21 | header + 'test.todo("a"); test.todo("b");', 22 | header + 'test("a", t => {}); notTest("a", t => {});', 23 | // eslint-disable-next-line no-template-curly-in-string 24 | header + 'test(`foo ${name}`, t => {}); test(`foo ${name}`, t => {});', 25 | header + 'const name = "foo"; test(name + " 1", t => {}); test(name + " 1", t => {});', 26 | header + 'test("a", t => {}); notTest("a", t => {});', 27 | header + 'notTest("a", t => {}); notTest("a", t => {});', 28 | header + 'test.before(t => {}); test.before(t => {});', 29 | header + 'test.after(t => {}); test.after(t => {});', 30 | header + 'test.beforeEach(t => {}); test.beforeEach(t => {});', 31 | header + 'test.afterEach(t => {}); test.afterEach(t => {});', 32 | // Macros 33 | ` ${header} 34 | const macro = (t, value) => { t.true(value); }; 35 | 36 | test(macro, true); 37 | test('should work', macro, true); 38 | test('should fail', macro, false); 39 | `, 40 | ` ${header} 41 | const macro = (t, value) => { t.true(value); }; 42 | 43 | test('same title', macro, true); 44 | test('same title', macro, false); 45 | `, 46 | // Shouldn't be triggered since it's not a test file 47 | 'test(t => {}); test(t => {});', 48 | 'test("a", t => {}); test("a", t => {});', 49 | ], 50 | invalid: [ 51 | { 52 | code: header + 'test("a", t => {}); test("a", t => {});', 53 | errors: [{ 54 | message, 55 | type: 'Literal', 56 | line: 2, 57 | column: 26, 58 | }], 59 | }, 60 | { 61 | code: header + 'test(`a`, t => {}); test(`a`, t => {});', 62 | errors: [{ 63 | message, 64 | type: 'TemplateLiteral', 65 | line: 2, 66 | column: 26, 67 | }], 68 | }, 69 | { 70 | code: header + 'test("foo" + 1, t => {}); test("foo" + 1, t => {});', 71 | errors: [{ 72 | message, 73 | type: 'BinaryExpression', 74 | line: 2, 75 | column: 32, 76 | }], 77 | }, 78 | { 79 | // eslint-disable-next-line no-template-curly-in-string 80 | code: header + 'test(`${"foo" + 1}`, t => {}); test(`${"foo" + 1}`, t => {});', 81 | errors: [{ 82 | message, 83 | type: 'TemplateLiteral', 84 | line: 2, 85 | column: 37, 86 | }], 87 | }, 88 | { 89 | code: header + 'test.todo("a"); test.todo("a");', 90 | errors: [{ 91 | message, 92 | type: 'Literal', 93 | line: 2, 94 | column: 27, 95 | }], 96 | }, 97 | ], 98 | }); 99 | -------------------------------------------------------------------------------- /test/no-ignored-test-files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('node:path'); 4 | const test = require('ava'); 5 | const avaRuleTester = require('eslint-ava-rule-tester'); 6 | const util = require('../util'); 7 | const rule = require('../rules/no-ignored-test-files'); 8 | 9 | const ruleTester = avaRuleTester(test, { 10 | env: { 11 | es6: true, 12 | }, 13 | }); 14 | 15 | const header = 'const test = require(\'ava\');\n'; 16 | const rootDirectory = path.dirname(__dirname); 17 | 18 | const toPath = subPath => path.join(rootDirectory, subPath); 19 | const code = hasHeader => (hasHeader ? header : '') + 'test(t => { t.pass(); });'; 20 | 21 | util.loadAvaHelper = () => ({ 22 | classifyFile(file) { 23 | switch (file) { 24 | case toPath('lib/foo.test.js'): { 25 | return {isHelper: false, isTest: true}; 26 | } 27 | 28 | case toPath('lib/foo/fixtures/bar.test.js'): { 29 | return {isHelper: false, isTest: false}; 30 | } 31 | 32 | case toPath('lib/foo/helpers/bar.test.js'): { 33 | return {isHelper: true, isTest: false}; 34 | } 35 | 36 | default: { 37 | return {isHelper: false, isTest: false}; 38 | } 39 | } 40 | }, 41 | }); 42 | 43 | ruleTester.run('no-ignored-test-files', rule, { 44 | valid: [ 45 | { 46 | code: code(true), 47 | filename: toPath('lib/foo.test.js'), 48 | }, 49 | ], 50 | invalid: [ 51 | { 52 | code: code(true), 53 | filename: toPath('lib/foo/fixtures/bar.test.js'), 54 | errors: [{message: 'AVA ignores this file.'}], 55 | }, 56 | { 57 | code: code(true), 58 | filename: toPath('lib/foo/helpers/bar.test.js'), 59 | errors: [{message: 'AVA treats this as a helper file.'}], 60 | }, 61 | { 62 | code: code(true), 63 | filename: toPath('test.js'), 64 | errors: [{message: 'AVA ignores this file.'}], 65 | }, 66 | { 67 | code: code(true), 68 | filename: toPath('bar/foo.test.js'), 69 | errors: [{message: 'AVA ignores this file.'}], 70 | }, 71 | ], 72 | }); 73 | -------------------------------------------------------------------------------- /test/no-import-test-files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('node:path'); 4 | const test = require('ava'); 5 | const avaRuleTester = require('eslint-ava-rule-tester'); 6 | const util = require('../util'); 7 | const rule = require('../rules/no-import-test-files'); 8 | 9 | const ruleTester = avaRuleTester(test, { 10 | env: { 11 | es6: true, 12 | }, 13 | parserOptions: { 14 | sourceType: 'module', 15 | }, 16 | }); 17 | 18 | const rootDirectory = path.dirname(__dirname); 19 | const toPath = subPath => path.join(rootDirectory, subPath); 20 | 21 | util.loadAvaHelper = () => ({ 22 | classifyImport(importPath) { 23 | switch (importPath) { 24 | case toPath('lib/foo.test.js'): { 25 | return {isHelper: false, isTest: true}; 26 | } 27 | 28 | case toPath('../foo.test.js'): { 29 | return {isHelper: false, isTest: true}; 30 | } 31 | 32 | case toPath('@foo/bar'): { // Regression test for https://github.com/avajs/eslint-plugin-ava/issues/253 33 | return {isHelper: false, isTest: true}; 34 | } 35 | 36 | default: { 37 | return {isHelper: false, isTest: false}; 38 | } 39 | } 40 | }, 41 | }); 42 | 43 | const errors = [ 44 | { 45 | message: 'Test files should not be imported.', 46 | }, 47 | ]; 48 | 49 | ruleTester.run('no-import-test-files', rule, { 50 | valid: [ 51 | 'import test from \'ava\';', 52 | 'import foo from \'@foo/bar\';', 53 | 'import foo from \'/foo/bar\';', // Classfied as not a test. 54 | 'const test = require(\'ava\');', 55 | 'console.log()', 56 | 'const value = require(somePath);', 57 | 'const highlight = require(\'highlight.js\')', 58 | { 59 | code: 'const highlight = require(\'highlight.js\')', 60 | filename: toPath('test/index.js'), 61 | }, 62 | 'const value = require(true);', 63 | 'const value = require();', 64 | ], 65 | invalid: [ 66 | { 67 | code: 'const test = require(\'./foo.test.js\');', 68 | filename: toPath('lib/foo.js'), 69 | errors, 70 | }, 71 | { 72 | code: 'const test = require(\'../foo.test.js\');', 73 | filename: toPath('foo.js'), 74 | errors, 75 | }, 76 | ], 77 | }); 78 | -------------------------------------------------------------------------------- /test/no-incorrect-deep-equal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-incorrect-deep-equal'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const error = { 14 | messageId: 'no-deep-equal-with-primative', 15 | }; 16 | 17 | const header = 'const test = require(\'ava\');\n'; 18 | 19 | ruleTester.run('no-incorrect-deep-equal', rule, { 20 | valid: [ 21 | ` 22 | ${header} 23 | test('x', t => { 24 | t.deepEqual(expression, otherExpression); 25 | }); 26 | `, 27 | ` 28 | ${header} 29 | test('x', t => { 30 | t.deepEqual(expression, {}); 31 | }); 32 | `, 33 | ` 34 | ${header} 35 | test('x', t => { 36 | t.deepEqual(expression, []); 37 | }); 38 | `, 39 | ` 40 | ${header} 41 | test('x', t => { 42 | t.notDeepEqual(expression, []); 43 | }); 44 | `, 45 | ` 46 | ${header} 47 | test('x', t => { 48 | t.deepEqual(expression, /regex/); 49 | }); 50 | `, 51 | ` 52 | ${header} 53 | test('x', t => { 54 | t.deepEqual(/regex/, expression); 55 | }); 56 | `, 57 | ], 58 | invalid: [ 59 | { 60 | code: ` 61 | ${header} 62 | test('x', t => { 63 | t.deepEqual(expression, 'foo'); 64 | }); 65 | `, 66 | output: ` 67 | ${header} 68 | test('x', t => { 69 | t.is(expression, 'foo'); 70 | }); 71 | `, 72 | errors: [error], 73 | }, 74 | { 75 | code: ` 76 | ${header} 77 | test('x', t => { 78 | t.deepEqual('foo', expression); 79 | }); 80 | `, 81 | output: ` 82 | ${header} 83 | test('x', t => { 84 | t.is('foo', expression); 85 | }); 86 | `, 87 | errors: [error], 88 | }, 89 | { 90 | code: ` 91 | ${header} 92 | test('x', t => { 93 | t.notDeepEqual(expression, 'foo'); 94 | }); 95 | `, 96 | output: ` 97 | ${header} 98 | test('x', t => { 99 | t.not(expression, 'foo'); 100 | }); 101 | `, 102 | errors: [error], 103 | }, 104 | { 105 | code: ` 106 | ${header} 107 | test('x', t => { 108 | t.notDeepEqual('foo', expression); 109 | }); 110 | `, 111 | output: ` 112 | ${header} 113 | test('x', t => { 114 | t.not('foo', expression); 115 | }); 116 | `, 117 | errors: [error], 118 | }, 119 | { 120 | code: ` 121 | ${header} 122 | test('x', t => { 123 | t.deepEqual(expression, 1); 124 | }); 125 | `, 126 | output: ` 127 | ${header} 128 | test('x', t => { 129 | t.is(expression, 1); 130 | }); 131 | `, 132 | errors: [error], 133 | }, 134 | { 135 | code: ` 136 | ${header} 137 | test('x', t => { 138 | t.deepEqual(expression, \`foo\${bar}\`); 139 | }); 140 | `, 141 | output: ` 142 | ${header} 143 | test('x', t => { 144 | t.is(expression, \`foo\${bar}\`); 145 | }); 146 | `, 147 | errors: [error], 148 | }, 149 | { 150 | code: ` 151 | ${header} 152 | test('x', t => { 153 | t.deepEqual(\`foo\${bar}\`, expression); 154 | }); 155 | `, 156 | output: ` 157 | ${header} 158 | test('x', t => { 159 | t.is(\`foo\${bar}\`, expression); 160 | }); 161 | `, 162 | errors: [error], 163 | }, 164 | { 165 | code: ` 166 | ${header} 167 | test('x', t => { 168 | t.notDeepEqual(expression, \`foo\${bar}\`); 169 | }); 170 | `, 171 | output: ` 172 | ${header} 173 | test('x', t => { 174 | t.not(expression, \`foo\${bar}\`); 175 | }); 176 | `, 177 | errors: [error], 178 | }, 179 | { 180 | code: ` 181 | ${header} 182 | test('x', t => { 183 | t.notDeepEqual(\`foo\${bar}\`, expression); 184 | }); 185 | `, 186 | output: ` 187 | ${header} 188 | test('x', t => { 189 | t.not(\`foo\${bar}\`, expression); 190 | }); 191 | `, 192 | errors: [error], 193 | }, 194 | { 195 | code: ` 196 | ${header} 197 | test('x', t => { 198 | t.deepEqual(expression, null); 199 | }); 200 | `, 201 | output: ` 202 | ${header} 203 | test('x', t => { 204 | t.is(expression, null); 205 | }); 206 | `, 207 | errors: [error], 208 | }, 209 | { 210 | code: ` 211 | ${header} 212 | test('x', t => { 213 | t.deepEqual(null, expression); 214 | }); 215 | `, 216 | output: ` 217 | ${header} 218 | test('x', t => { 219 | t.is(null, expression); 220 | }); 221 | `, 222 | errors: [error], 223 | }, 224 | { 225 | code: ` 226 | ${header} 227 | test('x', t => { 228 | t.notDeepEqual(expression, null); 229 | }); 230 | `, 231 | output: ` 232 | ${header} 233 | test('x', t => { 234 | t.not(expression, null); 235 | }); 236 | `, 237 | errors: [error], 238 | }, 239 | { 240 | code: ` 241 | ${header} 242 | test('x', t => { 243 | t.notDeepEqual(null, expression); 244 | }); 245 | `, 246 | output: ` 247 | ${header} 248 | test('x', t => { 249 | t.not(null, expression); 250 | }); 251 | `, 252 | errors: [error], 253 | }, 254 | { 255 | code: ` 256 | ${header} 257 | test('x', t => { 258 | t.deepEqual(expression, undefined); 259 | }); 260 | `, 261 | output: ` 262 | ${header} 263 | test('x', t => { 264 | t.is(expression, undefined); 265 | }); 266 | `, 267 | errors: [error], 268 | }, 269 | { 270 | code: ` 271 | ${header} 272 | test('x', t => { 273 | t.deepEqual(undefined, expression); 274 | }); 275 | `, 276 | output: ` 277 | ${header} 278 | test('x', t => { 279 | t.is(undefined, expression); 280 | }); 281 | `, 282 | errors: [error], 283 | }, 284 | { 285 | code: ` 286 | ${header} 287 | test('x', t => { 288 | t.notDeepEqual(expression, undefined); 289 | }); 290 | `, 291 | output: ` 292 | ${header} 293 | test('x', t => { 294 | t.not(expression, undefined); 295 | }); 296 | `, 297 | errors: [error], 298 | }, 299 | { 300 | code: ` 301 | ${header} 302 | test('x', t => { 303 | t.notDeepEqual(undefined, expression); 304 | }); 305 | `, 306 | output: ` 307 | ${header} 308 | test('x', t => { 309 | t.not(undefined, expression); 310 | }); 311 | `, 312 | errors: [error], 313 | }, 314 | ], 315 | }); 316 | -------------------------------------------------------------------------------- /test/no-inline-assertions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-inline-assertions'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const errors = [{}]; 14 | const header = 'const test = require(\'ava\');\n'; 15 | 16 | ruleTester.run('no-todo-test', rule, { 17 | valid: [ 18 | // Shouldn't be triggered as the test implementation is not an inline arrow function 19 | header + 'test("my test name", t => {\n t.true(fn()); \n});', 20 | header + 'test("my test name", function (t) { foo(); });', 21 | // Shouldn't be triggered since test body is empty 22 | header + 'test("my test name", () => {});', 23 | header + 'test("my test name", t => {});', 24 | // Shouldn't be triggered since test body is ill-defined 25 | header + 'test("my test name", t => "foo");', 26 | // Shouldn't be triggered since it's not a test file 27 | 'test.todo("my test name");', 28 | // Shouldn't be triggered since the signature is incorrect 29 | header + 'test.todo("my test name", "bar");', 30 | header + 'test.todo("my test name", undefined, t => {})', 31 | ], 32 | invalid: [ 33 | { 34 | code: header + 'test("my test name", t => t.skip());', 35 | errors, 36 | output: header + 'test("my test name", t => {t.skip()});', 37 | }, 38 | { 39 | code: header + 'test("my test name", t => t.true(fn()));', 40 | errors, 41 | output: header + 'test("my test name", t => {t.true(fn())});', 42 | }, 43 | { 44 | code: header + 'test("my test name", t => \n t.true(fn()));', 45 | errors, 46 | output: header + 'test("my test name", t => \n {t.true(fn())});', 47 | }, 48 | ], 49 | }); 50 | -------------------------------------------------------------------------------- /test/no-nested-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-nested-tests'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const header = 'const test = require(\'ava\');\n'; 14 | const error = { 15 | message: 'Tests should not be nested.', 16 | }; 17 | 18 | ruleTester.run('no-nested-tests', rule, { 19 | valid: [ 20 | header + 'test(t => {});', 21 | header + 'test("title", t => {});', 22 | header + 'test(t => {}); test(t => {});', 23 | header + 'test("title", t => {}); test("title", t => {});', 24 | header + 'test.skip(t => {});', 25 | header + 'test.skip(t => {}); test.skip(t => {});', 26 | header + 'test.only(t => {});', 27 | header + 'test.only(t => {}); test.only(t => {});', 28 | // Shouldn't be triggered since it's not a test file 29 | 'test(t => { test(t => {}); });', 30 | ], 31 | invalid: [ 32 | { 33 | code: header + 'test("2", t => { test(t => {}); });', 34 | errors: [error], 35 | }, 36 | { 37 | code: header + 'test(t => { test(t => {}); test(t => {}); });', 38 | errors: [error, error], 39 | }, 40 | { 41 | code: header + 'test(t => { test(t => { test(t => {}); }); });', 42 | errors: [error, error], 43 | }, 44 | ], 45 | }); 46 | -------------------------------------------------------------------------------- /test/no-only-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-only-test'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | }, 14 | }); 15 | 16 | const message = '`test.only()` should not be used.'; 17 | const header = 'const test = require(\'ava\');\n'; 18 | 19 | ruleTester.run('no-only-test', rule, { 20 | valid: [ 21 | header + 'test("my test name", t => { t.pass(); });', 22 | header + 'test(t => { t.pass(); }); test(t => { t.pass(); });', 23 | header + 'notTest.only();', 24 | // Shouldn't be triggered since it's not a test file 25 | 'test.only(t => {});', 26 | ], 27 | invalid: [ 28 | { 29 | code: header + 'test\n\t.only(t => { t.pass(); });', 30 | errors: [{ 31 | message, 32 | type: 'Identifier', 33 | line: 3, 34 | column: 3, 35 | suggestions: [{ 36 | desc: 'Remove the `.only`', 37 | output: header + 'test\n\t(t => { t.pass(); });', 38 | }], 39 | }], 40 | }, 41 | { 42 | code: header + 'test\n .only(t => { t.pass(); });', 43 | errors: [{ 44 | message, 45 | type: 'Identifier', 46 | line: 3, 47 | column: 4, 48 | suggestions: [{ 49 | desc: 'Remove the `.only`', 50 | output: header + 'test\n (t => { t.pass(); });', 51 | }], 52 | }], 53 | }, 54 | { 55 | code: header + 'test\t.only(t => { t.pass(); });', 56 | errors: [{ 57 | message, 58 | type: 'Identifier', 59 | line: 2, 60 | column: 7, 61 | suggestions: [{ 62 | desc: 'Remove the `.only`', 63 | output: header + 'test\t(t => { t.pass(); });', 64 | }], 65 | }], 66 | }, 67 | { 68 | code: header + 'test .only(t => { t.pass(); });', 69 | errors: [{ 70 | message, 71 | type: 'Identifier', 72 | line: 2, 73 | column: 8, 74 | suggestions: [{ 75 | desc: 'Remove the `.only`', 76 | output: header + 'test (t => { t.pass(); });', 77 | }], 78 | }], 79 | }, 80 | { 81 | code: header + 'test.\n\tonly(t => { t.pass(); });', 82 | errors: [{ 83 | message, 84 | type: 'Identifier', 85 | line: 3, 86 | column: 2, 87 | suggestions: [{ 88 | desc: 'Remove the `.only`', 89 | output: header + 'test\n\t(t => { t.pass(); });', 90 | }], 91 | }], 92 | }, 93 | { 94 | code: header + 'test.\n only(t => { t.pass(); });', 95 | errors: [{ 96 | message, 97 | type: 'Identifier', 98 | line: 3, 99 | column: 3, 100 | suggestions: [{ 101 | desc: 'Remove the `.only`', 102 | output: header + 'test\n (t => { t.pass(); });', 103 | }], 104 | }], 105 | }, 106 | { 107 | code: header + 'test.only(t => { t.pass(); });', 108 | errors: [{ 109 | message, 110 | type: 'Identifier', 111 | line: 2, 112 | column: 6, 113 | suggestions: [{ 114 | desc: 'Remove the `.only`', 115 | output: header + 'test(t => { t.pass(); });', 116 | }], 117 | }], 118 | }, 119 | ], 120 | }); 121 | -------------------------------------------------------------------------------- /test/no-skip-assert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-skip-assert'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const errors = [{}]; 14 | const header = 'const test = require(\'ava\');\n'; 15 | 16 | ruleTester.run('no-skip-assert', rule, { 17 | valid: [ 18 | header + 'test(t => { t.is(1, 1); });', 19 | header + 'test.skip(t => { t.is(1, 1); });', 20 | // Not an actual AVA skip 21 | header + 'test(t => { notT.skip.is(1, 1); });', 22 | header + 'test(t => { t.context.is.skip(1, 1); });', 23 | header + 'test(t => { foo.t.is.skip(1, 1); });', 24 | header + 'test(t => { t.skip(); });', 25 | // Shouldn't be triggered since it's not a test file 26 | 'test(t => { t.is.skip(1, 1); });', 27 | ], 28 | invalid: [ 29 | { 30 | code: header + 'test(t => { t.is.skip(1, 1); });', 31 | errors, 32 | }, 33 | { 34 | code: header + 'test(t => { t.true.skip(1); });', 35 | errors, 36 | }, 37 | { 38 | code: header + 'test.skip(t => { t.is.skip(1, 1); });', 39 | errors, 40 | }, 41 | ], 42 | }); 43 | -------------------------------------------------------------------------------- /test/no-skip-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-skip-test'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const message = 'No tests should be skipped.'; 14 | const header = 'const test = require(\'ava\');\n'; 15 | 16 | ruleTester.run('no-skip-test', rule, { 17 | valid: [ 18 | header + 'test("my test name", t => { t.pass(); });', 19 | header + 'test(t => { t.pass(); }); test(t => { t.pass(); });', 20 | header + 'test(t => { t.skip.is(1, 2); });', 21 | header + 'notTest.skip();', 22 | // Shouldn't be triggered since it's not a test file 23 | 'test.skip(t => {});', 24 | ], 25 | invalid: [ 26 | { 27 | code: header + 'test.skip(t => { t.pass(); });', 28 | errors: [{ 29 | message, 30 | type: 'Identifier', 31 | line: 2, 32 | column: 6, 33 | suggestions: [{ 34 | desc: 'Remove the `.skip`', 35 | output: header + 'test(t => { t.pass(); });', 36 | }], 37 | }], 38 | }, 39 | ], 40 | }); 41 | -------------------------------------------------------------------------------- /test/no-todo-implementation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-todo-implementation'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const errors = [{ 14 | message: '`test.todo()` should not be passed an implementation function.', 15 | }]; 16 | const header = 'const test = require(\'ava\');\n'; 17 | 18 | ruleTester.run('no-todo-implementation', rule, { 19 | valid: [ 20 | header + 'test(t => {});', 21 | header + 'test("title", t => {});', 22 | header + 'test.todo("title");', 23 | header + 'notTest.todo(t => {});', 24 | // Shouldn't be triggered since it's not a test file 25 | 'test.todo("title", t => {});', 26 | ], 27 | invalid: [ 28 | { 29 | code: header + 'test.todo("title", t => {});', 30 | errors, 31 | }, 32 | { 33 | code: header + 'test.todo(t => {});', 34 | errors, 35 | }, 36 | { 37 | code: header + 'test.todo("title", function (t) {});', 38 | errors, 39 | }, 40 | { 41 | code: header + 'test.todo(function (t) {});', 42 | errors, 43 | }, 44 | { 45 | code: header + 'test.todo("title", function foo(t) {});', 46 | errors, 47 | }, 48 | { 49 | code: header + 'test.todo(function foo(t) {});', 50 | errors, 51 | }, 52 | ], 53 | }); 54 | -------------------------------------------------------------------------------- /test/no-todo-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-todo-test'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const errors = [{}]; 14 | const header = 'const test = require(\'ava\');\n'; 15 | 16 | ruleTester.run('no-todo-test', rule, { 17 | valid: [ 18 | header + 'test("my test name", t => { t.pass(); });', 19 | header + 'test.only("my test name", t => { t.pass(); });', 20 | header + 'notTest.todo(t => { t.pass(); });', 21 | // Shouldn't be triggered since it's not a test file 22 | 'test.todo("my test name");', 23 | ], 24 | invalid: [ 25 | { 26 | code: header + 'test.todo("my test name");', 27 | errors, 28 | }, 29 | ], 30 | }); 31 | -------------------------------------------------------------------------------- /test/no-unknown-modifiers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/no-unknown-modifiers'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const header = 'const test = require(\'ava\');\n'; 14 | 15 | ruleTester.run('no-unknown-modifiers', rule, { 16 | valid: [ 17 | `${header}test(t => {});`, 18 | `${header}test.after(t => {});`, 19 | `${header}test.afterEach(t => {});`, 20 | `${header}test.before(t => {});`, 21 | `${header}test.beforeEach(t => {});`, 22 | `${header}test.default(t => {});`, 23 | `${header}test.default.serial(t => {});`, 24 | `${header}test.only(t => {});`, 25 | `${header}test.serial(t => {});`, 26 | `${header}test.skip(t => {});`, 27 | `${header}test.todo(t => {});`, 28 | `${header}test.after.always(t => {});`, 29 | `${header}test.afterEach.always(t => {});`, 30 | `${header}test.failing(t => {});`, 31 | `${header}test.macro(t => {});`, 32 | // Shouldn't be triggered since it's not a test file 33 | 'test.foo(t => {});', 34 | ], 35 | invalid: [ 36 | { 37 | code: `${header}test.foo(t => {});`, 38 | errors: [{ 39 | message: 'Unknown test modifier `.foo`.', 40 | type: 'Identifier', 41 | line: 2, 42 | column: 6, 43 | }], 44 | }, 45 | { 46 | code: `${header}test.onlu(t => {});`, 47 | errors: [{ 48 | message: 'Unknown test modifier `.onlu`.', 49 | type: 'Identifier', 50 | line: 2, 51 | column: 6, 52 | }], 53 | }, 54 | { 55 | code: `${header}test.beforeeach(t => {});`, 56 | errors: [{ 57 | message: 'Unknown test modifier `.beforeeach`.', 58 | type: 'Identifier', 59 | line: 2, 60 | column: 6, 61 | }], 62 | }, 63 | { 64 | code: `${header}test.c.only(t => {});`, 65 | errors: [{ 66 | message: 'Unknown test modifier `.c`.', 67 | type: 'Identifier', 68 | line: 2, 69 | column: 6, 70 | }], 71 | }, 72 | { 73 | code: `${header}test.cb(t => {});`, 74 | errors: [{ 75 | message: 'Unknown test modifier `.cb`.', 76 | type: 'Identifier', 77 | line: 2, 78 | column: 6, 79 | }], 80 | }, 81 | { 82 | code: `${header}test.foo.bar.baz(t => {});`, 83 | errors: [{ 84 | message: 'Unknown test modifier `.foo`.', 85 | type: 'Identifier', 86 | line: 2, 87 | column: 6, 88 | }], 89 | }, 90 | { 91 | code: `${header}test.test(t => {});`, 92 | errors: [{ 93 | message: 'Unknown test modifier `.test`.', 94 | type: 'Identifier', 95 | line: 2, 96 | column: 6, 97 | }], 98 | }, 99 | ], 100 | }); 101 | -------------------------------------------------------------------------------- /test/package.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs'); 4 | const path = require('node:path'); 5 | const test = require('ava'); 6 | const pify = require('pify'); 7 | const index = require('..'); 8 | 9 | let ruleFiles; 10 | 11 | test.before(async () => { 12 | const files = await pify(fs.readdir)('rules'); 13 | ruleFiles = files.filter(file => path.extname(file) === '.js'); 14 | }); 15 | 16 | const testSorted = (t, actualOrder, sourceName) => { 17 | const sortedOrder = [...actualOrder].sort(); 18 | 19 | for (const [wantedIndex, name] of sortedOrder.entries()) { 20 | const actualIndex = actualOrder.indexOf(name); 21 | const whereMessage = (wantedIndex === 0) ? '' : `, after '${sortedOrder[wantedIndex - 1]}'`; 22 | t.is(actualIndex, wantedIndex, `${sourceName} should be alphabetically sorted, '${name}' should be placed at index ${wantedIndex}${whereMessage}`); 23 | } 24 | }; 25 | 26 | test('Every rule is defined in index file in alphabetical order', t => { 27 | for (const file of ruleFiles) { 28 | const name = path.basename(file, '.js'); 29 | 30 | // Ignoring tests for no-ignored-test-files 31 | if (name === 'no-ignored-test-files') { 32 | return; 33 | } 34 | 35 | t.truthy(index.rules[name], `'${name}' is not exported in 'index.js'`); 36 | t.truthy(index.configs.recommended.rules[`ava/${name}`], `'${name}' is not set in the recommended config`); 37 | t.truthy(fs.existsSync(path.join('docs/rules', `${name}.md`)), `There is no documentation for '${name}'`); 38 | t.truthy(fs.existsSync(path.join('test', file)), `There are no tests for '${name}'`); 39 | } 40 | 41 | t.is(Object.keys(index.rules).length, ruleFiles.length, 42 | 'There are more exported rules than rule files.'); 43 | t.is(Object.keys(index.configs.recommended.rules).length, ruleFiles.length, 44 | 'There are more exported rules in the recommended config than rule files.'); 45 | 46 | testSorted(t, Object.keys(index.configs.recommended.rules), 'configs.recommended.rules'); 47 | }); 48 | -------------------------------------------------------------------------------- /test/prefer-async-await.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/prefer-async-await'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const header = 'const test = require(\'ava\');\n'; 14 | const errors = [{ 15 | message: 'Prefer using async/await instead of returning a Promise.', 16 | }]; 17 | 18 | ruleTester.run('prefer-async-await', rule, { 19 | valid: [ 20 | header + 'test(t => { t.is(1, 1); });', 21 | header + 'test(t => { foo(); });', 22 | header + 'test(t => { return foo(); });', 23 | header + 'test(t => { foo().then(fn); });', 24 | header + 'test(t => { function foo() { return foo().then(fn); } });', 25 | header + 'test(t => foo().then(fn));', 26 | // TODO: this should be an error, needs improvement 27 | header + 'test(t => { const bar = foo().then(fn); return bar; });', 28 | // Shouldn't be triggered since it's not a test file 29 | 'test(t => { return foo().then(fn); });', 30 | ], 31 | invalid: [ 32 | { 33 | code: header + 'test(t => { return foo().then(fn); });', 34 | errors, 35 | }, 36 | { 37 | code: header + 'test(function(t) { return foo().then(fn); });', 38 | errors, 39 | }, 40 | { 41 | code: header + 'test(t => { return foo().then(fn).catch(fn2); });', 42 | errors, 43 | }, 44 | { 45 | code: header + 'test(t => { return foo().catch(fn2).then(fn); });', 46 | errors, 47 | }, 48 | { 49 | code: header + 'test(t => { const bar = foo(); return bar.then(fn); });', 50 | errors, 51 | }, 52 | ], 53 | }); 54 | -------------------------------------------------------------------------------- /test/prefer-power-assert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/prefer-power-assert'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | parserOptions: { 12 | ecmaVersion: 'latest', 13 | sourceType: 'module', 14 | }, 15 | }); 16 | 17 | const errors = [{}]; 18 | 19 | function testNotAllowedMethod(methodName) { 20 | ruleTester.run('prefer-power-assert', rule, { 21 | valid: [], 22 | invalid: [ 23 | { 24 | code: `import test from 'ava';\n test(t => { t.${methodName}; });`, 25 | errors, 26 | }, 27 | { 28 | code: `import test from 'ava';\n test(t => { t.skip.${methodName}; });`, 29 | errors, 30 | }, 31 | ], 32 | }); 33 | } 34 | 35 | const notAllowedMethods = [ 36 | 'truthy(foo)', 37 | 'falsy(foo)', 38 | 'true(foo)', 39 | 'false(foo)', 40 | 'is(foo, bar)', 41 | 'not(foo, bar)', 42 | 'regex(str, re)', 43 | 'notRegex(str, re)', 44 | 'ifError(err)', 45 | ]; 46 | 47 | for (const methodName of notAllowedMethods) { 48 | testNotAllowedMethod(methodName); 49 | } 50 | 51 | function testAllowedMethod(methodName) { 52 | ruleTester.run('prefer-power-assert', rule, { 53 | valid: [ 54 | { 55 | code: `import test from 'ava';\n test(t => { t.${methodName}; });`, 56 | }, 57 | { 58 | code: `import test from 'ava';\n test(t => { t.skip.${methodName}; });`, 59 | }, 60 | ], 61 | invalid: [], 62 | }); 63 | } 64 | 65 | const allowedMethods = [ 66 | 'assert(foo)', 67 | 'deepEqual(foo, bar)', 68 | 'notDeepEqual(foo, bar)', 69 | 'like(foo, bar)', 70 | 'throws(block)', 71 | 'notThrows(block)', 72 | 'pass(foo)', 73 | 'fail(foo)', 74 | ]; 75 | 76 | for (const methodName of allowedMethods) { 77 | testAllowedMethod(methodName); 78 | } 79 | 80 | function testWithModifier(modifier) { 81 | ruleTester.run('prefer-power-assert', rule, { 82 | valid: [ 83 | { 84 | code: `import test from 'ava';\n test.${modifier}(t => { t.assert(foo); });`, 85 | }, 86 | ], 87 | invalid: [ 88 | { 89 | code: `import test from 'ava';\n test.${modifier}(t => { t.is(foo); });`, 90 | errors, 91 | }, 92 | ], 93 | }); 94 | } 95 | 96 | for (const modifiers of ['skip', 'only', 'serial']) { 97 | testWithModifier(modifiers); 98 | } 99 | 100 | function testDeclaration(declaration) { 101 | ruleTester.run('prefer-power-assert', rule, { 102 | valid: [ 103 | { 104 | code: `${declaration}\n test(t => { t.assert(foo); });`, 105 | }, 106 | ], 107 | invalid: [ 108 | { 109 | code: `${declaration}\n test(t => { t.is(foo); });`, 110 | errors, 111 | }, 112 | ], 113 | }); 114 | } 115 | 116 | for (const declaration of [ 117 | 'var test = require(\'ava\');', 118 | 'let test = require(\'ava\');', 119 | 'const test = require(\'ava\');', 120 | 'import test from \'ava\';', 121 | ]) { 122 | testDeclaration(declaration); 123 | } 124 | 125 | const assertionInNestedCode = ` 126 | import test from 'ava'; 127 | 128 | test(t => { 129 | const foo = () => { 130 | t.is('foo', 'bar'); 131 | }; 132 | foo(); 133 | }); 134 | `; 135 | 136 | ruleTester.run('prefer-power-assert', rule, { 137 | valid: [ 138 | { 139 | code: 'import test from \'ava\';\n test(function (t) { t.assert(foo); });', 140 | }, 141 | // Shouldn't be triggered since it's not a test file 142 | { 143 | code: 'test(t => {});', 144 | }, 145 | ], 146 | invalid: [ 147 | { 148 | code: assertionInNestedCode, 149 | errors, 150 | }, 151 | ], 152 | }); 153 | -------------------------------------------------------------------------------- /test/prefer-t-regex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/prefer-t-regex'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const errors = assertion => [{ 14 | message: `Prefer using the \`t.${assertion}()\` assertion.`, 15 | }]; 16 | const header = 'const test = require(\'ava\');\n'; 17 | 18 | ruleTester.run('prefer-t-regex', rule, { 19 | valid: [ 20 | header + 'test(t => t.regex("foo", /\\d+/));', 21 | header + 'test(t => t.regex(foo(), /\\d+/));', 22 | header + 'test(t => t.is(/\\d+/.test("foo"), foo));', 23 | header + 'test(t => t.is(RegExp("\\d+").test("foo"), foo));', 24 | header + 'test(t => t.is(RegExp(/\\d+/).test("foo"), foo));', 25 | header + 'test(t => t.is(/\\d+/, /\\w+/));', 26 | header + 'test(t => t.is(123, /\\d+/));', 27 | header + 'test(t => t.true(1 === 1));', 28 | header + 'test(t => t.true(foo.bar()));', 29 | header + 'test(t => t.is(foo, true));', 30 | header + 'const a = /\\d+/;\ntest(t => t.truthy(a));', 31 | header + 'const a = "not a regexp";\ntest(t => t.true(a.test("foo")));', 32 | header + 'test("main", t => t.true(foo()));', 33 | header + 'test(t => t.regex(foo, new RegExp("\\d+")));', 34 | header + 'test(t => t.regex(foo, RegExp("\\d+")));', 35 | header + 'test(t => t.regex(foo, new RegExp(/\\d+/)));', 36 | header + 'test(t => t.regex(foo, RegExp(/\\d+/)));', 37 | // Shouldn't be triggered since it's not a test file 38 | 'test(t => t.true(/\\d+/.test("foo")));', 39 | 'test(t => t.true());', 40 | // These shouldn't cause errors as this rule affects them. 41 | // This rule would crash on the following. 42 | header + 'test(t => t.true());', 43 | header + 'test(t => t.is(true))', 44 | header + 'test(t => t.is())', 45 | header + 'test(t => t.false())', 46 | header + 'test(t => t.falsy())', 47 | header + 'test(t => t.truthy())', 48 | header + 'test(t => t.deepEqual(true))', 49 | ], 50 | invalid: [ 51 | { 52 | code: header + 'test(t => t.true(/\\d+/.test("foo")));', 53 | output: header + 'test(t => t.regex("foo", /\\d+/));', 54 | errors: errors('regex'), 55 | }, 56 | { 57 | code: header + 'test(t => t.false(foo.search(/\\d+/)));', 58 | output: header + 'test(t => t.notRegex(foo, /\\d+/));', 59 | errors: errors('notRegex'), 60 | }, 61 | { 62 | code: header + 'const regexp = /\\d+/;\ntest(t => t.true(foo.search(regexp)));', 63 | output: header + 'const regexp = /\\d+/;\ntest(t => t.regex(foo, regexp));', 64 | errors: errors('regex'), 65 | }, 66 | { 67 | code: header + 'test(t => t.truthy(foo.match(/\\d+/)));', 68 | output: header + 'test(t => t.regex(foo, /\\d+/));', 69 | errors: errors('regex'), 70 | }, 71 | { 72 | code: header + 'test(t => t.false(/\\d+/.test("foo")));', 73 | output: header + 'test(t => t.notRegex("foo", /\\d+/));', 74 | errors: errors('notRegex'), 75 | }, 76 | { 77 | code: header + 'test(t => t.true(/\\d+/.test(foo())));', 78 | output: header + 'test(t => t.regex(foo(), /\\d+/));', 79 | errors: errors('regex'), 80 | }, 81 | { 82 | code: header + 'test(t => t.is(/\\d+/.test(foo), true));', 83 | output: header + 'test(t => t.regex(foo, /\\d+/));', 84 | errors: errors('regex'), 85 | }, 86 | { 87 | code: header + 'test(t => t.is(/\\d+/.test(foo), false));', 88 | output: header + 'test(t => t.notRegex(foo, /\\d+/));', 89 | errors: errors('notRegex'), 90 | }, 91 | { 92 | code: header + 'const reg = /\\d+/;\ntest(t => t.true(reg.test(foo.bar())));', 93 | output: header + 'const reg = /\\d+/;\ntest(t => t.regex(foo.bar(), reg));', 94 | errors: errors('regex'), 95 | }, 96 | // The same as the above tests but with `RegExp()` object instead of a regex literal 97 | { 98 | code: header + 'test(t => t.true(new RegExp("\\d+").test("foo")));', 99 | output: header + 'test(t => t.regex("foo", new RegExp("\\d+")));', 100 | errors: errors('regex'), 101 | }, 102 | { 103 | code: header + 'test(t => t.false(foo.search(new RegExp("\\d+"))));', 104 | output: header + 'test(t => t.notRegex(foo, new RegExp("\\d+")));', 105 | errors: errors('notRegex'), 106 | }, 107 | { 108 | code: header + 'const regexp = RegExp("\\d+");\ntest(t => t.true(foo.search(regexp)));', 109 | output: header + 'const regexp = RegExp("\\d+");\ntest(t => t.regex(foo, regexp));', 110 | errors: errors('regex'), 111 | }, 112 | { 113 | code: header + 'test(t => t.truthy(foo.match(new RegExp("\\d+"))));', 114 | output: header + 'test(t => t.regex(foo, new RegExp("\\d+")));', 115 | errors: errors('regex'), 116 | }, 117 | { 118 | code: header + 'test(t => t.false(RegExp("\\d+").test("foo")));', 119 | output: header + 'test(t => t.notRegex("foo", RegExp("\\d+")));', 120 | errors: errors('notRegex'), 121 | }, 122 | { 123 | code: header + 'test(t => t.true(new RegExp("\\d+").test(foo())));', 124 | output: header + 'test(t => t.regex(foo(), new RegExp("\\d+")));', 125 | errors: errors('regex'), 126 | }, 127 | { 128 | code: header + 'test(t => t.is(new RegExp("\\d+").test(foo), true));', 129 | output: header + 'test(t => t.regex(foo, new RegExp("\\d+")));', 130 | errors: errors('regex'), 131 | }, 132 | { 133 | code: header + 'test(t => t.is(new RegExp("\\d+").test(foo), false));', 134 | output: header + 'test(t => t.notRegex(foo, new RegExp("\\d+")));', 135 | errors: errors('notRegex'), 136 | }, 137 | { 138 | code: header + 'const reg = RegExp("\\d+");\ntest(t => t.true(reg.test(foo.bar())));', 139 | output: header + 'const reg = RegExp("\\d+");\ntest(t => t.regex(foo.bar(), reg));', 140 | errors: errors('regex'), 141 | }, 142 | // The same as the above tests but with regex literal instead of string regex 143 | { 144 | code: header + 'test(t => t.true(new RegExp(/\\d+/).test("foo")));', 145 | output: header + 'test(t => t.regex("foo", new RegExp(/\\d+/)));', 146 | errors: errors('regex'), 147 | }, 148 | { 149 | code: header + 'test(t => t.false(foo.search(new RegExp(/\\d+/))));', 150 | output: header + 'test(t => t.notRegex(foo, new RegExp(/\\d+/)));', 151 | errors: errors('notRegex'), 152 | }, 153 | { 154 | code: header + 'const regexp = RegExp(/\\d+/);\ntest(t => t.true(foo.search(regexp)));', 155 | output: header + 'const regexp = RegExp(/\\d+/);\ntest(t => t.regex(foo, regexp));', 156 | errors: errors('regex'), 157 | }, 158 | { 159 | code: header + 'test(t => t.truthy(foo.match(new RegExp(/\\d+/))));', 160 | output: header + 'test(t => t.regex(foo, new RegExp(/\\d+/)));', 161 | errors: errors('regex'), 162 | }, 163 | { 164 | code: header + 'test(t => t.false(RegExp(/\\d+/).test("foo")));', 165 | output: header + 'test(t => t.notRegex("foo", RegExp(/\\d+/)));', 166 | errors: errors('notRegex'), 167 | }, 168 | { 169 | code: header + 'test(t => t.true(new RegExp(/\\d+/).test(foo())));', 170 | output: header + 'test(t => t.regex(foo(), new RegExp(/\\d+/)));', 171 | errors: errors('regex'), 172 | }, 173 | { 174 | code: header + 'test(t => t.is(new RegExp(/\\d+/).test(foo), true));', 175 | output: header + 'test(t => t.regex(foo, new RegExp(/\\d+/)));', 176 | errors: errors('regex'), 177 | }, 178 | { 179 | code: header + 'test(t => t.is(new RegExp(/\\d+/).test(foo), false));', 180 | output: header + 'test(t => t.notRegex(foo, new RegExp(/\\d+/)));', 181 | errors: errors('notRegex'), 182 | }, 183 | { 184 | code: header + 'const reg = RegExp(/\\d+/);\ntest(t => t.true(reg.test(foo.bar())));', 185 | output: header + 'const reg = RegExp(/\\d+/);\ntest(t => t.regex(foo.bar(), reg));', 186 | errors: errors('regex'), 187 | }, 188 | ], 189 | }); 190 | -------------------------------------------------------------------------------- /test/test-title-format.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/test-title-format'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const errors = [{}]; 14 | const header = 'const test = require(\'ava\');\n'; 15 | 16 | ruleTester.run('test-title-format', rule, { 17 | valid: [ 18 | header + 'test("Foo", t => { t.pass(); });', 19 | { 20 | code: header + 'test("Foo", t => { t.pass(); });', 21 | options: [{format: '.'}], 22 | }, 23 | { 24 | code: header + 'test("Should pass tests.", t => { t.pass(); });', 25 | options: [{format: '^Should .+\\.$'}], 26 | }, 27 | { 28 | code: header + 'test.todo("Should pass tests.");', 29 | options: [{format: '^Should .+\\.$'}], 30 | }, 31 | { 32 | code: header + 'test(t => { t.pass(); });', 33 | options: [{format: '^Should'}], 34 | }, 35 | { 36 | code: header + 'notTest("Foo", t => { t.pass(); });', 37 | options: [{format: '^Should'}], 38 | }, 39 | { 40 | code: header + 'test(macro, t => { t.pass(); });', 41 | options: [{format: '^Should'}], 42 | }, 43 | // Shouldn't be triggered since it's not a test file 44 | { 45 | code: 'test("Test", t => { t.pass(); });', 46 | options: [{format: '^Should'}], 47 | }, 48 | ], 49 | invalid: [ 50 | { 51 | code: header + 'test("Test something", t => { t.pass(); });', 52 | options: [{format: '^Should'}], 53 | errors, 54 | }, 55 | { 56 | code: header + 'test.todo("Test something");', 57 | options: [{format: '^Should'}], 58 | errors, 59 | }, 60 | ], 61 | }); 62 | -------------------------------------------------------------------------------- /test/test-title.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/test-title'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const errors = [{}]; 14 | const header = 'const test = require(\'ava\');\n'; 15 | 16 | ruleTester.run('test-title', rule, { 17 | valid: [ 18 | header + 'test("my test name", t => { t.pass(); t.end(); });', 19 | header + 'test(`my test name`, t => { t.pass(); t.end(); });', 20 | header + 'test(\'my test name\', t => { t.pass(); t.end(); });', 21 | header + 'test.todo("my test name");', 22 | header + 'test.before(t => {});', 23 | header + 'test.after(t => {});', 24 | header + 'test.beforeEach(t => {});', 25 | header + 'test.afterEach(t => {});', 26 | header + 'test.macro(t => {});', 27 | header + 'notTest(t => { t.pass(); t.end(); });', 28 | header + 'test([], arg1, arg2);', 29 | header + 'test({}, arg1, arg2);', 30 | // Shouldn't be triggered since it's not a test file 31 | 'test(t => {});', 32 | ], 33 | invalid: [ 34 | { 35 | code: header + 'test(t => {});', 36 | errors, 37 | }, 38 | { 39 | code: header + 'test(t => {}, "my test name");', 40 | errors, 41 | }, 42 | { 43 | code: header + 'test(t => { t.pass(); t.end(); });', 44 | errors, 45 | }, 46 | { 47 | code: header + 'test(t => { t.pass(); t.end(); });', 48 | errors, 49 | }, 50 | { 51 | code: header + 'test.todo();', 52 | errors, 53 | }, 54 | ], 55 | }); 56 | -------------------------------------------------------------------------------- /test/use-t-throws-async-well.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/use-t-throws-async-well'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | parserOptions: { 9 | ecmaVersion: 'latest', 10 | }, 11 | }); 12 | 13 | const header = 'const test = require(\'ava\');\n'; 14 | 15 | function asyncTestCase(contents, prependHeader) { 16 | const content = `test(async t => { ${contents} });`; 17 | 18 | if (prependHeader !== false) { 19 | return header + content; 20 | } 21 | 22 | return content; 23 | } 24 | 25 | function syncTestCase(contents, prependHeader) { 26 | const content = `test(t => { ${contents} });`; 27 | 28 | if (prependHeader !== false) { 29 | return header + content; 30 | } 31 | 32 | return content; 33 | } 34 | 35 | ruleTester.run('use-t-throws-async-well', rule, { 36 | valid: [ 37 | asyncTestCase('await t.throwsAsync(f)'), 38 | asyncTestCase('await t.notThrowsAsync(f)'), 39 | asyncTestCase('t.throws(f)'), 40 | asyncTestCase('t.notThrows(f)'), 41 | asyncTestCase('f(t.throwsAsync(f))'), 42 | asyncTestCase('let p = t.throwsAsync(f)'), 43 | asyncTestCase('p = t.throwsAsync(f)'), 44 | asyncTestCase('t.throwsAsync(f)', false), // Shouldn't be triggered since it's not a test file 45 | syncTestCase('t.throwsAsync(f)', false), // Shouldn't be triggered since it's not a test file 46 | ], 47 | invalid: [ 48 | { 49 | code: syncTestCase('t.throwsAsync(f)'), 50 | errors: [{ 51 | message: 'Use `await` with `t.throwsAsync()`.', 52 | }], 53 | }, 54 | { 55 | code: syncTestCase('t.notThrowsAsync(f)'), 56 | errors: [{ 57 | message: 'Use `await` with `t.notThrowsAsync()`.', 58 | }], 59 | }, 60 | { 61 | code: asyncTestCase('t.throwsAsync(f)'), 62 | output: asyncTestCase('await t.throwsAsync(f)'), 63 | errors: [{ 64 | message: 'Use `await` with `t.throwsAsync()`.', 65 | }], 66 | }, 67 | { 68 | code: asyncTestCase('t.notThrowsAsync(f)'), 69 | output: asyncTestCase('await t.notThrowsAsync(f)'), 70 | errors: [{ 71 | message: 'Use `await` with `t.notThrowsAsync()`.', 72 | }], 73 | }, 74 | ], 75 | }); 76 | -------------------------------------------------------------------------------- /test/use-t-well.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/use-t-well'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const header = 'const test = require(\'ava\');\n'; 14 | 15 | function error(message) { 16 | return {message}; 17 | } 18 | 19 | function testCase(contents, prependHeader) { 20 | const content = `test(t => { ${contents} });`; 21 | 22 | if (prependHeader !== false) { 23 | return header + content; 24 | } 25 | 26 | return content; 27 | } 28 | 29 | ruleTester.run('use-t-well', rule, { 30 | valid: [ 31 | testCase('t;'), 32 | testCase('fn(t);'), 33 | testCase('t.end();'), 34 | testCase('t.pass();'), 35 | testCase('t.fail();'), 36 | testCase('t.assert(v);'), 37 | testCase('t.truthy(v);'), 38 | testCase('t.falsy(v);'), 39 | testCase('t.true(v);'), 40 | testCase('t.false(v);'), 41 | testCase('t.is(v);'), 42 | testCase('t.not(v);'), 43 | testCase('t.deepEqual(v, v);'), 44 | testCase('t.notDeepEqual(v, v);'), 45 | testCase('t.throws(fn);'), 46 | testCase('t.notThrows(fn);'), 47 | testCase('t.throwsAsync(fn);'), 48 | testCase('t.notThrowsAsync(fn);'), 49 | testCase('t.regex(v, /v/);'), 50 | testCase('t.notRegex(v, /v/);'), 51 | testCase('t.snapshot(v);'), 52 | testCase('t.ifError(error);'), 53 | testCase('t.deepEqual.skip(a, a);'), 54 | testCase('t.context.a = 1;'), 55 | testCase('t.context.foo.skip();'), 56 | testCase('console.log(t.context);'), 57 | testCase('t.true(t.context.title(foo));'), 58 | testCase('console.log(t.title);'), 59 | testCase('t.true(t.title.includes(\'Unicorns\'));'), 60 | testCase('setImmediate(t.end);'), 61 | testCase('t.deepEqual;'), 62 | testCase('t.plan(1);'), 63 | testCase('t.log(\'Unicorns\');'), 64 | testCase('a.foo();'), 65 | testCase('t.context.foo(a, a);'), 66 | testCase('foo.t.bar(a, a);'), 67 | testCase('t.teardown(() => {});'), 68 | testCase('t.timeout(100);'), 69 | testCase('t.try(tt => tt.pass())'), 70 | testCase('t.try(tt => tt.pass(), 1, 2)'), 71 | testCase('t.try(\'title\', tt => tt.pass())'), 72 | testCase('t.try(\'title\', tt => tt.pass(), 1, 2)'), 73 | testCase('t.like'), 74 | testCase('t.like(v, v)'), 75 | testCase('t.like(actual, {}, "")'), 76 | testCase('t.like.skip(v, v)'), 77 | // Shouldn't be triggered since it's not a test file 78 | testCase('t.foo(a, a);', false), 79 | testCase('t.foo;', false), 80 | ], 81 | invalid: [ 82 | { 83 | code: testCase('t();'), 84 | errors: [error('`t` is not a function.')], 85 | }, 86 | { 87 | code: testCase('t.foo(a, a);'), 88 | errors: [error('Unknown assertion method `.foo`.')], 89 | }, 90 | { 91 | code: testCase('t.depEqual(a, a);'), 92 | output: testCase('t.deepEqual(a, a);'), 93 | errors: [error('Misspelled `.deepEqual` as `.depEqual`.')], 94 | }, 95 | { 96 | code: testCase('t.deepEqual.skp(a, a);'), 97 | output: testCase('t.deepEqual.skip(a, a);'), 98 | errors: [error('Misspelled `.skip` as `.skp`.')], 99 | }, 100 | { 101 | code: testCase('t.context();'), 102 | errors: [error('Unknown assertion method `.context`.')], 103 | }, 104 | { 105 | code: testCase('t.title();'), 106 | errors: [error('Unknown assertion method `.title`.')], 107 | }, 108 | { 109 | code: testCase('t.a = 1;'), 110 | errors: [error('Unknown member `.a`. Use `.context.a` instead.')], 111 | }, 112 | { 113 | code: testCase('t.ctx.a = 1;'), 114 | errors: [error('Unknown member `.ctx`. Use `.context.ctx` instead.')], 115 | }, 116 | { 117 | code: testCase('t.deepEqu;'), 118 | output: testCase('t.deepEqual;'), 119 | errors: [error('Misspelled `.deepEqual` as `.deepEqu`.')], 120 | }, 121 | { 122 | code: testCase('t.deepEqual.is(a, a);'), 123 | errors: [error('Can\'t chain assertion methods.')], 124 | }, 125 | { 126 | code: testCase('t.paln(1);'), 127 | output: testCase('t.plan(1);'), 128 | errors: [error('Misspelled `.plan` as `.paln`.')], 129 | }, 130 | { 131 | code: testCase('t.skip();'), 132 | errors: [error('Missing assertion method.')], 133 | }, 134 | { 135 | code: testCase('t.deepEqual.skip.skip(a, a);'), 136 | output: testCase('t.deepEqual.skip(a, a);'), 137 | errors: [error('Too many chained uses of `.skip`.')], 138 | }, 139 | { 140 | code: testCase('t.falsey(a);'), 141 | output: testCase('t.falsy(a);'), 142 | errors: [error('Misspelled `.falsy` as `.falsey`.')], 143 | }, 144 | { 145 | code: testCase('t.truthey(a);'), 146 | output: testCase('t.truthy(a);'), 147 | errors: [error('Misspelled `.truthy` as `.truthey`.')], 148 | }, 149 | { 150 | code: testCase('t.deepequal(a, {});'), 151 | output: testCase('t.deepEqual(a, {});'), 152 | errors: [error('Misspelled `.deepEqual` as `.deepequal`.')], 153 | }, 154 | { 155 | code: testCase('t.contxt;'), 156 | output: testCase('t.context;'), 157 | errors: [error('Misspelled `.context` as `.contxt`.')], 158 | }, 159 | { 160 | code: testCase('t.notdeepEqual(a, {});'), 161 | output: testCase('t.notDeepEqual(a, {});'), 162 | errors: [error('Misspelled `.notDeepEqual` as `.notdeepEqual`.')], 163 | }, 164 | { 165 | code: testCase('t.throw(a);'), 166 | output: testCase('t.throws(a);'), 167 | errors: [error('Misspelled `.throws` as `.throw`.')], 168 | }, 169 | { 170 | code: testCase('t.notThrow(a);'), 171 | output: testCase('t.notThrows(a);'), 172 | errors: [error('Misspelled `.notThrows` as `.notThrow`.')], 173 | }, 174 | { 175 | code: testCase('t.throwAsync(a);'), 176 | output: testCase('t.throwsAsync(a);'), 177 | errors: [error('Misspelled `.throwsAsync` as `.throwAsync`.')], 178 | }, 179 | { 180 | code: testCase('t.notthrowAsync(a);'), 181 | output: testCase('t.notThrowsAsync(a);'), 182 | errors: [error('Misspelled `.notThrowsAsync` as `.notthrowAsync`.')], 183 | }, 184 | { 185 | code: testCase('t.regexp(a, /r/);'), 186 | output: testCase('t.regex(a, /r/);'), 187 | errors: [error('Misspelled `.regex` as `.regexp`.')], 188 | }, 189 | { 190 | code: testCase('t.notregexp(a, /r/);'), 191 | output: testCase('t.notRegex(a, /r/);'), 192 | errors: [error('Misspelled `.notRegex` as `.notregexp`.')], 193 | }, 194 | { 195 | code: testCase('t.contxt.foo = 1;'), 196 | output: testCase('t.context.foo = 1;'), 197 | errors: [error('Misspelled `.context` as `.contxt`.')], 198 | }, 199 | 200 | { 201 | code: testCase('t.skip.deepEqual(a, a);'), 202 | output: testCase('t.deepEqual.skip(a, a);'), 203 | errors: [error('`.skip` modifier should be the last in chain.')], 204 | }, 205 | { 206 | code: testCase('t.skp.deepEqual(a, a);'), 207 | output: testCase('t.skip.deepEqual(a, a);'), 208 | errors: [error('Misspelled `.skip` as `.skp`.')], 209 | }, 210 | { 211 | code: testCase('t.deepEqual.context(a, a);'), 212 | errors: [error('Unknown assertion method `.context`.')], 213 | }, 214 | { 215 | code: testCase('t.lik(a, a);'), 216 | output: testCase('t.like(a, a);'), 217 | errors: [error('Misspelled `.like` as `.lik`.')], 218 | }, 219 | ], 220 | }); 221 | -------------------------------------------------------------------------------- /test/use-t.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/use-t'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | }, 14 | }); 15 | 16 | const parameterNotNamedTErrors = [{ 17 | message: 'Test parameter should be named `t`.', 18 | }]; 19 | 20 | const header = 'const test = require(\'ava\');\n'; 21 | 22 | ruleTester.run('use-t', rule, { 23 | valid: [ 24 | header + 'test();', 25 | header + 'test(() => {});', 26 | header + 'test(t => {});', 27 | header + 'test("test name", t => {});', 28 | header + 'test((t, foo) => {});', 29 | header + 'test(function (t) {});', 30 | header + 'test(testFunction);', 31 | header + 'test.macro(testFunction);', 32 | header + 'test.macro(t => {});', 33 | header + 'test.macro({exec: t => {}, title: () => "title"});', 34 | header + 'test.todo("test name");', 35 | // Shouldn't be triggered since it's not a test file 36 | 'test(foo => {});', 37 | header + 'test(macro, arg1, (p1) => {})', 38 | header + 'test("name", macro, arg1, (p1) => {})', 39 | header + 'test("name", macro, (p1) => {})', 40 | ], 41 | invalid: [ 42 | { 43 | code: header + 'test(foo => {});', 44 | errors: parameterNotNamedTErrors, 45 | }, 46 | { 47 | code: header + 'test("test name", foo => {});', 48 | errors: parameterNotNamedTErrors, 49 | }, 50 | { 51 | code: header + 'test(function (foo) {});', 52 | errors: parameterNotNamedTErrors, 53 | }, 54 | { 55 | code: header + 'test.macro(function (foo) {});', 56 | errors: parameterNotNamedTErrors, 57 | }, 58 | { 59 | code: header + 'test.macro({ exec(foo) {} });', 60 | errors: parameterNotNamedTErrors, 61 | }, 62 | ], 63 | }); 64 | -------------------------------------------------------------------------------- /test/use-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/use-test'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | }, 14 | }); 15 | 16 | const typescriptRuleTester = avaRuleTester(test, { 17 | parser: require.resolve('@typescript-eslint/parser'), 18 | }); 19 | 20 | const errors = [{}]; 21 | 22 | const commonTestCases = { 23 | valid: [ 24 | {code: 'var test = require(\'ava\');', filename: 'file.js'}, 25 | {code: 'let test = require(\'ava\');', filename: 'file.js'}, 26 | {code: 'const test = require(\'ava\');', filename: 'file.js'}, 27 | {code: 'const a = 1, test = require(\'ava\'), b = 2;', filename: 'file.js'}, 28 | {code: 'const test = require(\'foo\');', filename: 'file.js'}, 29 | {code: 'import test from \'ava\';', filename: 'file.js'}, 30 | {code: 'import test, {} from \'ava\';', filename: 'file.js'}, 31 | {code: 'import test from \'foo\';', filename: 'file.js'}, 32 | {code: 'var anyTest = require(\'ava\');', filename: 'file.ts'}, 33 | {code: 'let anyTest = require(\'ava\');', filename: 'file.ts'}, 34 | {code: 'const anyTest = require(\'ava\');', filename: 'file.ts'}, 35 | {code: 'const a = 1, anyTest = require(\'ava\'), b = 2;', filename: 'file.ts'}, 36 | {code: 'const anyTest = require(\'foo\');', filename: 'file.ts'}, 37 | {code: 'import anyTest from \'ava\';', filename: 'file.ts'}, 38 | {code: 'import anyTest, {} from \'ava\';', filename: 'file.ts'}, 39 | {code: 'import anyTest from \'foo\';', filename: 'file.ts'}, 40 | {code: 'var anyTest = require(\'ava\');', filename: 'file.tsx'}, 41 | {code: 'let anyTest = require(\'ava\');', filename: 'file.tsx'}, 42 | {code: 'const anyTest = require(\'ava\');', filename: 'file.tsx'}, 43 | {code: 'const a = 1, anyTest = require(\'ava\'), b = 2;', filename: 'file.tsx'}, 44 | {code: 'const anyTest = require(\'foo\');', filename: 'file.tsx'}, 45 | {code: 'import anyTest from \'ava\';', filename: 'file.tsx'}, 46 | {code: 'import anyTest, {} from \'ava\';', filename: 'file.tsx'}, 47 | {code: 'import anyTest from \'foo\';', filename: 'file.tsx'}, 48 | ], 49 | invalid: [ 50 | { 51 | code: 'var ava = require(\'ava\');', 52 | errors, 53 | filename: 'file.ts', 54 | }, 55 | { 56 | code: 'let ava = require(\'ava\');', 57 | errors, 58 | filename: 'file.ts', 59 | }, 60 | { 61 | code: 'const ava = require(\'ava\');', 62 | errors, 63 | filename: 'file.ts', 64 | }, 65 | { 66 | code: 'const a = 1, ava = require(\'ava\'), b = 2;', 67 | errors, 68 | filename: 'file.ts', 69 | }, 70 | { 71 | code: 'import ava from \'ava\';', 72 | errors, 73 | filename: 'file.ts', 74 | }, 75 | { 76 | code: 'var anyTest = require(\'ava\');', 77 | errors, 78 | filename: 'file.js', 79 | }, 80 | { 81 | code: 'var ava = require(\'ava\');', 82 | errors, 83 | filename: 'file.ts', 84 | }, 85 | { 86 | code: 'let ava = require(\'ava\');', 87 | errors, 88 | filename: 'file.ts', 89 | }, 90 | { 91 | code: 'const ava = require(\'ava\');', 92 | errors, 93 | filename: 'file.ts', 94 | }, 95 | { 96 | code: 'const a = 1, ava = require(\'ava\'), b = 2;', 97 | errors, 98 | filename: 'file.ts', 99 | }, 100 | { 101 | code: 'import ava from \'ava\';', 102 | errors, 103 | filename: 'file.ts', 104 | }, 105 | { 106 | code: 'var ava = require(\'ava\');', 107 | errors, 108 | filename: 'file.tsx', 109 | }, 110 | { 111 | code: 'let ava = require(\'ava\');', 112 | errors, 113 | filename: 'file.tsx', 114 | }, 115 | { 116 | code: 'const ava = require(\'ava\');', 117 | errors, 118 | filename: 'file.tsx', 119 | }, 120 | { 121 | code: 'const a = 1, ava = require(\'ava\'), b = 2;', 122 | errors, 123 | filename: 'file.tsx', 124 | }, 125 | { 126 | code: 'import ava from \'ava\';', 127 | errors, 128 | filename: 'file.tsx', 129 | }, 130 | ], 131 | }; 132 | 133 | const typescriptTestCases = { 134 | valid: [ 135 | {code: 'import type {Macro} from \'ava\';', filename: 'file.ts'}, 136 | {code: 'import type {Macro} from \'ava\';', filename: 'file.tsx'}, 137 | ], 138 | invalid: [], 139 | }; 140 | 141 | ruleTester.run('use-test', rule, commonTestCases); 142 | typescriptRuleTester.run('use-test', rule, commonTestCases); 143 | typescriptRuleTester.run('use-test', rule, typescriptTestCases); 144 | -------------------------------------------------------------------------------- /test/use-true-false.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const avaRuleTester = require('eslint-ava-rule-tester'); 5 | const rule = require('../rules/use-true-false'); 6 | 7 | const ruleTester = avaRuleTester(test, { 8 | env: { 9 | es6: true, 10 | }, 11 | }); 12 | 13 | const trueErrors = [{ 14 | message: '`t.true()` should be used instead of `t.truthy()`.', 15 | }]; 16 | 17 | const falseErrors = [{ 18 | message: '`t.false()` should be used instead of `t.falsy()`.', 19 | }]; 20 | 21 | const header = 'const test = require(\'ava\');\n'; 22 | 23 | function testCase(contents, prependHeader) { 24 | const content = `test(t => { ${contents} });`; 25 | 26 | if (prependHeader !== false) { 27 | return header + content; 28 | } 29 | 30 | return content; 31 | } 32 | 33 | ruleTester.run('use-true-false', rule, { 34 | valid: [ 35 | testCase('t.true(true)'), 36 | testCase('t.true(false)'), 37 | testCase('t.true(value == 1)'), 38 | testCase('t.true(value === 1)'), 39 | testCase('t.true(value != 1)'), 40 | testCase('t.true(value !== 1)'), 41 | testCase('t.true(value < 1)'), 42 | testCase('t.true(value <= 1)'), 43 | testCase('t.true(value > 1)'), 44 | testCase('t.true(value >= 1)'), 45 | testCase('t.true(!value)'), 46 | testCase('t.true(!!value)'), 47 | testCase('t.false(value === 1)'), 48 | testCase('t.truthy(value)'), 49 | testCase('t.truthy(value())'), 50 | testCase('t.truthy(value + value)'), 51 | testCase('t.falsy(value)'), 52 | testCase('t.falsy(value())'), 53 | testCase('t.falsy(value + value)'), 54 | testCase('t.truthy()'), 55 | testCase('t.falsy()'), 56 | testCase('t.context.truthy(true)'), 57 | testCase('t.context.falsy(false)'), 58 | testCase('foo.t.truthy(true)'), 59 | testCase('foo.t.falsy(false)'), 60 | // Shouldn't be triggered since it's not a test file 61 | testCase('t.truthy(value === 1)', false), 62 | ], 63 | invalid: [ 64 | { 65 | code: testCase('t.truthy(true)'), 66 | errors: trueErrors, 67 | }, 68 | { 69 | code: testCase('t.truthy(false)'), 70 | errors: trueErrors, 71 | }, 72 | { 73 | code: testCase('t.truthy(value == 1)'), 74 | errors: trueErrors, 75 | }, 76 | { 77 | code: testCase('t.truthy(value === 1)'), 78 | errors: trueErrors, 79 | }, 80 | { 81 | code: testCase('t.truthy(value != 1)'), 82 | errors: trueErrors, 83 | }, 84 | { 85 | code: testCase('t.truthy(value !== 1)'), 86 | errors: trueErrors, 87 | }, 88 | { 89 | code: testCase('t.truthy(value < 1)'), 90 | errors: trueErrors, 91 | }, 92 | { 93 | code: testCase('t.truthy(value <= 1)'), 94 | errors: trueErrors, 95 | }, 96 | { 97 | code: testCase('t.truthy(value > 1)'), 98 | errors: trueErrors, 99 | }, 100 | { 101 | code: testCase('t.truthy(value >= 1)'), 102 | errors: trueErrors, 103 | }, 104 | { 105 | code: testCase('t.truthy(!value)'), 106 | errors: trueErrors, 107 | }, 108 | { 109 | code: testCase('t.truthy(!!value)'), 110 | errors: trueErrors, 111 | }, 112 | { 113 | code: testCase('t.truthy(Array.isArray(value))'), 114 | errors: trueErrors, 115 | }, 116 | { 117 | code: testCase('t.truthy(isFinite(3))'), 118 | errors: trueErrors, 119 | }, 120 | { 121 | code: testCase('t.falsy(value === 1)'), 122 | errors: falseErrors, 123 | }, 124 | ], 125 | }); 126 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('ava'); 4 | const util = require('../util'); 5 | const packageJson = require('../package'); 6 | 7 | test('returns the URL of the a named rule\'s documentation', t => { 8 | const url = `https://github.com/avajs/eslint-plugin-ava/blob/v${packageJson.version}/docs/rules/foo.md`; 9 | t.is(util.getDocsUrl('foo.js'), url); 10 | }); 11 | 12 | test('returns the URL of the a named rule\'s documentation at a commit hash', t => { 13 | const url = 'https://github.com/avajs/eslint-plugin-ava/blob/bar/docs/rules/foo.md'; 14 | t.is(util.getDocsUrl('foo.js', 'bar'), url); 15 | }); 16 | 17 | test('determines the rule name from the file', t => { 18 | const url = `https://github.com/avajs/eslint-plugin-ava/blob/v${packageJson.version}/docs/rules/util.md`; 19 | t.is(util.getDocsUrl(__filename), url); 20 | }); 21 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('node:path'); 4 | const pkgDir = require('pkg-dir'); 5 | const resolveFrom = require('resolve-from'); 6 | const pkg = require('./package'); 7 | 8 | exports.loadAvaHelper = (filename, overrides) => { 9 | const rootDirectory = pkgDir.sync(filename); 10 | if (!rootDirectory) { 11 | return undefined; 12 | } 13 | 14 | const avaHelperPath = resolveFrom.silent(rootDirectory, 'ava/eslint-plugin-helper'); 15 | if (!avaHelperPath) { 16 | return undefined; 17 | } 18 | 19 | const avaHelper = require(avaHelperPath); 20 | return avaHelper.load(rootDirectory, overrides); 21 | }; 22 | 23 | const functionExpressions = new Set([ 24 | 'FunctionExpression', 25 | 'ArrowFunctionExpression', 26 | ]); 27 | 28 | exports.getRootNode = node => { 29 | if (node.object.type === 'MemberExpression') { 30 | return exports.getRootNode(node.object); 31 | } 32 | 33 | return node; 34 | }; 35 | 36 | exports.getNameOfRootNodeObject = node => exports.getRootNode(node).object.name; 37 | 38 | exports.isPropertyUnderContext = node => exports.getRootNode(node).property.name === 'context'; 39 | 40 | exports.isFunctionExpression = node => node && functionExpressions.has(node.type); 41 | 42 | function getTestModifiers(node) { 43 | if (node.type === 'CallExpression') { 44 | return getTestModifiers(node.callee); 45 | } 46 | 47 | if (node.type === 'MemberExpression') { 48 | return [...getTestModifiers(node.object), node.property]; 49 | } 50 | 51 | return []; 52 | } 53 | 54 | exports.getTestModifiers = getTestModifiers; 55 | 56 | exports.getTestModifier = (node, module_) => getTestModifiers(node).find(property => property.name === module_); 57 | 58 | exports.removeTestModifier = parameters => { 59 | const modifier = parameters.modifier.trim(); 60 | const range = [...exports.getTestModifier(parameters.node, modifier).range]; 61 | const replacementRegExp = new RegExp(`\\.|${modifier}`, 'g'); 62 | const source = parameters.context.getSourceCode().getText(); 63 | let dotPosition = range[0] - 1; 64 | while (source.charAt(dotPosition) !== '.') { 65 | dotPosition -= 1; 66 | } 67 | 68 | let snippet = source.slice(dotPosition, range[1]); 69 | snippet = snippet.replace(replacementRegExp, ''); 70 | return [[dotPosition, range[1]], snippet]; 71 | }; 72 | 73 | const getMembers = node => { 74 | const {name} = node.property; 75 | 76 | if (node.object.type === 'MemberExpression') { 77 | return [...getMembers(node.object), name]; 78 | } 79 | 80 | return [name]; 81 | }; 82 | 83 | exports.getMembers = getMembers; 84 | 85 | const repoUrl = 'https://github.com/avajs/eslint-plugin-ava'; 86 | 87 | const getDocumentationUrl = (filename, commitHash = `v${pkg.version}`) => { 88 | const ruleName = path.basename(filename, '.js'); 89 | return `${repoUrl}/blob/${commitHash}/docs/rules/${ruleName}.md`; 90 | }; 91 | 92 | exports.getDocsUrl = getDocumentationUrl; 93 | 94 | const assertionMethodsNumberArguments = new Map([ 95 | ['assert', 1], 96 | ['deepEqual', 2], 97 | ['fail', 0], 98 | ['false', 1], 99 | ['falsy', 1], 100 | ['ifError', 1], 101 | ['is', 2], 102 | ['like', 2], 103 | ['not', 2], 104 | ['notDeepEqual', 2], 105 | ['notRegex', 2], 106 | ['notThrows', 1], 107 | ['notThrowsAsync', 1], 108 | ['pass', 0], 109 | ['regex', 2], 110 | ['snapshot', 1], 111 | ['throws', 1], 112 | ['throwsAsync', 1], 113 | ['true', 1], 114 | ['truthy', 1], 115 | ['try', 1], 116 | ]); 117 | 118 | const assertionMethodNames = [...assertionMethodsNumberArguments.keys()]; 119 | 120 | exports.assertionMethodsNumArguments = assertionMethodsNumberArguments; 121 | exports.assertionMethods = new Set(assertionMethodNames); 122 | exports.executionMethods = new Set([...assertionMethodNames, 'end', 'plan', 'log', 'teardown', 'timeout']); 123 | --------------------------------------------------------------------------------