eslint-plugin-test-governance 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -1
- package/README.md +85 -41
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/rules/require-ticket-in-comments.js +5 -3
- package/dist/utils/analyze.js +176 -42
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +9 -1
- package/dist/utils/frameworks.d.ts +31 -0
- package/dist/utils/frameworks.js +128 -1
- package/dist/utils/options.d.ts +5 -5
- package/dist/utils/schema.js +3 -1
- package/docs/rules/marker-matches-state.md +1 -1
- package/docs/rules/no-conflicting-states.md +1 -1
- package/docs/rules/no-orphaned-marker.md +3 -3
- package/docs/rules/require-ticket-in-comments.md +5 -4
- package/docs/rules/require-ticket.md +60 -6
- package/docs/upgrading.md +2 -2
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,48 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.8.0] - 2026-10-22
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `node:test` support. `configs['node-test']`, or `configure({ framework: 'node-test' })`, checks tests
|
|
14
|
+
written with Node's built-in runner: `test`, `it`, `describe` and `suite` imported or required from
|
|
15
|
+
`node:test`, their `skip`, `todo` and `expectFailure` members, which `node:test` also exports by name,
|
|
16
|
+
and the same options. Node counts an option set to any value but `false` or `undefined`, so
|
|
17
|
+
`{ skip: 'broker sandbox is down' }` always skips and needs a ticket, while `{ skip: isWindows }` is a
|
|
18
|
+
conditional skip. `t.skip()` and `t.todo()` on a test's context, or `this.skip()` and `this.todo()` in a
|
|
19
|
+
`function`, count in a test, `beforeEach` and `afterEach`, and subtests declared with `t.test()` are
|
|
20
|
+
checked like tests. `expectFailure` is the `fail` state, which is off by default.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- A marker in the wrong case directly above a test in that state, as `// todo:` above `test.todo(...)`, is
|
|
25
|
+
reported once, by `require-ticket`, which asks for uppercase. `require-ticket-in-comments` reported it too.
|
|
26
|
+
|
|
27
|
+
## [0.7.0] - 2026-10-20
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Mocha, Cypress and WebdriverIO support. `configs.mocha`, `configs.cypress` and `configs.webdriverio`,
|
|
32
|
+
or `configure({ framework })`, check those frameworks' tests with the same rules and markers:
|
|
33
|
+
`it.skip`, `describe.skip`, `context.skip`, `xit`, `xdescribe` and the other `x` forms, the TDD
|
|
34
|
+
interface's `suite.skip` and `test.skip`, with globals or imports from `mocha`, and `this.skip()` in a
|
|
35
|
+
test or hook written as a `function`. Under a condition, as in `if (!broker) this.skip()`, it is a
|
|
36
|
+
conditional skip, so it needs no ticket by default. Cypress and WebdriverIO run their tests on Mocha,
|
|
37
|
+
so their configs read the same calls; `webdriverio` is for WebdriverIO's Mocha framework.
|
|
38
|
+
- A Mocha test with only a title, `it('settles at T+1')`, which Mocha runs as pending, is in the `todo`
|
|
39
|
+
state and needs a `// TODO:` marker, like Jest's `test.todo`. `it.skip('title')` with no body is a
|
|
40
|
+
skip.
|
|
41
|
+
- In Cypress files, tags from @cypress/grep's `{ tags: '@smoke' }` count like tags in the title, and
|
|
42
|
+
`{ browser: '!webkit' }`, which runs the test only in some browsers, is a conditional skip.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- A runtime skip after other code in a `try` block that has a `catch`, as in
|
|
47
|
+
`try { await stat(target); test.skip(); } catch {}`, is a conditional skip: an error before it jumps to
|
|
48
|
+
the `catch`, so the test runs. Before, it needed a ticket like an unconditional skip. A skip that starts
|
|
49
|
+
the `try` block, or one in a `try` with only `finally`, still does. This covers Mocha's `this.skip()` too.
|
|
50
|
+
|
|
9
51
|
## [0.6.2] - 2026-10-14
|
|
10
52
|
|
|
11
53
|
### Fixed
|
|
@@ -305,7 +347,9 @@ First release.
|
|
|
305
347
|
- Support for ESLint 9 and 10 flat config, JavaScript and TypeScript test files, `.extend()`
|
|
306
348
|
fixtures, describe blocks, runtime skips and tags in titles or the `tag` option.
|
|
307
349
|
|
|
308
|
-
[Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.
|
|
350
|
+
[Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.8.0...HEAD
|
|
351
|
+
[0.8.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.7.0...v0.8.0
|
|
352
|
+
[0.7.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.6.2...v0.7.0
|
|
309
353
|
[0.6.2]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.6.1...v0.6.2
|
|
310
354
|
[0.6.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.6.0...v0.6.1
|
|
311
355
|
[0.6.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.5.1...v0.6.0
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://github.com/Huldoser/eslint-plugin-test-governance/actions/workflows/ci.yml)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
ESLint rules that keep a Playwright, Jest or
|
|
7
|
+
ESLint rules that keep a Playwright, Jest, Vitest, Mocha, Cypress, WebdriverIO or `node:test` suite honest. Every skipped, fixme, todo, `@new`
|
|
8
8
|
or `@quarantined` test must point at the ticket that tracks it:
|
|
9
9
|
|
|
10
10
|
<!-- example: valid -->
|
|
@@ -34,8 +34,9 @@ Requires ESLint 9 or 10 with flat config, and Node.js 22.12 or later. For TypeSc
|
|
|
34
34
|
|
|
35
35
|
## Usage
|
|
36
36
|
|
|
37
|
-
Pick the config for the framework your tests use, `configs.playwright`, `configs.jest
|
|
38
|
-
`configs.vitest`,
|
|
37
|
+
Pick the config for the framework your tests use, `configs.playwright`, `configs.jest`,
|
|
38
|
+
`configs.vitest`, `configs.mocha`, `configs.cypress`, `configs.webdriverio` or `configs['node-test']` for
|
|
39
|
+
Node's built-in runner, and point it at those test files with `files`:
|
|
39
40
|
|
|
40
41
|
```js
|
|
41
42
|
// eslint.config.js
|
|
@@ -52,9 +53,16 @@ export default [
|
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
There is no default config. Each framework declares, skips and tags tests its own way, so the rules
|
|
55
|
-
need to know which one a file uses. In a Jest or
|
|
56
|
-
that framework, whether they are globals or imported from `@jest/globals` or `
|
|
57
|
-
Jest and
|
|
56
|
+
need to know which one a file uses. In a Jest, Vitest or Mocha config, `test`, `it` and `describe` belong
|
|
57
|
+
to that framework, whether they are globals or imported from `@jest/globals`, `vitest` or `mocha`, the
|
|
58
|
+
way the Jest, Vitest and Mocha ESLint plugins are set up. `node:test` has no globals, so in its config
|
|
59
|
+
they are the names imported or required from `node:test`, and `test` and `it` from `testFunctions`.
|
|
60
|
+
|
|
61
|
+
Cypress and WebdriverIO run their tests on Mocha, so `configs.cypress` and `configs.webdriverio` read the
|
|
62
|
+
same calls as `configs.mocha`. `webdriverio` means WebdriverIO's Mocha framework; its Jasmine framework
|
|
63
|
+
isn't supported yet. Puppeteer has no test functions of its own, so use the config of the runner its
|
|
64
|
+
tests run on: `configs.jest` for jest-puppeteer, or `configs.vitest`, `configs.mocha` or
|
|
65
|
+
`configs['node-test']`.
|
|
58
66
|
|
|
59
67
|
With ESLint's `defineConfig()` (ESLint 9.22 or later), put the config in `extends`:
|
|
60
68
|
|
|
@@ -125,28 +133,40 @@ export default [
|
|
|
125
133
|
];
|
|
126
134
|
```
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
`configure()` for the same files keeps that config's options and changes only the framework.
|
|
136
|
+
Each framework's config, such as `configs.playwright`, sets only the framework, so one listed after a
|
|
137
|
+
`configure()` for the same files keeps that config's options and changes only the framework. They all
|
|
130
138
|
turn on the same rules. What puts a test in a state in each framework:
|
|
131
139
|
|
|
132
|
-
| State (marker) | Playwright | Jest | Vitest |
|
|
133
|
-
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
134
|
-
| `skip` (`// SKIP:`) | `test.skip(...)`, `test.describe.skip`, `test.skip()` in a body, `testInfo.skip()`, `test.step.skip`, `step.skip()` | `test.skip`, `it.skip`, `describe.skip`, `xit`, `xtest`, `xdescribe` | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skipIf(true)`, `runIf(false)`, `{ skip: true }`, `context.skip()`, `context.skip('note')` or `skip()` in a body or in `beforeEach` |
|
|
135
|
-
| `todo` (`// TODO:`) | | `test.todo`, `it.todo` | `test.todo`, `describe.todo`, `{ todo: true }` |
|
|
136
|
-
| `fixme` (`// FIXME:`) | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | | |
|
|
137
|
-
| `fail` (`// FAIL:`), off by default | `test.fail(...)`, `testInfo.fail()` | `test.failing` | `test.fails`, `{ fails: true }` |
|
|
138
|
-
| `slow` (`// SLOW:`), off by default | `test.slow()`, `testInfo.slow()` | | |
|
|
139
|
-
| conditional skip, no ticket by default | `test.skip(condition)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)` | `(condition ? it.skip : it)(...)` | `skipIf(condition)`, `runIf(condition)`, `{ skip: condition }`, `context.skip(condition)`, `if (...) context.skip()`, `(condition ? it.skip : it)(...)` |
|
|
140
|
-
| tags such as `@new` (`// NEW:`) | the title, `{ tag }` | the title | the title, `{ tags }`, where `'new'` and `'@new'` both mean `@new` |
|
|
140
|
+
| State (marker) | Playwright | Jest | Vitest | Mocha | Cypress | WebdriverIO | node-test |
|
|
141
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `skip` (`// SKIP:`) | `test.skip(...)`, `test.describe.skip`, `test.skip()` in a body, `testInfo.skip()`, `test.step.skip`, `step.skip()` | `test.skip`, `it.skip`, `describe.skip`, `xit`, `xtest`, `xdescribe` | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skipIf(true)`, `runIf(false)`, `{ skip: true }`, `context.skip()`, `context.skip('note')` or `skip()` in a body or in `beforeEach` | `it.skip`, `specify.skip`, `describe.skip`, `context.skip`, `xit`, `xspecify`, `xdescribe`, `xcontext`, TDD `test.skip` and `suite.skip`, `this.skip()` in a test or hook | as Mocha | as Mocha | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skip()`, `{ skip: true }` or `{ skip: 'reason' }`, `t.skip()`, `t.skip('reason')` or `this.skip()` in a test, `beforeEach` or `afterEach` |
|
|
143
|
+
| `todo` (`// TODO:`) | | `test.todo`, `it.todo` | `test.todo`, `describe.todo`, `{ todo: true }` | a test with only a title, `it('title')`, which Mocha runs as pending | as Mocha | as Mocha | `test.todo`, `it.todo`, `describe.todo`, `suite.todo`, `todo()`, `{ todo: true }` or `{ todo: 'reason' }`, `t.todo()`, `this.todo()` |
|
|
144
|
+
| `fixme` (`// FIXME:`) | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | | | | | | |
|
|
145
|
+
| `fail` (`// FAIL:`), off by default | `test.fail(...)`, `testInfo.fail()` | `test.failing` | `test.fails`, `{ fails: true }` | | | | `test.expectFailure`, `expectFailure()`, `{ expectFailure }` |
|
|
146
|
+
| `slow` (`// SLOW:`), off by default | `test.slow()`, `testInfo.slow()` | | | | | | |
|
|
147
|
+
| conditional skip, no ticket by default | `test.skip(condition)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)` | `(condition ? it.skip : it)(...)` | `skipIf(condition)`, `runIf(condition)`, `{ skip: condition }`, `context.skip(condition)`, `if (...) context.skip()`, `(condition ? it.skip : it)(...)` | `if (...) this.skip()`, `(condition ? it.skip : it)(...)` | as Mocha, and `{ browser: '!webkit' }` | as Mocha | `{ skip: condition }`, `{ todo: condition }`, `if (...) t.skip()`, `(condition ? it.skip : it)(...)` |
|
|
148
|
+
| tags such as `@new` (`// NEW:`) | the title, `{ tag }` | the title | the title, `{ tags }`, where `'new'` and `'@new'` both mean `@new` | the title | the title, `{ tags }` from @cypress/grep | the title | the title |
|
|
141
149
|
|
|
142
150
|
Table-driven tests count too: `test.skip.each(table)(...)`, `describe.skip.each` and Vitest's `.for`.
|
|
143
151
|
A test or describe whose function a condition picks, as in `(isMobile ? test.skip : test)('title', fn)`,
|
|
144
152
|
is a conditional skip in every framework.
|
|
145
153
|
A literal condition decides on its own: `skipIf(true)` and `runIf(false)` always skip, and `skipIf(false)`,
|
|
146
154
|
`runIf(true)` and `{ skip: false }` never do.
|
|
147
|
-
In Vitest, `test.skip('title')` and `describe.skip('title')` with no body are skipped too.
|
|
155
|
+
In Vitest, Mocha and `node:test`, `test.skip('title')` and `describe.skip('title')` with no body are skipped too.
|
|
156
|
+
In Mocha, `this.skip()` counts in a test or hook written as a `function`; an arrow function gets no
|
|
157
|
+
Mocha context, so its `this` is not the test's. `this.slow(ms)` only sets Mocha's speed threshold, so
|
|
158
|
+
it isn't the `slow` state.
|
|
148
159
|
Vitest writes tag names without the `@`, as in `{ tags: ['flaky'] }`, so a name in `tags` counts as the
|
|
149
160
|
tag with an `@` in front: `'flaky'` is `@flaky`, and a custom state with `when: '@flaky'` matches it.
|
|
161
|
+
In `node:test`, an option counts with any value but `false` or `undefined`, as Node reads it, so
|
|
162
|
+
`{ skip: 'broker sandbox is down' }` always skips. `t.skip()` and `t.todo()` take a message, not a
|
|
163
|
+
condition, and in a test or hook written as a `function`, `this.skip()` and `this.todo()` do the same.
|
|
164
|
+
Subtests declared with `t.test('title', options, fn)` are checked like tests, and a test or describe can
|
|
165
|
+
leave out its title and start with its options or body, as in `test({ skip: true }, fn)`.
|
|
166
|
+
A test with no function, `test('title')`, passes in Node, so it isn't a todo. A describe's body, and
|
|
167
|
+
`before` and `after` inside it, get the suite's context, which can't skip. Node reports a test in one
|
|
168
|
+
state only, skip before todo before `expectFailure`, so `describe.skip('title', { todo: 'why' }, fn)`
|
|
169
|
+
needs only a `// SKIP:` marker.
|
|
150
170
|
|
|
151
171
|
## Rules
|
|
152
172
|
|
|
@@ -154,19 +174,23 @@ tag with an `@` in front: `'flaky'` is `@flaky`, and a custom state with `when:
|
|
|
154
174
|
|
|
155
175
|
💼 [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) enabled in.\
|
|
156
176
|
⚠️ [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) set to warn in.\
|
|
177
|
+
🌲 Set in the `cypress` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
157
178
|
🃏 Set in the `jest` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
179
|
+
☕ Set in the `mocha` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
180
|
+
🟢 Set in the `node-test` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
158
181
|
🎭 Set in the `playwright` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
159
182
|
⚡ Set in the `vitest` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
183
|
+
🤖 Set in the `webdriverio` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
160
184
|
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
|
|
161
185
|
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
162
186
|
|
|
163
|
-
| Name | Description | 💼
|
|
164
|
-
| :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------- |
|
|
165
|
-
| [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state | 🃏 🎭 ⚡ |
|
|
166
|
-
| [no-conflicting-states](docs/rules/no-conflicting-states.md) | Disallow state tags that contradict each other, differ in case or look like typos |
|
|
167
|
-
| [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state | 🃏 🎭 ⚡ |
|
|
168
|
-
| [require-ticket](docs/rules/require-ticket.md) | Require a ticket marker comment above skipped, fixme and tagged tests | 🃏 🎭 ⚡ |
|
|
169
|
-
| [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket | 🃏 🎭 ⚡ |
|
|
187
|
+
| Name | Description | 💼 | ⚠️ | 🔧 | 💡 |
|
|
188
|
+
| :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :------------------- | :------------------- | :-- | :-- |
|
|
189
|
+
| [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
190
|
+
| [no-conflicting-states](docs/rules/no-conflicting-states.md) | Disallow state tags that contradict each other, differ in case or look like typos | | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | 🔧 | |
|
|
191
|
+
| [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
192
|
+
| [require-ticket](docs/rules/require-ticket.md) | Require a ticket marker comment above skipped, fixme and tagged tests | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
193
|
+
| [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
170
194
|
|
|
171
195
|
<!-- end auto-generated rules list -->
|
|
172
196
|
|
|
@@ -176,7 +200,7 @@ The defaults follow how most teams already work, so each framework config is use
|
|
|
176
200
|
|
|
177
201
|
| Setting | Default | Why |
|
|
178
202
|
| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
179
|
-
| States that need a ticket | `skip`, `fixme`; in
|
|
203
|
+
| States that need a ticket | `skip`, `fixme`; in the other frameworks `skip`, `todo` | Every team skips tests. Chromium DevTools, GitLab and Slack all require a bug link on disabled tests. |
|
|
180
204
|
| `@new`, `@quarantined` | off; `lifecycleTags: true` turns both on | A promotion workflow is not universal. Teams that say `@flaky` instead [rename the tag](#tag-names). |
|
|
181
205
|
| Conditional skips (`test.skip(browserName === 'webkit', ...)`, `if (...) test.skip()`, a skip in `catch`) | no ticket needed | These are usually permanent platform limits, not bugs. `requireTicketForConditional: true` changes this. |
|
|
182
206
|
| `test.fail`, `test.slow`, Jest's `test.failing`, Vitest's `test.fails` | off | They don't remove coverage. Turn them on with `states: { fail: true, slow: true }`. |
|
|
@@ -196,20 +220,20 @@ yourself. `framework` is required, and every other option has a default. The rul
|
|
|
196
220
|
of their own: they all read these shared settings, so they always agree on ticket formats, states
|
|
197
221
|
and markers. To turn a rule off, set it to `'off'` as usual.
|
|
198
222
|
|
|
199
|
-
| Option | Type
|
|
200
|
-
| ----------------------------- |
|
|
201
|
-
| `framework` | `'playwright' \| 'jest' \| 'vitest'`
|
|
202
|
-
| `ticket` | `TicketSpec \| TicketSpec[]`
|
|
203
|
-
| `placeholders` | `string[]`
|
|
204
|
-
| `lifecycleTags` | `boolean`
|
|
205
|
-
| `states` | `{ [state]: boolean \| { enabled?, tag?, marker?, ticket? } }`
|
|
206
|
-
| `customStates` | `{ [name]: { when, marker, ticket? } }`
|
|
207
|
-
| `requireTicketForConditional` | `boolean`
|
|
208
|
-
| `allowBlankLine` | `boolean`
|
|
209
|
-
| `allowNotes` | `boolean`
|
|
210
|
-
| `comments` | `false \| { keywords?: string[] }`
|
|
211
|
-
| `testFunctions` | `string[]`
|
|
212
|
-
| `reportUnreadableTags` | `boolean`
|
|
223
|
+
| Option | Type | Default | Description |
|
|
224
|
+
| ----------------------------- | ----------------------------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
225
|
+
| `framework` | `'playwright' \| 'jest' \| 'vitest' \| 'mocha' \| 'cypress' \| 'webdriverio'` | required | The test framework of the files the config applies to. Each framework's config, such as `configs.playwright`, sets it, and `configure()` needs it. See [Frameworks](#frameworks). |
|
|
226
|
+
| `ticket` | `TicketSpec \| TicketSpec[]` | `{ preset: 'any' }` | Accepted ticket formats. An array accepts a ticket that matches any entry. See [Ticket presets](#ticket-presets). |
|
|
227
|
+
| `placeholders` | `string[]` | see above | Tickets rejected as placeholders. `*` matches any characters. Case-insensitive; a leading `#` is ignored. Your list replaces the default one, so repeat the defaults you want to keep. Tickets numbered 0 are always rejected. |
|
|
228
|
+
| `lifecycleTags` | `boolean` | `false` | Turns on the `new` (`@new`) and `quarantined` (`@quarantined`) states. |
|
|
229
|
+
| `states` | `{ [state]: boolean \| { enabled?, tag?, marker?, ticket? } }` | `skip`, `fixme` and `todo` on | Turns built-in states (`skip`, `fixme`, `fail`, `slow`, `todo`, `new`, `quarantined`) on or off, renames their tag or marker, or gives them their own ticket format. `fail` and `slow` are off by default; a setting for `new` or `quarantined` here wins over `lifecycleTags`. Only `new` and `quarantined` have a tag to rename; see [Tag names](#tag-names). `fixme` and `slow` exist only in Playwright, `todo` in every framework but Playwright, and `fail` in Playwright, Jest and Vitest, so turning one on elsewhere has no effect. |
|
|
230
|
+
| `customStates` | `{ [name]: { when, marker, ticket? } }` | `{}` | Adds a state for a tag, e.g. `{ 'needs-data': { when: '@needs-data', marker: 'NEEDS-DATA' } }`. `when` is a tag and `marker` an uppercase keyword; neither can be shared with another state. |
|
|
231
|
+
| `requireTicketForConditional` | `boolean` | `false` | Require a ticket for conditional skips too: `test.skip(condition, ...)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)`, a skip in a `catch` block, after other code in a `try` block with a `catch`, or after an early `return` or `throw`. |
|
|
232
|
+
| `allowBlankLine` | `boolean` | `false` | Let blank lines separate a marker from its test. |
|
|
233
|
+
| `allowNotes` | `boolean` | `false` | Allow free text after the tickets, e.g. `// SKIP: TRADE-123 flaky on CI`. Applies to markers and to `FIXME` / `TODO` comments. |
|
|
234
|
+
| `comments` | `false \| { keywords?: string[] }` | `{ keywords: ['FIXME', 'TODO'] }` | Comment keywords that must start with a ticket anywhere in a test file, e.g. `// TODO: TRADE-123`. Your keywords replace the default ones. `false` allows free-form comments. |
|
|
235
|
+
| `testFunctions` | `string[]` | `['test']` | Names treated as the framework's `test`. Jest and Vitest default to `['test', 'it']`, and Mocha, Cypress and WebdriverIO to `['it', 'specify', 'test']`. Your list replaces the default one, so include those names if you still need them. See below. |
|
|
236
|
+
| `reportUnreadableTags` | `boolean` | `false` | When a tag state is on, report titles that aren't static text, and tags or details that can't be read in the file, such as ones imported from another module, since their tags can't be checked. |
|
|
213
237
|
|
|
214
238
|
### Tag names
|
|
215
239
|
|
|
@@ -293,6 +317,24 @@ In Jest and Vitest files:
|
|
|
293
317
|
- Vitest fixtures made with `test.extend()` in the same file.
|
|
294
318
|
- Any name in `testFunctions`, which defaults to `['test', 'it']`.
|
|
295
319
|
|
|
320
|
+
In Mocha, Cypress and WebdriverIO files:
|
|
321
|
+
|
|
322
|
+
- The globals `it`, `specify`, `describe` and `context`, their `x` forms, the TDD names `suite` and
|
|
323
|
+
`test`, and the hooks `before`, `beforeEach`, `after`, `afterEach`, `suiteSetup`, `setup`,
|
|
324
|
+
`suiteTeardown` and `teardown`.
|
|
325
|
+
- The names imported or required from `mocha`, including aliases and `import * as mocha from 'mocha'`.
|
|
326
|
+
- Any name in `testFunctions`, which defaults to `['it', 'specify', 'test']`.
|
|
327
|
+
|
|
328
|
+
In `node:test` files:
|
|
329
|
+
|
|
330
|
+
- `test`, `it`, `describe`, `suite`, the hooks `before`, `beforeEach`, `after` and `afterEach`, and
|
|
331
|
+
the members exported by name, `skip`, `todo`, `only` and `expectFailure`, imported or required from
|
|
332
|
+
`node:test`, including aliases and `import * as nodeTest from 'node:test'`. The module is `test`
|
|
333
|
+
itself, so after `import test from 'node:test'` or `const test = require('node:test')`,
|
|
334
|
+
`test.describe(...)` is a describe.
|
|
335
|
+
- Subtests: `t.test(...)` on the context a test's body receives.
|
|
336
|
+
- Any name in `testFunctions`, which defaults to `['test', 'it']`. `node:test` has no globals.
|
|
337
|
+
|
|
296
338
|
A name imported or required from another test runner (Playwright, `vitest`, `@jest/globals`,
|
|
297
339
|
`node:test`, `bun:test`, `mocha`, `ava`, `tap`, `uvu`), or a variable set from one such as
|
|
298
340
|
`const test = require('ava')`, is never treated as the framework's `test`, and neither is a local
|
|
@@ -321,7 +363,9 @@ The [Frameworks](#frameworks) table lists what each framework's tests can do. In
|
|
|
321
363
|
Not covered in this version: tests generated in loops with dynamic titles, and tags or details imported
|
|
322
364
|
from other modules (see `reportUnreadableTags`), fixtures imported from other modules under a name not in
|
|
323
365
|
`testFunctions`, a Vitest test with only a title, which Vitest runs as a todo but which looks the same as a
|
|
324
|
-
helper called `test`, Playwright annotations as markers,
|
|
366
|
+
helper called `test`, Playwright annotations as markers, Cypress tests skipped with @cypress/skip-test's
|
|
367
|
+
`skipOn()` and `onlyOn()`, `node:test`'s experimental `tags` option and `getTestContext()`, and other
|
|
368
|
+
runners such as Jasmine and Bun.
|
|
325
369
|
|
|
326
370
|
## TypeScript
|
|
327
371
|
|
package/dist/index.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export interface FlatConfig {
|
|
|
13
13
|
settings?: Record<string, unknown>;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* One config per framework.
|
|
17
|
-
*
|
|
16
|
+
* One config per framework. Each turns on the same rules for that framework's tests; point each one at its
|
|
17
|
+
* test files with `files`.
|
|
18
18
|
*/
|
|
19
19
|
export type ConfigName = FrameworkName;
|
|
20
20
|
export interface TestGovernancePlugin extends ESLint.Plugin {
|
|
@@ -33,5 +33,5 @@ declare const plugin: TestGovernancePlugin;
|
|
|
33
33
|
* so all rules read the same ticket formats and states.
|
|
34
34
|
*/
|
|
35
35
|
export declare function configure(options: GovernanceOptions): FlatConfig;
|
|
36
|
-
export declare const configs: Record<"playwright" | "jest" | "vitest", FlatConfig>;
|
|
36
|
+
export declare const configs: Record<"playwright" | "jest" | "vitest" | "mocha" | "cypress" | "webdriverio" | "node-test", FlatConfig>;
|
|
37
37
|
export default plugin;
|
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ for (const framework of FRAMEWORK_NAMES) {
|
|
|
52
52
|
// tools that list the configs don't see it.
|
|
53
53
|
Object.defineProperty(plugin.configs, 'recommended', {
|
|
54
54
|
get() {
|
|
55
|
-
throw new ConfigError(
|
|
55
|
+
throw new ConfigError("configs.recommended was removed in 0.5.0. Use the config for your framework: configs.playwright, configs.jest, configs.vitest, configs.mocha, configs.cypress, configs.webdriverio or configs['node-test']. See https://github.com/Huldoser/eslint-plugin-test-governance/blob/main/docs/upgrading.md");
|
|
56
56
|
},
|
|
57
57
|
});
|
|
58
58
|
export const configs = plugin.configs;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appliesTo, commentLines,
|
|
1
|
+
import { appliesTo, commentLines, parseTickets } from "../utils/analyze.js";
|
|
2
2
|
import { createRule } from "../utils/create-rule.js";
|
|
3
3
|
import { compileTicketSpec } from "../utils/tickets.js";
|
|
4
4
|
/** Recognises anything shaped like a ticket, to tell a wrong-format ticket from plain prose. */
|
|
@@ -30,11 +30,13 @@ export default createRule({
|
|
|
30
30
|
return;
|
|
31
31
|
const { sourceCode } = context;
|
|
32
32
|
const matcher = options.commentTicket;
|
|
33
|
-
// A marker directly above a test in that state belongs to require-ticket
|
|
33
|
+
// A marker directly above a test in that state belongs to require-ticket, which also reports one
|
|
34
|
+
// in the wrong case.
|
|
34
35
|
const owned = new Set();
|
|
35
36
|
for (const subject of analysis.subjects) {
|
|
36
37
|
for (const marker of subject.markers) {
|
|
37
|
-
|
|
38
|
+
const state = marker.state ?? marker.caseOf;
|
|
39
|
+
if (state && appliesTo(subject, state.name))
|
|
38
40
|
owned.add(marker.comment);
|
|
39
41
|
}
|
|
40
42
|
}
|