eslint-plugin-test-governance 0.3.1 → 0.5.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 +114 -1
- package/README.md +202 -83
- package/dist/index.d.ts +12 -11
- package/dist/index.js +28 -14
- package/dist/rules/marker-matches-state.d.ts +0 -1
- package/dist/rules/marker-matches-state.js +4 -4
- package/dist/rules/no-conflicting-states.d.ts +0 -1
- package/dist/rules/no-conflicting-states.js +17 -19
- package/dist/rules/no-orphaned-marker.d.ts +0 -1
- package/dist/rules/no-orphaned-marker.js +12 -7
- package/dist/rules/require-ticket-in-comments.d.ts +0 -1
- package/dist/rules/require-ticket-in-comments.js +8 -13
- package/dist/rules/require-ticket.d.ts +1 -2
- package/dist/rules/require-ticket.js +22 -10
- package/dist/utils/analyze.d.ts +29 -12
- package/dist/utils/analyze.js +526 -181
- package/dist/utils/constants.d.ts +5 -3
- package/dist/utils/constants.js +4 -3
- package/dist/utils/create-rule.d.ts +7 -8
- package/dist/utils/create-rule.js +3 -9
- package/dist/utils/distance.d.ts +0 -1
- package/dist/utils/distance.js +0 -1
- package/dist/utils/errors.d.ts +0 -1
- package/dist/utils/errors.js +0 -1
- package/dist/utils/fix.d.ts +0 -3
- package/dist/utils/fix.js +0 -8
- package/dist/utils/frameworks.d.ts +68 -0
- package/dist/utils/frameworks.js +108 -0
- package/dist/utils/options.d.ts +33 -12
- package/dist/utils/options.js +9 -5
- package/dist/utils/schema.d.ts +0 -1
- package/dist/utils/schema.js +9 -3
- package/dist/utils/tickets.d.ts +0 -1
- package/dist/utils/tickets.js +3 -3
- package/dist/utils/validate.d.ts +4 -1
- package/dist/utils/validate.js +9 -4
- package/docs/rules/marker-matches-state.md +19 -11
- package/docs/rules/no-conflicting-states.md +18 -10
- package/docs/rules/no-orphaned-marker.md +59 -18
- package/docs/rules/require-ticket-in-comments.md +27 -23
- package/docs/rules/require-ticket.md +135 -49
- package/docs/upgrading.md +131 -0
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,117 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-10-08
|
|
10
|
+
|
|
11
|
+
This release changes how the plugin is configured, ahead of 1.0.
|
|
12
|
+
[Upgrading to 0.5](https://github.com/Huldoser/eslint-plugin-test-governance/blob/main/docs/upgrading.md#upgrading-to-05)
|
|
13
|
+
shows what to change, with a before and after for each one.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Breaking:** `configure()` needs a `framework`, as in
|
|
18
|
+
`configure({ framework: 'playwright', ticket: { preset: 'jira' } })`, and so does
|
|
19
|
+
`settings['test-governance']` in a config you build yourself. Before, a config without one checked
|
|
20
|
+
files as Playwright tests. Now `configure()` fails when the config file loads, and hand-written
|
|
21
|
+
settings fail on the first lint, with a message that names the three frameworks. To share options
|
|
22
|
+
between frameworks, pass the same options to a `configure()` for each one.
|
|
23
|
+
- **Breaking:** `reportDynamicTitles` is renamed to `reportUnreadableTags`, because it also reports
|
|
24
|
+
tags and details objects imported from another module, not only titles. It works the same way, and
|
|
25
|
+
a config that still uses the old name fails with a message that names the new one.
|
|
26
|
+
- Vitest's `skipIf(true)` and `runIf(false)` always skip, so they need a ticket, like `{ skip: true }`
|
|
27
|
+
and Playwright's `test.skip(true)`. `skipIf(false)` and `runIf(true)` never skip, so a `// SKIP:`
|
|
28
|
+
marker above one is reported as left over. Any other condition is still a conditional skip. Before,
|
|
29
|
+
a literal condition was treated like any other.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- **Breaking:** `configs.recommended` is gone. It checked every file as Playwright tests, so pick the
|
|
34
|
+
config for your framework instead: `configs.playwright`, `configs.jest` or `configs.vitest`. For
|
|
35
|
+
Playwright, replace `configs.recommended` with `configs.playwright`, and
|
|
36
|
+
`extends: ['test-governance/recommended']` with `extends: ['test-governance/playwright']`. The
|
|
37
|
+
rules and their severities are the same. A config that still reads `configs.recommended` fails when
|
|
38
|
+
it loads, with a message that points to the upgrade guide, rather than spreading nothing and turning
|
|
39
|
+
the rules off.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Tags kept in a `const` or an enum in the same file count, as in `{ tag: TAGS.NEW }` after
|
|
44
|
+
`const TAGS = { NEW: '@new' } as const`, with spreads, `as const` and `satisfies`, and so do titles
|
|
45
|
+
and details objects kept in a constant. Before, a test tagged this way needed no ticket, and a
|
|
46
|
+
correct `// NEW:` marker above it was reported as left over.
|
|
47
|
+
- Tags and details imported from another module can't be read, so a marker for a tag state above such
|
|
48
|
+
a test is no longer reported as left over, and neither is a Vitest state marker such as `// SKIP:`
|
|
49
|
+
above a test whose options object is imported. With `reportUnreadableTags: true`, the unreadable
|
|
50
|
+
tags are reported, like titles that aren't static text.
|
|
51
|
+
- Vitest tag names, which are written without the `@` (`{ tags: ['flaky'] }`), match the states:
|
|
52
|
+
`'new'` is `@new`, and a custom state with `when: '@flaky'` matches `'flaky'`. Before, they never
|
|
53
|
+
matched. `no-conflicting-states` shows and fixes them without the `@`.
|
|
54
|
+
- `context.skip()` in Vitest's `beforeEach`, global or imported, needs a ticket, like the one in
|
|
55
|
+
`test.beforeEach`. Before, it wasn't checked.
|
|
56
|
+
- A marker in the wrong case with a ticket, such as `// skip: TRADE-1`, is reported by
|
|
57
|
+
`no-orphaned-marker` once the test is no longer skipped, and by `marker-matches-state` above a test in
|
|
58
|
+
another state. Before, it was only reported while the test was still in that state.
|
|
59
|
+
|
|
60
|
+
## [0.4.0] - 2026-10-08
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- Jest and Vitest support. `configs.jest` and `configs.vitest`, or `configure({ framework })`, check
|
|
65
|
+
those frameworks' tests with the same rules and markers: `test.skip`, `it.skip`, `describe.skip`,
|
|
66
|
+
Jest's `xit`, `xtest` and `xdescribe`, Vitest's `{ skip: true }` and `context.skip()`, and their
|
|
67
|
+
`.each` tables, with globals or imports from `@jest/globals` and `vitest`. Vitest's `skipIf()`,
|
|
68
|
+
`runIf()` and `{ skip: condition }` are conditional skips, so they need no ticket by default.
|
|
69
|
+
Point each config at its framework's files with `files`; Playwright files keep using
|
|
70
|
+
`recommended` or `configs.playwright`.
|
|
71
|
+
- A `todo` state for Jest's and Vitest's `test.todo`, with the `// TODO:` marker, on by default. A
|
|
72
|
+
`// TODO: TRADE-123` above a test that isn't a todo is left alone as a tracked work comment. Jest's
|
|
73
|
+
`test.failing` and Vitest's `test.fails` use the `fail` state, which stays off by default.
|
|
74
|
+
- Skipped steps need a ticket, like skipped tests: `test.step.skip('title', fn)` and `step.skip()`
|
|
75
|
+
on the `TestStepInfo` a step's body receives. The marker goes above the call, above the step or
|
|
76
|
+
above a skipped test around it. A marker above a step that isn't skipped is reported as left over.
|
|
77
|
+
Before, skipped steps were not checked.
|
|
78
|
+
|
|
79
|
+
### Changed
|
|
80
|
+
|
|
81
|
+
- `#123`, `#1234` and `#12345` are real tickets now, not placeholders, since young repositories
|
|
82
|
+
have issues with small numbers. Add them to `placeholders` to keep rejecting them. `TODO`, `TBD`,
|
|
83
|
+
`XXX-*` and tickets numbered 0 are still rejected.
|
|
84
|
+
|
|
85
|
+
### Fixed
|
|
86
|
+
|
|
87
|
+
- The type declarations load in TypeScript 5.0 to 5.5. Before, they included a string export name
|
|
88
|
+
that only TypeScript 5.6 and later can parse, so older versions failed on the plugin's types even
|
|
89
|
+
with `skipLibCheck`.
|
|
90
|
+
- Tests inside a node type that a custom parser gives no visitor keys for are now checked. ESLint
|
|
91
|
+
reads such nodes' children from the node itself, and the rules now do the same. Before, everything
|
|
92
|
+
inside such a node was skipped.
|
|
93
|
+
- Test files that ESLint parses as CommonJS, such as `.cjs` files, are now checked. Before, their
|
|
94
|
+
top-level variables were taken for local helpers, so a `test` from `require('@playwright/test')`
|
|
95
|
+
was ignored by every rule.
|
|
96
|
+
- The `jira` preset accepts issue URLs on Jira Server and Data Center sites that run under a path,
|
|
97
|
+
such as `https://issues.example.com/jira/browse/TRADE-123`. Before, only `/browse/...` right
|
|
98
|
+
after the host was accepted.
|
|
99
|
+
- A plain alias of a Playwright test, as in `const orderTest = base`, is now followed like
|
|
100
|
+
`base.extend()` and `mergeTests()` are. Before, tests declared through it were not checked.
|
|
101
|
+
- A quoted key in a destructured `require()` is read like a plain one. Before,
|
|
102
|
+
`const { 'test': test } = require('node:test')` still counted as Playwright's `test`, so its
|
|
103
|
+
skipped tests were reported, and `const { 'test': t } = require('@playwright/test')` was not
|
|
104
|
+
followed.
|
|
105
|
+
- Tests written through the whole Playwright module, as in `import * as pw from '@playwright/test'`
|
|
106
|
+
or `const pw = require('@playwright/test')` followed by `pw.test.skip(...)`, are now checked,
|
|
107
|
+
including `pw.test.extend()` and `pw.mergeTests()`. Before, they were ignored.
|
|
108
|
+
- The `no-conflicting-states` fix for a tag in the wrong case changes only that tag. Before, an
|
|
109
|
+
escape earlier in the title could make it change another one: in `'@New\u002Dbeta @New'` it
|
|
110
|
+
rewrote the start of `@New-beta` and left `@New` as it was. A tag after an escape in its title is
|
|
111
|
+
still reported, without a fix.
|
|
112
|
+
- Block comments with lone CR line breaks, or with the Unicode line and paragraph separators
|
|
113
|
+
(U+2028, U+2029), are read line by line. Before, only LF and CRLF ended a line, so a marker or a
|
|
114
|
+
`TODO` after the first line of such a comment was missed.
|
|
115
|
+
- A marker with only punctuation after the colon, such as `// SKIP: .`, is reported as missing its
|
|
116
|
+
ticket. Before, the message was `'' is not a valid ticket`.
|
|
117
|
+
- A regex passed where an option expects a string is shown as written in the error, as in
|
|
118
|
+
`ticket.pattern must be a string (got /TRADE-\d+/)`. Before, it was shown as `{}`.
|
|
119
|
+
|
|
9
120
|
## [0.3.1] - 2026-10-06
|
|
10
121
|
|
|
11
122
|
### Fixed
|
|
@@ -134,7 +245,9 @@ First release.
|
|
|
134
245
|
- Support for ESLint 9 and 10 flat config, JavaScript and TypeScript test files, `.extend()`
|
|
135
246
|
fixtures, describe blocks, runtime skips and tags in titles or the `tag` option.
|
|
136
247
|
|
|
137
|
-
[Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.
|
|
248
|
+
[Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.5.0...HEAD
|
|
249
|
+
[0.5.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.4.0...v0.5.0
|
|
250
|
+
[0.4.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.3.1...v0.4.0
|
|
138
251
|
[0.3.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.3.0...v0.3.1
|
|
139
252
|
[0.3.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.2.1...v0.3.0
|
|
140
253
|
[0.2.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.2.0...v0.2.1
|
package/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# eslint-plugin-test-governance
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/eslint-plugin-test-governance)
|
|
4
|
+
[](https://github.com/Huldoser/eslint-plugin-test-governance/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
ESLint rules that keep a Playwright, Jest or Vitest suite honest. Every skipped, fixme, todo, `@new`
|
|
8
|
+
or `@unstable` test must point at the ticket that tracks it:
|
|
9
|
+
|
|
10
|
+
<!-- example: valid -->
|
|
5
11
|
|
|
6
12
|
```ts
|
|
7
|
-
// SKIP:
|
|
8
|
-
test.skip('
|
|
13
|
+
// SKIP: TRADE-123
|
|
14
|
+
test.skip('rejects a margin order above the buying power', async ({ page }) => {
|
|
9
15
|
// ...
|
|
10
16
|
});
|
|
11
17
|
```
|
|
@@ -14,7 +20,8 @@ Without the comment, `test-governance/require-ticket` fails the lint. When the t
|
|
|
14
20
|
skip removed, `no-orphaned-marker` flags the leftover comment. The markers are plain
|
|
15
21
|
comments, so `grep -rn "SKIP:" tests/` lists every disabled test and its ticket.
|
|
16
22
|
|
|
17
|
-
> **Status:** early release (0.x). Options may still change before 1.0.
|
|
23
|
+
> **Status:** early release (0.x). Options may still change before 1.0. Coming from 0.4?
|
|
24
|
+
> [Upgrading to 0.5](docs/upgrading.md#upgrading-to-05) shows what to change.
|
|
18
25
|
|
|
19
26
|
## Install
|
|
20
27
|
|
|
@@ -27,6 +34,9 @@ Requires ESLint 9 or 10 with flat config, and Node.js 22.12 or later. For TypeSc
|
|
|
27
34
|
|
|
28
35
|
## Usage
|
|
29
36
|
|
|
37
|
+
Pick the config for the framework your tests use, `configs.playwright`, `configs.jest` or
|
|
38
|
+
`configs.vitest`, and point it at those test files with `files`:
|
|
39
|
+
|
|
30
40
|
```js
|
|
31
41
|
// eslint.config.js
|
|
32
42
|
import tsParser from '@typescript-eslint/parser';
|
|
@@ -36,26 +46,50 @@ export default [
|
|
|
36
46
|
{ files: ['**/*.ts'], languageOptions: { parser: tsParser } },
|
|
37
47
|
{
|
|
38
48
|
files: ['tests/**/*.{js,ts}'],
|
|
39
|
-
...testGovernance.configs.
|
|
49
|
+
...testGovernance.configs.playwright,
|
|
40
50
|
},
|
|
41
51
|
];
|
|
42
52
|
```
|
|
43
53
|
|
|
54
|
+
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 Vitest config, `test`, `it` and `describe` belong to
|
|
56
|
+
that framework, whether they are globals or imported from `@jest/globals` or `vitest`, the way the
|
|
57
|
+
Jest and Vitest ESLint plugins are set up.
|
|
58
|
+
|
|
59
|
+
With ESLint's `defineConfig()` (ESLint 9.22 or later), put the config in `extends`:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
// eslint.config.js
|
|
63
|
+
import { defineConfig } from 'eslint/config';
|
|
64
|
+
import tsParser from '@typescript-eslint/parser';
|
|
65
|
+
import testGovernance from 'eslint-plugin-test-governance';
|
|
66
|
+
|
|
67
|
+
export default defineConfig([
|
|
68
|
+
{ files: ['**/*.ts'], languageOptions: { parser: tsParser } },
|
|
69
|
+
{
|
|
70
|
+
files: ['tests/**/*.{js,ts}'],
|
|
71
|
+
extends: [testGovernance.configs.playwright],
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
```
|
|
75
|
+
|
|
44
76
|
Any flat config file works. The package is ESM, and `require('eslint-plugin-test-governance')` in an
|
|
45
|
-
`eslint.config.cjs` returns the plugin itself.
|
|
46
|
-
`extends: ['test-governance/
|
|
47
|
-
[`jiti`](https://github.com/unjs/jiti), so install it as a dev
|
|
77
|
+
`eslint.config.cjs` returns the plugin itself. Inside `defineConfig()`, once the plugin is registered
|
|
78
|
+
under `plugins`, the string form `extends: ['test-governance/playwright']` works too. ESLint loads
|
|
79
|
+
an `eslint.config.ts` through [`jiti`](https://github.com/unjs/jiti), so install it as a dev
|
|
80
|
+
dependency too.
|
|
48
81
|
|
|
49
|
-
To change the defaults, use `configure()`. It
|
|
50
|
-
stored in `settings['test-governance']`, which all the rules read:
|
|
82
|
+
To change the defaults, use `configure()`. It takes the framework and your options, and returns that
|
|
83
|
+
framework's config with the options stored in `settings['test-governance']`, which all the rules read:
|
|
51
84
|
|
|
52
85
|
```js
|
|
53
86
|
export default [
|
|
54
87
|
{
|
|
55
88
|
files: ['tests/**/*.{js,ts}'],
|
|
56
89
|
...testGovernance.configure({
|
|
90
|
+
framework: 'playwright',
|
|
57
91
|
// Only accept Jira keys from these projects (or Jira URLs).
|
|
58
|
-
ticket: { preset: 'jira', projects: ['
|
|
92
|
+
ticket: { preset: 'jira', projects: ['TRADE', 'RISK'] },
|
|
59
93
|
// Turn on the @new and @unstable states.
|
|
60
94
|
lifecycleTags: true,
|
|
61
95
|
// Any tag your team uses, with the same rules as the built-ins.
|
|
@@ -76,84 +110,129 @@ eslint-plugin-test-governance: invalid options:
|
|
|
76
110
|
- ticket.preset must be one of "any", "jira", "github", … (got "jria")
|
|
77
111
|
```
|
|
78
112
|
|
|
113
|
+
### Frameworks
|
|
114
|
+
|
|
115
|
+
A project with tests in more than one framework gives each framework its own config, pointed at its
|
|
116
|
+
files. To share options between them, pass the same options to each `configure()`:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
const options = { ticket: { preset: 'jira', projects: ['TRADE'] } };
|
|
120
|
+
|
|
121
|
+
export default [
|
|
122
|
+
// The e2e specs run on Playwright, the unit tests on Vitest.
|
|
123
|
+
{ files: ['e2e/**/*.spec.ts'], ...testGovernance.configure({ framework: 'playwright', ...options }) },
|
|
124
|
+
{ files: ['src/**/*.test.ts'], ...testGovernance.configure({ framework: 'vitest', ...options }) },
|
|
125
|
+
];
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`configs.playwright`, `configs.jest` and `configs.vitest` set only the framework, so one listed after a
|
|
129
|
+
`configure()` for the same files keeps that config's options and changes only the framework. All three
|
|
130
|
+
turn on the same rules. What puts a test in a state in each framework:
|
|
131
|
+
|
|
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()` 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()` | | `skipIf(condition)`, `runIf(condition)`, `{ skip: condition }`, `context.skip(condition)`, `if (...) context.skip()` |
|
|
140
|
+
| tags such as `@new` (`// NEW:`) | the title, `{ tag }` | the title | the title, `{ tags }`, where `'new'` and `'@new'` both mean `@new` |
|
|
141
|
+
|
|
142
|
+
Table-driven tests count too: `test.skip.each(table)(...)`, `describe.skip.each` and Vitest's `.for`.
|
|
143
|
+
A literal condition decides on its own: `skipIf(true)` and `runIf(false)` always skip, and `skipIf(false)`,
|
|
144
|
+
`runIf(true)` and `{ skip: false }` never do.
|
|
145
|
+
Vitest writes tag names without the `@`, as in `{ tags: ['flaky'] }`, so a name in `tags` counts as the
|
|
146
|
+
tag with an `@` in front: `'flaky'` is `@flaky`, and a custom state with `when: '@flaky'` matches it.
|
|
147
|
+
|
|
79
148
|
## Rules
|
|
80
149
|
|
|
81
150
|
<!-- begin auto-generated rules list -->
|
|
82
151
|
|
|
83
152
|
💼 [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) enabled in.\
|
|
84
153
|
⚠️ [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) set to warn in.\
|
|
85
|
-
|
|
154
|
+
🃏 Set in the `jest` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
155
|
+
🎭 Set in the `playwright` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
156
|
+
⚡ Set in the `vitest` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
86
157
|
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
|
|
87
158
|
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
88
159
|
|
|
89
|
-
| Name | Description | 💼
|
|
90
|
-
| :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------- |
|
|
91
|
-
| [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state |
|
|
92
|
-
| [no-conflicting-states](docs/rules/no-conflicting-states.md) | Disallow state tags that contradict each other, differ in case or look like typos |
|
|
93
|
-
| [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state |
|
|
94
|
-
| [require-ticket](docs/rules/require-ticket.md) | Require a ticket marker comment above skipped, fixme and tagged tests |
|
|
95
|
-
| [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket |
|
|
160
|
+
| Name | Description | 💼 | ⚠️ | 🔧 | 💡 |
|
|
161
|
+
| :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :------- | :------- | :-- | :-- |
|
|
162
|
+
| [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state | 🃏 🎭 ⚡ | | | 💡 |
|
|
163
|
+
| [no-conflicting-states](docs/rules/no-conflicting-states.md) | Disallow state tags that contradict each other, differ in case or look like typos | | 🃏 🎭 ⚡ | 🔧 | |
|
|
164
|
+
| [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state | 🃏 🎭 ⚡ | | | 💡 |
|
|
165
|
+
| [require-ticket](docs/rules/require-ticket.md) | Require a ticket marker comment above skipped, fixme and tagged tests | 🃏 🎭 ⚡ | | | 💡 |
|
|
166
|
+
| [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket | 🃏 🎭 ⚡ | | | 💡 |
|
|
96
167
|
|
|
97
168
|
<!-- end auto-generated rules list -->
|
|
98
169
|
|
|
99
170
|
## Defaults
|
|
100
171
|
|
|
101
|
-
The defaults follow how most teams already work, so
|
|
102
|
-
|
|
103
|
-
| Setting | Default
|
|
104
|
-
| --------------------------------------------------------------------------------------------------------- |
|
|
105
|
-
| States that need a ticket | `skip`, `fixme`
|
|
106
|
-
| `@new`, `@unstable` | off; `lifecycleTags: true` turns both on
|
|
107
|
-
| Conditional skips (`test.skip(browserName === 'webkit', ...)`, `if (...) test.skip()`, a skip in `catch`) | no ticket needed
|
|
108
|
-
| `test.fail`, `test.slow`
|
|
109
|
-
| Marker format | `// SKIP:
|
|
110
|
-
| Notes after the ticket | not allowed; `allowNotes: true` allows them
|
|
111
|
-
| `FIXME` / `TODO` comments | must start with a ticket, e.g. `// TODO:
|
|
112
|
-
| Placement | comment block directly above; other comments allowed; a blank line breaks it
|
|
113
|
-
| Ticket format | `any`: `PROJ-123`, `#123`, `owner/repo#123`, or an http(s) URL
|
|
114
|
-
| Placeholder tickets | rejected: `TODO`, `TBD`, `XXX-*`,
|
|
115
|
-
| Describe blocks | a marker on a skipped or tagged describe covers its tests
|
|
116
|
-
| Severity | `
|
|
172
|
+
The defaults follow how most teams already work, so each framework config is useful with no options.
|
|
173
|
+
|
|
174
|
+
| Setting | Default | Why |
|
|
175
|
+
| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
176
|
+
| States that need a ticket | `skip`, `fixme`; in Jest and Vitest `skip`, `todo` | Every team skips tests. Chromium DevTools, GitLab and Slack all require a bug link on disabled tests. |
|
|
177
|
+
| `@new`, `@unstable` | off; `lifecycleTags: true` turns both on | A promotion workflow is not universal. Teams that call it `@flaky` or `@quarantine` use `customStates`. |
|
|
178
|
+
| 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. |
|
|
179
|
+
| `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 }`. |
|
|
180
|
+
| Marker format | `// SKIP: TRADE-123` or `// SKIP: TRADE-123, TRADE-124` | Uppercase keyword and colon, like `TODO:`. Easy to grep. |
|
|
181
|
+
| Notes after the ticket | not allowed; `allowNotes: true` allows them | The ticket is the source of truth. A note in a comment goes stale while the ticket stays current. |
|
|
182
|
+
| `FIXME` / `TODO` comments | must start with a ticket, e.g. `// TODO: TRADE-123` or `// TODO(TRADE-123)` | The ticket is the source of truth; an untracked `TODO` is never done. `comments: false` turns this off. |
|
|
183
|
+
| Placement | comment block directly above; other comments allowed; a blank line breaks it | Survives `eslint-disable-next-line` and Prettier. |
|
|
184
|
+
| Ticket format | `any`: `PROJ-123`, `#123`, `owner/repo#123`, or an http(s) URL | Works for Jira, Linear, GitHub and GitLab out of the box. Narrow it with a preset. |
|
|
185
|
+
| Placeholder tickets | rejected: `TODO`, `TBD`, `XXX-*`, and any ticket numbered 0 (`PROJ-0`, `#0`) | A fake ticket is worse than none, and no tracker issues number 0. |
|
|
186
|
+
| Describe blocks | a marker on a skipped or tagged describe covers its tests | One ticket per skipped suite. |
|
|
187
|
+
| Severity | `no-conflicting-states`: warn; every other rule: error | A marker must always match a real state, so a missing, wrong or leftover marker fails the lint. Tag spelling is cleanup. |
|
|
117
188
|
|
|
118
189
|
## Options
|
|
119
190
|
|
|
120
191
|
All options go in `configure({...})`, or in `settings['test-governance']` if you build the config
|
|
121
|
-
yourself.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
192
|
+
yourself. `framework` is required, and every other option has a default. The rules have no options
|
|
193
|
+
of their own: they all read these shared settings, so they always agree on ticket formats, states
|
|
194
|
+
and markers. To turn a rule off, set it to `'off'` as usual.
|
|
195
|
+
|
|
196
|
+
| Option | Type | Default | Description |
|
|
197
|
+
| ----------------------------- | -------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
198
|
+
| `framework` | `'playwright' \| 'jest' \| 'vitest'` | required | The test framework of the files the config applies to. `configs.playwright`, `configs.jest` and `configs.vitest` set it, and `configure()` needs it. See [Frameworks](#frameworks). |
|
|
199
|
+
| `ticket` | `TicketSpec \| TicketSpec[]` | `{ preset: 'any' }` | Accepted ticket formats. An array accepts a ticket that matches any entry. See [Ticket presets](#ticket-presets). |
|
|
200
|
+
| `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. |
|
|
201
|
+
| `lifecycleTags` | `boolean` | `false` | Turns on the `new` (`@new`) and `unstable` (`@unstable`) states. |
|
|
202
|
+
| `states` | `{ [state]: boolean \| { enabled?, marker?, ticket? } }` | `skip`, `fixme` and `todo` on | Turns built-in states (`skip`, `fixme`, `fail`, `slow`, `todo`, `new`, `unstable`) on or off, renames their marker, or gives them their own ticket format. `fail` and `slow` are off by default; a setting for `new` or `unstable` here wins over `lifecycleTags`. `fixme` and `slow` exist only in Playwright and `todo` only in Jest and Vitest, so turning one on elsewhere has no effect. |
|
|
203
|
+
| `customStates` | `{ [name]: { when, marker, ticket? } }` | `{}` | Adds a state for a tag, e.g. `{ quarantine: { when: '@quarantine', marker: 'QUARANTINE' } }`. `when` is a tag and `marker` an uppercase keyword; neither can be shared with another state. |
|
|
204
|
+
| `requireTicketForConditional` | `boolean` | `false` | Require a ticket for conditional skips too: `test.skip(condition, ...)`, `if (...) test.skip()`, a skip in a `catch` block or after an early `return` or `throw`. |
|
|
205
|
+
| `allowBlankLine` | `boolean` | `false` | Let blank lines separate a marker from its test. |
|
|
206
|
+
| `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. |
|
|
207
|
+
| `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. |
|
|
208
|
+
| `testFunctions` | `string[]` | `['test']` | Names treated as the framework's `test`. Jest and Vitest default to `['test', 'it']`. Your list replaces the default one, so include those names if you still need them. See below. |
|
|
209
|
+
| `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. |
|
|
137
210
|
|
|
138
211
|
### Ticket presets
|
|
139
212
|
|
|
140
|
-
| Preset | Accepts
|
|
141
|
-
| -------------- |
|
|
142
|
-
| `any` | `PROJ-123`, `#123`, `owner/repo#123`, any http(s) URL
|
|
143
|
-
| `jira` | `
|
|
144
|
-
| `github` | `#123`, `owner/repo#123`, `https://github.com/owner/repo/issues/123` (or `/pull/`)
|
|
145
|
-
| `gitlab` | `#123`, `group/project#123`, `https://gitlab.com/group/project/-/issues/123`
|
|
146
|
-
| `linear` | `
|
|
147
|
-
| `azure-devops` | `AB#123`, `https://dev.azure.com/<org>/<project>/_workitems/edit/123`
|
|
148
|
-
| `numeric` | `4821`
|
|
149
|
-
| `pattern` | anything matching your regex, anchored at both ends
|
|
213
|
+
| Preset | Accepts | Options |
|
|
214
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
215
|
+
| `any` | `PROJ-123`, `#123`, `owner/repo#123`, any http(s) URL | |
|
|
216
|
+
| `jira` | `TRADE-123`, `https://<host>/browse/TRADE-123` (also under a path, as in `/jira/browse/TRADE-123`), board URLs with `selectedIssue=TRADE-123` | `projects`; `host` (any host by default) |
|
|
217
|
+
| `github` | `#123`, `owner/repo#123`, `https://github.com/owner/repo/issues/123` (or `/pull/`) | `host` for GitHub Enterprise (`github.com` by default) |
|
|
218
|
+
| `gitlab` | `#123`, `group/project#123`, `https://gitlab.com/group/project/-/issues/123` | `host` for self-managed (`gitlab.com` by default) |
|
|
219
|
+
| `linear` | `RISK-123`, `https://linear.app/<workspace>/issue/RISK-123/...` | `teams` |
|
|
220
|
+
| `azure-devops` | `AB#123`, `https://dev.azure.com/<org>/<project>/_workitems/edit/123` | `host` for Azure DevOps Server (`dev.azure.com` and `*.visualstudio.com` by default) |
|
|
221
|
+
| `numeric` | `4821` | `minLength` (1), `maxLength` (20) |
|
|
222
|
+
| `pattern` | anything matching your regex, anchored at both ends | `pattern`, `flags` |
|
|
223
|
+
|
|
224
|
+
`projects` (Jira) and `teams` (Linear) are keys as they appear in tickets, in uppercase: `TRADE` for
|
|
225
|
+
`TRADE-123`. Each preset takes only its own options, so `host` on the `any` preset fails with a clear
|
|
226
|
+
message instead of being ignored. A `pattern` always matches the whole ticket, so the `g` and `y`
|
|
227
|
+
flags make no difference and are ignored.
|
|
150
228
|
|
|
151
229
|
Presets combine, and each state can have its own format. For example, bugs in Jira and flaky-test
|
|
152
230
|
reports in GitHub:
|
|
153
231
|
|
|
154
232
|
```js
|
|
155
233
|
testGovernance.configure({
|
|
156
|
-
|
|
234
|
+
framework: 'playwright',
|
|
235
|
+
ticket: { preset: 'jira', projects: ['TRADE'] },
|
|
157
236
|
lifecycleTags: true,
|
|
158
237
|
states: { unstable: { ticket: { preset: 'github' } } },
|
|
159
238
|
});
|
|
@@ -164,43 +243,83 @@ so keep it free of nested quantifiers.
|
|
|
164
243
|
|
|
165
244
|
### Which functions count as `test`
|
|
166
245
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
246
|
+
In Playwright files:
|
|
247
|
+
|
|
248
|
+
- `test` imported from `@playwright/test`, `playwright/test` or a component-testing package such as
|
|
249
|
+
`@playwright/experimental-ct-react`, including the default import, aliases such as
|
|
250
|
+
`import { test as it }` or `const it = test`, and `const { test } = require('@playwright/test')`.
|
|
251
|
+
- `pw.test` on the whole module, after `import * as pw from '@playwright/test'` or
|
|
252
|
+
`const pw = require('@playwright/test')`.
|
|
170
253
|
- Fixtures made with `.extend()` or `mergeTests()` in the same file, such as
|
|
171
254
|
`const test = base.extend({...})`.
|
|
172
255
|
- Any name in `testFunctions`, for fixtures imported from your own modules. The default `['test']`
|
|
173
256
|
covers the common `import { test } from './fixtures'`.
|
|
174
257
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
258
|
+
In Jest and Vitest files:
|
|
259
|
+
|
|
260
|
+
- The globals `test`, `it` and `describe`, Jest's `xit`, `xtest`, `fit`, `xdescribe` and
|
|
261
|
+
`fdescribe`, and Vitest's `suite`, and the same names imported or required from `@jest/globals` or
|
|
262
|
+
`vitest`, including aliases and `import * as vi from 'vitest'`.
|
|
263
|
+
- Vitest fixtures made with `test.extend()` in the same file.
|
|
264
|
+
- Any name in `testFunctions`, which defaults to `['test', 'it']`.
|
|
265
|
+
|
|
266
|
+
A name imported or required from another test runner (Playwright, `vitest`, `@jest/globals`,
|
|
267
|
+
`node:test`, `bun:test`, `mocha`, `ava`, `tap`, `uvu`), or a variable set from one such as
|
|
268
|
+
`const test = require('ava')`, is never treated as the framework's `test`, and neither is a local
|
|
178
269
|
variable declared inside a function that happens to be called `test`.
|
|
179
270
|
|
|
180
271
|
### Syntax the rules understand
|
|
181
272
|
|
|
182
|
-
|
|
273
|
+
The [Frameworks](#frameworks) table lists what each framework's tests can do. In Playwright:
|
|
274
|
+
|
|
275
|
+
- `test.skip('title', fn)`, `test.fixme(...)`, `test.fail(...)`, with or without a details object,
|
|
276
|
+
and with a body defined elsewhere, as in `test.skip('places a limit order', placeLimitOrder)`.
|
|
183
277
|
- `test.describe.skip(...)` and `test.describe.fixme(...)`, including `serial` and `parallel` variants.
|
|
184
278
|
The state covers every test inside.
|
|
185
|
-
-
|
|
186
|
-
`test.
|
|
279
|
+
- Runtime calls inside a test, a describe or a hook such as `test.beforeEach`: `test.skip()`,
|
|
280
|
+
`test.skip(true)`, `test.fixme()`, `test.fail()`, `test.slow()`, the same methods on `testInfo`,
|
|
281
|
+
and `test.info().skip()`.
|
|
282
|
+
- Skipped steps: `test.step.skip('title', fn)`, and `step.skip()` on the `TestStepInfo` a step's body
|
|
283
|
+
receives. Only the step is skipped, so a marker above the step or above a skipped test around it
|
|
284
|
+
covers it.
|
|
187
285
|
- Tags in titles (string or template literal) and in `{ tag: '@new' }` / `{ tag: [...] }`, inherited
|
|
188
|
-
from enclosing describes.
|
|
286
|
+
from enclosing describes. Tags, titles and details can also come from a `const` or an enum declared
|
|
287
|
+
in the same file, as in `{ tag: TAGS.NEW }` after `const TAGS = { NEW: '@new' } as const`, including
|
|
288
|
+
arrays and spreads of them.
|
|
189
289
|
|
|
190
|
-
Not covered in this version: tests generated in loops with dynamic titles,
|
|
191
|
-
other modules
|
|
290
|
+
Not covered in this version: tests generated in loops with dynamic titles, and tags or details imported
|
|
291
|
+
from other modules (see `reportUnreadableTags`), fixtures imported from other modules under a name not in
|
|
292
|
+
`testFunctions`, Playwright annotations as markers, and other runners such as Mocha, Bun and `node:test`.
|
|
192
293
|
|
|
193
|
-
##
|
|
294
|
+
## TypeScript
|
|
194
295
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
296
|
+
The package ships its own type declarations, which work with TypeScript 5.0 or later. Besides the
|
|
297
|
+
plugin, it exports these types: `GovernanceOptions` (the options of `configure()`), `TicketSpec`,
|
|
298
|
+
`CustomState`, `StateOverride`, `FrameworkName`, `RuleName`, `ConfigName`, `FlatConfig` and
|
|
299
|
+
`TestGovernancePlugin`.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
// eslint.config.ts
|
|
303
|
+
import tsParser from '@typescript-eslint/parser';
|
|
304
|
+
import testGovernance, { type GovernanceOptions } from 'eslint-plugin-test-governance';
|
|
305
|
+
|
|
306
|
+
const options: GovernanceOptions = {
|
|
307
|
+
framework: 'playwright',
|
|
308
|
+
lifecycleTags: true,
|
|
309
|
+
ticket: { preset: 'jira', projects: ['TRADE'] },
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export default [
|
|
313
|
+
{ files: ['**/*.ts'], languageOptions: { parser: tsParser } },
|
|
314
|
+
{ files: ['tests/**/*.ts'], ...testGovernance.configure(options) },
|
|
315
|
+
];
|
|
202
316
|
```
|
|
203
317
|
|
|
318
|
+
## Contributing
|
|
319
|
+
|
|
320
|
+
Bug reports and pull requests are welcome. [CONTRIBUTING.md](CONTRIBUTING.md) explains the setup and
|
|
321
|
+
the checks every pull request runs, and [CHANGELOG.md](CHANGELOG.md) lists the changes in each release.
|
|
322
|
+
|
|
204
323
|
## License
|
|
205
324
|
|
|
206
325
|
[MIT](LICENSE)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ESLint, Rule } from 'eslint';
|
|
2
|
+
import { type FrameworkName } from './utils/constants.ts';
|
|
2
3
|
import { type GovernanceOptions } from './utils/options.ts';
|
|
4
|
+
export type { FrameworkName } from './utils/constants.ts';
|
|
3
5
|
export type { CustomState, GovernanceOptions, StateOverride } from './utils/options.ts';
|
|
4
6
|
export type { TicketSpec } from './utils/tickets.ts';
|
|
5
7
|
export type RuleName = 'require-ticket' | 'marker-matches-state' | 'no-orphaned-marker' | 'no-conflicting-states' | 'require-ticket-in-comments';
|
|
@@ -10,6 +12,11 @@ export interface FlatConfig {
|
|
|
10
12
|
rules: Record<string, 'error' | 'warn'>;
|
|
11
13
|
settings?: Record<string, unknown>;
|
|
12
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* One config per framework. `playwright`, `jest` and `vitest` turn on the same rules for that framework's
|
|
17
|
+
* tests; point each one at its test files with `files`.
|
|
18
|
+
*/
|
|
19
|
+
export type ConfigName = FrameworkName;
|
|
13
20
|
export interface TestGovernancePlugin extends ESLint.Plugin {
|
|
14
21
|
meta: {
|
|
15
22
|
name: string;
|
|
@@ -17,20 +24,14 @@ export interface TestGovernancePlugin extends ESLint.Plugin {
|
|
|
17
24
|
namespace: string;
|
|
18
25
|
};
|
|
19
26
|
rules: Record<RuleName, Rule.RuleModule>;
|
|
20
|
-
configs:
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
configure(options?: GovernanceOptions): FlatConfig;
|
|
27
|
+
configs: Record<ConfigName, FlatConfig>;
|
|
28
|
+
configure(options: GovernanceOptions): FlatConfig;
|
|
24
29
|
}
|
|
25
30
|
declare const plugin: TestGovernancePlugin;
|
|
26
31
|
/**
|
|
27
|
-
* Returns the
|
|
32
|
+
* Returns the config of `options.framework` with shared options in `settings['test-governance']`,
|
|
28
33
|
* so all rules read the same ticket formats and states.
|
|
29
34
|
*/
|
|
30
|
-
export declare function configure(options
|
|
31
|
-
export declare const configs:
|
|
32
|
-
recommended: FlatConfig;
|
|
33
|
-
};
|
|
35
|
+
export declare function configure(options: GovernanceOptions): FlatConfig;
|
|
36
|
+
export declare const configs: Record<"playwright" | "jest" | "vitest", FlatConfig>;
|
|
34
37
|
export default plugin;
|
|
35
|
-
export { plugin as 'module.exports' };
|
|
36
|
-
//# sourceMappingURL=index.d.ts.map
|