eslint-plugin-test-governance 0.8.1 → 0.9.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 +14 -1
- package/README.md +51 -31
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/analyze.d.ts +2 -0
- package/dist/utils/analyze.js +68 -13
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -0
- package/dist/utils/frameworks.d.ts +5 -0
- package/dist/utils/frameworks.js +64 -1
- package/dist/utils/options.d.ts +1 -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 +1 -1
- package/docs/rules/require-ticket-in-comments.md +1 -1
- package/docs/rules/require-ticket.md +25 -3
- package/docs/upgrading.md +2 -2
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.0] - 2026-10-27
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Jasmine support. `configs.jasmine`, or `configure({ framework: 'jasmine' })`, checks tests run by
|
|
14
|
+
Jasmine on its own, on Karma with karma-jasmine (as Angular's karma builder sets up) or by WebdriverIO's
|
|
15
|
+
Jasmine framework: the globals `it`, `xit`, `fit`, `describe`, `xdescribe` and `fdescribe`, or the same
|
|
16
|
+
names from `jasmine-core`. `xit` and `xdescribe` skip a test, and `it('title')` with no function is a
|
|
17
|
+
todo. `pending()` and Jasmine 7's `notApplicable()` skip the running test in a test, `beforeEach` or
|
|
18
|
+
`afterEach`; like `t.skip('reason')`, `pending('reason')` needs a ticket unless a condition guards it.
|
|
19
|
+
`testFunctions` defaults to `['it']`, since Jasmine has no `test`.
|
|
20
|
+
|
|
9
21
|
## [0.8.1] - 2026-10-22
|
|
10
22
|
|
|
11
23
|
### Fixed
|
|
@@ -357,7 +369,8 @@ First release.
|
|
|
357
369
|
- Support for ESLint 9 and 10 flat config, JavaScript and TypeScript test files, `.extend()`
|
|
358
370
|
fixtures, describe blocks, runtime skips and tags in titles or the `tag` option.
|
|
359
371
|
|
|
360
|
-
[Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.
|
|
372
|
+
[Unreleased]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.9.0...HEAD
|
|
373
|
+
[0.9.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.8.1...v0.9.0
|
|
361
374
|
[0.8.1]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.8.0...v0.8.1
|
|
362
375
|
[0.8.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.7.0...v0.8.0
|
|
363
376
|
[0.7.0]: https://github.com/Huldoser/eslint-plugin-test-governance/compare/v0.6.2...v0.7.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, Vitest, Mocha, Cypress, WebdriverIO
|
|
7
|
+
ESLint rules that keep a Playwright, Jest, Vitest, Mocha, Cypress, WebdriverIO, `node:test` or Jasmine 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 -->
|
|
@@ -35,8 +35,8 @@ Requires ESLint 9 or 10 with flat config, and Node.js 22.12 or later. For TypeSc
|
|
|
35
35
|
## Usage
|
|
36
36
|
|
|
37
37
|
Pick the config for the framework your tests use, `configs.playwright`, `configs.jest`,
|
|
38
|
-
`configs.vitest`, `configs.mocha`, `configs.cypress`, `configs.webdriverio
|
|
39
|
-
Node's built-in runner
|
|
38
|
+
`configs.vitest`, `configs.mocha`, `configs.cypress`, `configs.webdriverio`, `configs['node-test']` for
|
|
39
|
+
Node's built-in runner or `configs.jasmine`, and point it at those test files with `files`:
|
|
40
40
|
|
|
41
41
|
```js
|
|
42
42
|
// eslint.config.js
|
|
@@ -53,16 +53,17 @@ export default [
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
There is no default config. Each framework declares, skips and tags tests its own way, so the rules
|
|
56
|
-
need to know which one a file uses. In a Jest, Vitest or
|
|
57
|
-
to
|
|
58
|
-
way the Jest, Vitest and Mocha ESLint plugins are set up. `node:test` has no globals, so in its config
|
|
56
|
+
need to know which one a file uses. In a Jest, Vitest, Mocha or Jasmine config, the framework's functions,
|
|
57
|
+
such as `it` and `describe`, belong to it, whether they are globals or imported from `@jest/globals`,
|
|
58
|
+
`vitest`, `mocha` or `jasmine-core`, the way the Jest, Vitest and Mocha ESLint plugins are set up. `node:test` has no globals, so in its config
|
|
59
59
|
they are the names imported or required from `node:test`, and `test` and `it` from `testFunctions`.
|
|
60
60
|
|
|
61
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
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
`configs
|
|
62
|
+
same calls as `configs.mocha`. `webdriverio` means WebdriverIO's Mocha framework; for its Jasmine
|
|
63
|
+
framework, use `configs.jasmine`. Karma runs Jasmine or Mocha tests, so use `configs.jasmine` with
|
|
64
|
+
karma-jasmine, which Angular's karma builder sets up, and `configs.mocha` with karma-mocha. Puppeteer has
|
|
65
|
+
no test functions of its own, so use the config of the runner its tests run on: `configs.jest` for
|
|
66
|
+
jest-puppeteer, or `configs.vitest`, `configs.mocha`, `configs['node-test']` or `configs.jasmine`.
|
|
66
67
|
|
|
67
68
|
With ESLint's `defineConfig()` (ESLint 9.22 or later), put the config in `extends`:
|
|
68
69
|
|
|
@@ -133,26 +134,30 @@ export default [
|
|
|
133
134
|
];
|
|
134
135
|
```
|
|
135
136
|
|
|
137
|
+
This matters most for Jasmine next to Jest or Mocha, whose `it`, `xit` and `describe` look the same:
|
|
138
|
+
only the `files` of each config tell them apart.
|
|
139
|
+
|
|
136
140
|
Each framework's config, such as `configs.playwright`, sets only the framework, so one listed after a
|
|
137
141
|
`configure()` for the same files keeps that config's options and changes only the framework. They all
|
|
138
142
|
turn on the same rules. What puts a test in a state in each framework:
|
|
139
143
|
|
|
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 |
|
|
144
|
+
| State (marker) | Playwright | Jest | Vitest | Mocha | Cypress | WebdriverIO | node-test | Jasmine |
|
|
145
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
146
|
+
| `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` | `xit`, `xdescribe`, `pending()`, `pending('reason')` or `notApplicable('reason')` in a test, `beforeEach` or `afterEach` |
|
|
147
|
+
| `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()` | a test with only a title, `it('title')`, which Jasmine reports as pending |
|
|
148
|
+
| `fixme` (`// FIXME:`) | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | | | | | | | |
|
|
149
|
+
| `fail` (`// FAIL:`), off by default | `test.fail(...)`, `testInfo.fail()` | `test.failing` | `test.fails`, `{ fails: true }` | | | | `test.expectFailure`, `expectFailure()`, `{ expectFailure }` | |
|
|
150
|
+
| `slow` (`// SLOW:`), off by default | `test.slow()`, `testInfo.slow()` | | | | | | | |
|
|
151
|
+
| 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)(...)` | `if (...) pending()`, `(condition ? xit : it)(...)` |
|
|
152
|
+
| 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 | the title |
|
|
149
153
|
|
|
150
154
|
Table-driven tests count too: `test.skip.each(table)(...)`, `describe.skip.each` and Vitest's `.for`.
|
|
151
155
|
A test or describe whose function a condition picks, as in `(isMobile ? test.skip : test)('title', fn)`,
|
|
152
156
|
is a conditional skip in every framework.
|
|
153
157
|
A literal condition decides on its own: `skipIf(true)` and `runIf(false)` always skip, and `skipIf(false)`,
|
|
154
158
|
`runIf(true)` and `{ skip: false }` never do.
|
|
155
|
-
In Vitest, Mocha and `node:test`, `test.skip('title')` and `describe.skip('title')` with no body are skipped too
|
|
159
|
+
In Vitest, Mocha and `node:test`, `test.skip('title')` and `describe.skip('title')` with no body are skipped too,
|
|
160
|
+
as is `xit('title')` in Jasmine.
|
|
156
161
|
In Mocha, `this.skip()` counts in a test or hook written as a `function`; an arrow function gets no
|
|
157
162
|
Mocha context, so its `this` is not the test's. `this.slow(ms)` only sets Mocha's speed threshold, so
|
|
158
163
|
it isn't the `slow` state.
|
|
@@ -167,6 +172,10 @@ A test with no function, `test('title')`, passes in Node, so it isn't a todo. A
|
|
|
167
172
|
`before` and `after` inside it, get the suite's context, which can't skip. Node reports a test in one
|
|
168
173
|
state only, skip before todo before `expectFailure`, so `describe.skip('title', { todo: 'why' }, fn)`
|
|
169
174
|
needs only a `// SKIP:` marker.
|
|
175
|
+
In Jasmine, `pending()` and Jasmine 7's `notApplicable()` stop the running test, so they count in a test,
|
|
176
|
+
`beforeEach` or `afterEach`; in `beforeAll`, `afterAll` or a describe's body Jasmine fails the suite
|
|
177
|
+
instead, so they are left alone there. Like `t.skip('reason')`, `pending('reason')` takes a reason, not a
|
|
178
|
+
condition. A function or variable of your own called `pending` is not Jasmine's.
|
|
170
179
|
|
|
171
180
|
## Rules
|
|
172
181
|
|
|
@@ -175,6 +184,7 @@ needs only a `// SKIP:` marker.
|
|
|
175
184
|
💼 [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) enabled in.\
|
|
176
185
|
⚠️ [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) set to warn in.\
|
|
177
186
|
🌲 Set in the `cypress` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
187
|
+
🌼 Set in the `jasmine` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
178
188
|
🃏 Set in the `jest` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
179
189
|
☕ Set in the `mocha` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
180
190
|
🟢 Set in the `node-test` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
@@ -184,13 +194,13 @@ needs only a `// SKIP:` marker.
|
|
|
184
194
|
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
|
|
185
195
|
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
186
196
|
|
|
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 | 🌲 🃏 ☕ 🟢 🎭 ⚡ 🤖 |
|
|
197
|
+
| Name | Description | 💼 | ⚠️ | 🔧 | 💡 |
|
|
198
|
+
| :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :---------------------- | :---------------------- | :-- | :-- |
|
|
199
|
+
| [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
200
|
+
| [no-conflicting-states](docs/rules/no-conflicting-states.md) | Disallow state tags that contradict each other, differ in case or look like typos | | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | 🔧 | |
|
|
201
|
+
| [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
202
|
+
| [require-ticket](docs/rules/require-ticket.md) | Require a ticket marker comment above skipped, fixme and tagged tests | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
203
|
+
| [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
194
204
|
|
|
195
205
|
<!-- end auto-generated rules list -->
|
|
196
206
|
|
|
@@ -232,7 +242,7 @@ and markers. To turn a rule off, set it to `'off'` as usual.
|
|
|
232
242
|
| `allowBlankLine` | `boolean` | `false` | Let blank lines separate a marker from its test. |
|
|
233
243
|
| `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
244
|
| `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']`,
|
|
245
|
+
| `testFunctions` | `string[]` | `['test']` | Names treated as the framework's `test`. Jest and Vitest default to `['test', 'it']`, Mocha, Cypress and WebdriverIO to `['it', 'specify', 'test']`, and Jasmine to `['it']`. Your list replaces the default one, so include those names if you still need them. See below. |
|
|
236
246
|
| `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. |
|
|
237
247
|
|
|
238
248
|
### Tag names
|
|
@@ -337,8 +347,17 @@ In `node:test` files:
|
|
|
337
347
|
- Subtests: `t.test(...)` on the context a test's body receives.
|
|
338
348
|
- Any name in `testFunctions`, which defaults to `['test', 'it']`. `node:test` has no globals.
|
|
339
349
|
|
|
350
|
+
In Jasmine files:
|
|
351
|
+
|
|
352
|
+
- The globals `it`, `xit`, `fit`, `describe`, `xdescribe`, `fdescribe`, and the hooks `beforeEach`,
|
|
353
|
+
`afterEach`, `beforeAll` and `afterAll`. With the runner's `globals: false`, Jasmine 7 takes the same
|
|
354
|
+
names from `jasmine-core`, so names imported or required from it count too, including aliases and
|
|
355
|
+
the whole module, as `core.xit` after `const core = require('jasmine-core')`.
|
|
356
|
+
- `pending()` and `notApplicable()`, as globals or imported by name.
|
|
357
|
+
- Any name in `testFunctions`, which defaults to `['it']`. Jasmine has no `test`.
|
|
358
|
+
|
|
340
359
|
A name imported or required from another test runner (Playwright, `vitest`, `@jest/globals`,
|
|
341
|
-
`node:test`, `bun:test`, `mocha`, `ava`, `tap`, `uvu`), or a variable set from one such as
|
|
360
|
+
`node:test`, `jasmine-core`, `bun:test`, `mocha`, `ava`, `tap`, `uvu`), or a variable set from one such as
|
|
342
361
|
`const test = require('ava')`, is never treated as the framework's `test`, and neither is a local
|
|
343
362
|
variable or function declared inside a function that happens to be called `test`.
|
|
344
363
|
|
|
@@ -366,8 +385,9 @@ Not covered in this version: tests generated in loops with dynamic titles, and t
|
|
|
366
385
|
from other modules (see `reportUnreadableTags`), fixtures imported from other modules under a name not in
|
|
367
386
|
`testFunctions`, a Vitest test with only a title, which Vitest runs as a todo but which looks the same as a
|
|
368
387
|
helper called `test`, Playwright annotations as markers, Cypress tests skipped with @cypress/skip-test's
|
|
369
|
-
`skipOn()` and `onlyOn()`, `node:test`'s experimental `tags` option and `getTestContext()`,
|
|
370
|
-
|
|
388
|
+
`skipOn()` and `onlyOn()`, `node:test`'s experimental `tags` option and `getTestContext()`, Jasmine's
|
|
389
|
+
`pending()` in a helper function that tests call, Jasmine 5 and 6's `require('jasmine-core').noGlobals()`,
|
|
390
|
+
and other runners such as Bun.
|
|
371
391
|
|
|
372
392
|
## TypeScript
|
|
373
393
|
|
package/dist/index.d.ts
CHANGED
|
@@ -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" | "mocha" | "cypress" | "webdriverio" | "node-test", FlatConfig>;
|
|
36
|
+
export declare const configs: Record<"playwright" | "jest" | "vitest" | "mocha" | "cypress" | "webdriverio" | "node-test" | "jasmine", 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("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
|
|
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, configs['node-test'] or configs.jasmine. 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;
|
package/dist/utils/analyze.d.ts
CHANGED
|
@@ -73,6 +73,8 @@ export interface Subject {
|
|
|
73
73
|
ownSkip: boolean;
|
|
74
74
|
markers: Marker[];
|
|
75
75
|
dynamicTitle: boolean;
|
|
76
|
+
/** For a runtime function such as Jasmine's `pending()`, its name; messages call the others by their state. */
|
|
77
|
+
functionName?: string;
|
|
76
78
|
}
|
|
77
79
|
export interface Analysis {
|
|
78
80
|
subjects: Subject[];
|
package/dist/utils/analyze.js
CHANGED
|
@@ -82,6 +82,7 @@ function collectTestNames(program, framework, configured) {
|
|
|
82
82
|
const roots = new Map(framework.globals);
|
|
83
83
|
for (const name of configured)
|
|
84
84
|
roots.set(name, TEST);
|
|
85
|
+
const runtime = new Map();
|
|
85
86
|
const modules = new Set();
|
|
86
87
|
const mergeTests = new Set();
|
|
87
88
|
/** Names bound to another test runner, such as `ava` in `import ava from 'ava'`. */
|
|
@@ -123,6 +124,12 @@ function collectTestNames(program, framework, configured) {
|
|
|
123
124
|
roots.delete(name);
|
|
124
125
|
others.add(name);
|
|
125
126
|
};
|
|
127
|
+
/** Takes `name` for the runtime function `imported`, as in `const { pending: pend } = require('jasmine-core')`. */
|
|
128
|
+
const bindRuntime = (name, imported) => {
|
|
129
|
+
const modifier = imported === undefined ? undefined : framework.runtimeFunctions.get(imported);
|
|
130
|
+
if (imported !== undefined && modifier !== undefined)
|
|
131
|
+
runtime.set(name, { name: imported, modifier });
|
|
132
|
+
};
|
|
126
133
|
for (const statement of program.body) {
|
|
127
134
|
if (statement.type === 'ImportDeclaration') {
|
|
128
135
|
const source = statement.source.value;
|
|
@@ -144,6 +151,7 @@ function collectTestNames(program, framework, configured) {
|
|
|
144
151
|
}
|
|
145
152
|
else {
|
|
146
153
|
const root = framework.exports.get(importedName(spec));
|
|
154
|
+
bindRuntime(local, importedName(spec));
|
|
147
155
|
if (root)
|
|
148
156
|
roots.set(local, root);
|
|
149
157
|
else if (importedName(spec) === 'mergeTests')
|
|
@@ -200,6 +208,7 @@ function collectTestNames(program, framework, configured) {
|
|
|
200
208
|
setOther(prop.value.name);
|
|
201
209
|
else if (framework.ownsModule(source)) {
|
|
202
210
|
const root = framework.exports.get(propertyName(prop) ?? '');
|
|
211
|
+
bindRuntime(prop.value.name, propertyName(prop));
|
|
203
212
|
if (root)
|
|
204
213
|
roots.set(prop.value.name, root);
|
|
205
214
|
}
|
|
@@ -207,7 +216,7 @@ function collectTestNames(program, framework, configured) {
|
|
|
207
216
|
}
|
|
208
217
|
}
|
|
209
218
|
}
|
|
210
|
-
return { roots, modules };
|
|
219
|
+
return { roots, modules, runtime };
|
|
211
220
|
}
|
|
212
221
|
/** The name a property key spells out: `test` in `{ test }`, `{ 'test': t }` or `{ ['test']: t }`. */
|
|
213
222
|
function propertyName(prop) {
|
|
@@ -225,6 +234,21 @@ function requireSource(node) {
|
|
|
225
234
|
const first = node.arguments.at(0);
|
|
226
235
|
return first?.type === 'Literal' && typeof first.value === 'string' ? first.value : undefined;
|
|
227
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Where the file declares what `id` refers to: `inside` a function or block, a parameter included, at the
|
|
239
|
+
* `top` level, or nowhere, for a `global`.
|
|
240
|
+
*/
|
|
241
|
+
function declaredWhere(sourceCode, id) {
|
|
242
|
+
for (let scope = sourceCode.getScope(id); scope; scope = scope.upper) {
|
|
243
|
+
const variable = scope.set.get(id.name);
|
|
244
|
+
if (!variable)
|
|
245
|
+
continue;
|
|
246
|
+
if (scope.block.type !== 'Program')
|
|
247
|
+
return 'inside';
|
|
248
|
+
return variable.defs.length > 0 ? 'top' : 'global';
|
|
249
|
+
}
|
|
250
|
+
return 'global';
|
|
251
|
+
}
|
|
228
252
|
/**
|
|
229
253
|
* Whether `id` refers to a variable or function declared inside a function or block, such as a local
|
|
230
254
|
* helper that happens to be called `test`. Top-level variables, imports, parameters and globals still count.
|
|
@@ -816,14 +840,31 @@ function runAnalysis(sourceCode, options) {
|
|
|
816
840
|
if (body?.type === 'FunctionExpression')
|
|
817
841
|
thisBodies.add(body);
|
|
818
842
|
};
|
|
819
|
-
/**
|
|
843
|
+
/**
|
|
844
|
+
* What a body can change the test's state with: Playwright's `testInfo`, Vitest's test context, or
|
|
845
|
+
* Jasmine's `pending()`.
|
|
846
|
+
*/
|
|
820
847
|
const bodyInfos = (args, path) => {
|
|
821
848
|
const body = args.findLast(isFunction);
|
|
822
|
-
|
|
823
|
-
if (!framework.info || !body || path.includes('each()'))
|
|
849
|
+
if (!body)
|
|
824
850
|
return [];
|
|
851
|
+
const functions = [
|
|
852
|
+
...[...framework.runtimeFunctions].map(([name, modifier]) => ({
|
|
853
|
+
name,
|
|
854
|
+
method: modifier,
|
|
855
|
+
runtimeFunction: { name, imported: false },
|
|
856
|
+
})),
|
|
857
|
+
...[...testNames.runtime].map(([local, fn]) => ({
|
|
858
|
+
name: local,
|
|
859
|
+
method: fn.modifier,
|
|
860
|
+
runtimeFunction: { name: fn.name, imported: true },
|
|
861
|
+
})),
|
|
862
|
+
];
|
|
863
|
+
// `test.each(table)` passes the row to the body, and `test.for(cases)` passes it before the context.
|
|
864
|
+
if (!framework.info || path.includes('each()'))
|
|
865
|
+
return functions;
|
|
825
866
|
const param = body.params.at(framework.info.param + (path.includes('for()') ? 1 : 0));
|
|
826
|
-
return infoNames(param, framework.info.methods);
|
|
867
|
+
return [...functions, ...infoNames(param, framework.info.methods)];
|
|
827
868
|
};
|
|
828
869
|
/**
|
|
829
870
|
* What a callee declares and with which members: `describe` and `['skip']` for `test.describe.skip`.
|
|
@@ -834,12 +875,16 @@ function runAnalysis(sourceCode, options) {
|
|
|
834
875
|
return undefined;
|
|
835
876
|
const playwrightDescribe = framework.playwright && path[0] === 'describe';
|
|
836
877
|
const kind = playwrightDescribe ? 'describe' : root.kind;
|
|
837
|
-
|
|
878
|
+
const written = playwrightDescribe ? path.slice(1) : path;
|
|
879
|
+
return { kind, members: [...root.modifiers, ...written], written, args };
|
|
838
880
|
};
|
|
839
|
-
/**
|
|
840
|
-
|
|
881
|
+
/**
|
|
882
|
+
* Whether a call with these members and arguments declares a test or describe of this kind. Only the
|
|
883
|
+
* members written after the name must exist: Jasmine's `xit` skips, but it has no `it.skip`.
|
|
884
|
+
*/
|
|
885
|
+
const declares = ({ kind, members, written }, args) => {
|
|
841
886
|
const allowed = kind === 'test' ? framework.testMembers : framework.describeMembers;
|
|
842
|
-
return
|
|
887
|
+
return written.every((m) => allowed.has(m)) && isDeclaration(sourceCode, framework, kind, args, members);
|
|
843
888
|
};
|
|
844
889
|
/**
|
|
845
890
|
* `(isCI ? describe.skip : describe)('order entry', ...)`: a test or describe whose function is picked by
|
|
@@ -893,7 +938,7 @@ function runAnalysis(sourceCode, options) {
|
|
|
893
938
|
const { object } = callee;
|
|
894
939
|
if (object.type !== 'Identifier' || !infos.findLast((info) => info.name === object.name)?.methods)
|
|
895
940
|
return undefined;
|
|
896
|
-
const declaration = { kind: 'test', members: [], args: new Map() };
|
|
941
|
+
const declaration = { kind: 'test', members: [], written: [], args: new Map() };
|
|
897
942
|
return declares(declaration, call.arguments)
|
|
898
943
|
? declare(call, declaration, bodyInfos(call.arguments, []))
|
|
899
944
|
: undefined;
|
|
@@ -938,7 +983,7 @@ function runAnalysis(sourceCode, options) {
|
|
|
938
983
|
};
|
|
939
984
|
/**
|
|
940
985
|
* `testInfo.skip()`, `step.skip()`, `test.info().skip()`, Vitest's `context.skip()` or `skip()`,
|
|
941
|
-
* Mocha's `this.skip()`,
|
|
986
|
+
* Mocha's `this.skip()`, node:test's `t.skip()` and `t.todo()`, and Jasmine's `pending()`.
|
|
942
987
|
*/
|
|
943
988
|
const runtimeCall = (call) => {
|
|
944
989
|
const { callee } = call;
|
|
@@ -946,7 +991,17 @@ function runAnalysis(sourceCode, options) {
|
|
|
946
991
|
const innermost = (name) => infos.findLast((info) => info.name === name);
|
|
947
992
|
if (callee.type === 'Identifier') {
|
|
948
993
|
const info = innermost(callee.name);
|
|
949
|
-
|
|
994
|
+
if (info?.method === undefined)
|
|
995
|
+
return undefined;
|
|
996
|
+
// A global such as Jasmine's `pending` counts unless the file declares the name itself; an import of
|
|
997
|
+
// one counts unless a function or block declares the name again.
|
|
998
|
+
const fn = info.runtimeFunction;
|
|
999
|
+
if (fn && declaredWhere(sourceCode, callee) !== (fn.imported ? 'top' : 'global'))
|
|
1000
|
+
return undefined;
|
|
1001
|
+
const subject = runtimeSubject(call, info.method, info.step);
|
|
1002
|
+
if (subject)
|
|
1003
|
+
subject.functionName = fn?.name;
|
|
1004
|
+
return subject;
|
|
950
1005
|
}
|
|
951
1006
|
if (callee.type !== 'MemberExpression')
|
|
952
1007
|
return undefined;
|
|
@@ -1105,6 +1160,6 @@ export function describeSubject(subject) {
|
|
|
1105
1160
|
if (subject.kind === 'step')
|
|
1106
1161
|
return 'This step';
|
|
1107
1162
|
if (subject.kind === 'runtime')
|
|
1108
|
-
return `This ${subject.states[0].state.modifier} call`;
|
|
1163
|
+
return `This ${subject.functionName ?? subject.states[0].state.modifier} call`;
|
|
1109
1164
|
return 'This test';
|
|
1110
1165
|
}
|
|
@@ -8,7 +8,7 @@ export type BuiltinStateName = (typeof BUILTIN_STATE_NAMES)[number];
|
|
|
8
8
|
export declare const TAG_STATE_NAMES: readonly ["new", "quarantined"];
|
|
9
9
|
export type TagStateName = (typeof TAG_STATE_NAMES)[number];
|
|
10
10
|
/** The test frameworks the rules understand. */
|
|
11
|
-
export declare const FRAMEWORK_NAMES: readonly ["playwright", "jest", "vitest", "mocha", "cypress", "webdriverio", "node-test"];
|
|
11
|
+
export declare const FRAMEWORK_NAMES: readonly ["playwright", "jest", "vitest", "mocha", "cypress", "webdriverio", "node-test", "jasmine"];
|
|
12
12
|
export type FrameworkName = (typeof FRAMEWORK_NAMES)[number];
|
|
13
13
|
export declare const TICKET_PRESETS: readonly ["any", "jira", "github", "gitlab", "linear", "azure-devops", "numeric", "pattern"];
|
|
14
14
|
/** Comment keywords that must reference a ticket by default (`// TODO: TRADE-123`). */
|
package/dist/utils/constants.js
CHANGED
|
@@ -93,6 +93,11 @@ export interface Framework {
|
|
|
93
93
|
thisMethods: ReadonlySet<string>;
|
|
94
94
|
/** Whether a test with only a title written out is a todo, as Mocha's pending `it('title')`. */
|
|
95
95
|
todoWithoutBody: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Functions, global or imported by name, that change the state of the test whose body calls them, or
|
|
98
|
+
* whose `beforeEach` or `afterEach` does, as Jasmine's `pending()`, with the state each puts it in.
|
|
99
|
+
*/
|
|
100
|
+
runtimeFunctions: ReadonlyMap<string, Modifier>;
|
|
96
101
|
/** Built-in states a test can be put in with this framework. */
|
|
97
102
|
states: ReadonlySet<BuiltinStateName>;
|
|
98
103
|
/** Playwright's own API on `test`: `test.describe`, `test.step`, `test.info()` and runtime `test.skip()`. */
|
package/dist/utils/frameworks.js
CHANGED
|
@@ -3,7 +3,17 @@ const DESCRIBE = { kind: 'describe', modifiers: [] };
|
|
|
3
3
|
const HOOK = { kind: 'hook', modifiers: [] };
|
|
4
4
|
const PLAYWRIGHT_MODULES = new Set(['@playwright/test', 'playwright/test']);
|
|
5
5
|
/** Test runners besides Playwright. A name imported from one of them belongs to that runner. */
|
|
6
|
-
const OTHER_RUNNERS = new Set([
|
|
6
|
+
const OTHER_RUNNERS = new Set([
|
|
7
|
+
'vitest',
|
|
8
|
+
'@jest/globals',
|
|
9
|
+
'node:test',
|
|
10
|
+
'jasmine-core',
|
|
11
|
+
'bun:test',
|
|
12
|
+
'mocha',
|
|
13
|
+
'ava',
|
|
14
|
+
'tap',
|
|
15
|
+
'uvu',
|
|
16
|
+
]);
|
|
7
17
|
/** `@playwright/test` and the component-testing packages such as `@playwright/experimental-ct-react`. */
|
|
8
18
|
function isPlaywrightModule(source) {
|
|
9
19
|
return PLAYWRIGHT_MODULES.has(source) || source.startsWith('@playwright/experimental-ct-');
|
|
@@ -39,6 +49,7 @@ const playwright = {
|
|
|
39
49
|
skipNotes: false,
|
|
40
50
|
thisMethods: new Set(),
|
|
41
51
|
todoWithoutBody: false,
|
|
52
|
+
runtimeFunctions: new Map(),
|
|
42
53
|
states: new Set(['skip', 'fixme', 'fail', 'slow', 'new', 'quarantined']),
|
|
43
54
|
playwright: true,
|
|
44
55
|
};
|
|
@@ -77,6 +88,7 @@ const jest = {
|
|
|
77
88
|
skipNotes: false,
|
|
78
89
|
thisMethods: new Set(),
|
|
79
90
|
todoWithoutBody: false,
|
|
91
|
+
runtimeFunctions: new Map(),
|
|
80
92
|
states: new Set(['skip', 'todo', 'fail', 'new', 'quarantined']),
|
|
81
93
|
playwright: false,
|
|
82
94
|
};
|
|
@@ -124,6 +136,7 @@ const vitest = {
|
|
|
124
136
|
skipNotes: true,
|
|
125
137
|
thisMethods: new Set(),
|
|
126
138
|
todoWithoutBody: false,
|
|
139
|
+
runtimeFunctions: new Map(),
|
|
127
140
|
states: new Set(['skip', 'todo', 'fail', 'new', 'quarantined']),
|
|
128
141
|
playwright: false,
|
|
129
142
|
};
|
|
@@ -168,6 +181,7 @@ const mocha = {
|
|
|
168
181
|
skipNotes: false,
|
|
169
182
|
thisMethods: new Set(['skip']),
|
|
170
183
|
todoWithoutBody: true,
|
|
184
|
+
runtimeFunctions: new Map(),
|
|
171
185
|
states: new Set(['skip', 'todo', 'new', 'quarantined']),
|
|
172
186
|
playwright: false,
|
|
173
187
|
};
|
|
@@ -222,9 +236,57 @@ const nodeTest = {
|
|
|
222
236
|
skipNotes: true,
|
|
223
237
|
thisMethods: NODE_TEST_CONTEXT,
|
|
224
238
|
todoWithoutBody: false,
|
|
239
|
+
runtimeFunctions: new Map(),
|
|
225
240
|
states: new Set(['skip', 'todo', 'fail', 'new', 'quarantined']),
|
|
226
241
|
playwright: false,
|
|
227
242
|
};
|
|
243
|
+
const JASMINE_FUNCTIONS = new Map([
|
|
244
|
+
['it', TEST],
|
|
245
|
+
['xit', SKIPPED_TEST],
|
|
246
|
+
['fit', { kind: 'test', modifiers: ['only'] }],
|
|
247
|
+
['describe', DESCRIBE],
|
|
248
|
+
['xdescribe', SKIPPED_DESCRIBE],
|
|
249
|
+
['fdescribe', { kind: 'describe', modifiers: ['only'] }],
|
|
250
|
+
['beforeEach', HOOK],
|
|
251
|
+
['afterEach', HOOK],
|
|
252
|
+
// A `pending()` in `beforeAll` or `afterAll` fails the suite instead of skipping its tests.
|
|
253
|
+
['beforeAll', { kind: 'hook', modifiers: [], noContext: true }],
|
|
254
|
+
['afterAll', { kind: 'hook', modifiers: [], noContext: true }],
|
|
255
|
+
]);
|
|
256
|
+
/**
|
|
257
|
+
* Jasmine, run on its own, on Karma or by WebdriverIO's Jasmine framework. Its functions are globals, or
|
|
258
|
+
* with `globals: false` in Jasmine 7 exports of `jasmine-core`, and have no members: the `x` and `f` forms
|
|
259
|
+
* are functions of their own. `pending()` and Jasmine 7's `notApplicable()` skip the running test.
|
|
260
|
+
*/
|
|
261
|
+
const jasmine = {
|
|
262
|
+
name: 'jasmine',
|
|
263
|
+
ownsModule: (source) => source === 'jasmine-core',
|
|
264
|
+
exports: JASMINE_FUNCTIONS,
|
|
265
|
+
moduleIsDefault: true,
|
|
266
|
+
globals: new Map([...JASMINE_FUNCTIONS].filter(([name]) => name !== 'it')),
|
|
267
|
+
testFunctions: ['it'],
|
|
268
|
+
testMembers: new Set(),
|
|
269
|
+
describeMembers: new Set(),
|
|
270
|
+
factories: new Set(),
|
|
271
|
+
effects: effects([['skip', 'skip']]),
|
|
272
|
+
optionEffects: new Map(),
|
|
273
|
+
anyOptionValue: false,
|
|
274
|
+
exclusiveStates: [],
|
|
275
|
+
optionalTitle: false,
|
|
276
|
+
plainTags: false,
|
|
277
|
+
subtests: false,
|
|
278
|
+
skipWithoutBody: true,
|
|
279
|
+
// `pending('why')` takes a reason, not a condition.
|
|
280
|
+
skipNotes: true,
|
|
281
|
+
thisMethods: new Set(),
|
|
282
|
+
todoWithoutBody: true,
|
|
283
|
+
runtimeFunctions: new Map([
|
|
284
|
+
['pending', 'skip'],
|
|
285
|
+
['notApplicable', 'skip'],
|
|
286
|
+
]),
|
|
287
|
+
states: new Set(['skip', 'todo', 'new', 'quarantined']),
|
|
288
|
+
playwright: false,
|
|
289
|
+
};
|
|
228
290
|
export const FRAMEWORKS = {
|
|
229
291
|
playwright,
|
|
230
292
|
jest,
|
|
@@ -233,6 +295,7 @@ export const FRAMEWORKS = {
|
|
|
233
295
|
cypress: { name: 'cypress', ...mocha },
|
|
234
296
|
webdriverio: { name: 'webdriverio', ...mocha },
|
|
235
297
|
'node-test': nodeTest,
|
|
298
|
+
jasmine,
|
|
236
299
|
};
|
|
237
300
|
/** Whether `source` belongs to a test runner other than `framework`, so its names are never tests here. */
|
|
238
301
|
export function isOtherRunner(framework, source) {
|
package/dist/utils/options.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface CustomState {
|
|
|
21
21
|
export interface GovernanceOptions {
|
|
22
22
|
/**
|
|
23
23
|
* The test framework of the files the config applies to: `'playwright'`, `'jest'`, `'vitest'`, `'mocha'`,
|
|
24
|
-
* `'cypress'`, `'webdriverio'
|
|
24
|
+
* `'cypress'`, `'webdriverio'`, `'node-test'` or `'jasmine'`. Required; each framework's config, such as `configs.mocha`, sets it.
|
|
25
25
|
*/
|
|
26
26
|
framework: FrameworkName;
|
|
27
27
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
📝 Require the marker keyword to match the test's state.
|
|
4
4
|
|
|
5
|
-
💼 This rule is enabled in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
5
|
+
💼 This rule is enabled in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🌼 `jasmine`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
6
6
|
|
|
7
7
|
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
📝 Disallow state tags that contradict each other, differ in case or look like typos.
|
|
4
4
|
|
|
5
|
-
⚠️ This rule _warns_ in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
5
|
+
⚠️ This rule _warns_ in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🌼 `jasmine`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
6
6
|
|
|
7
7
|
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
📝 Disallow marker comments that no longer match a test state.
|
|
4
4
|
|
|
5
|
-
💼 This rule is enabled in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
5
|
+
💼 This rule is enabled in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🌼 `jasmine`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
6
6
|
|
|
7
7
|
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
📝 Require FIXME and TODO comments in test files to start with a ticket.
|
|
4
4
|
|
|
5
|
-
💼 This rule is enabled in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
5
|
+
💼 This rule is enabled in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🌼 `jasmine`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
6
6
|
|
|
7
7
|
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
📝 Require a ticket marker comment above skipped, fixme and tagged tests.
|
|
4
4
|
|
|
5
|
-
💼 This rule is enabled in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
5
|
+
💼 This rule is enabled in the following [configs](https://github.com/Huldoser/eslint-plugin-test-governance#usage): 🌲 `cypress`, 🌼 `jasmine`, 🃏 `jest`, ☕ `mocha`, 🟢 `node-test`, 🎭 `playwright`, ⚡ `vitest`, 🤖 `webdriverio`.
|
|
6
6
|
|
|
7
7
|
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
8
8
|
|
|
@@ -17,7 +17,7 @@ A test is in a state when it is:
|
|
|
17
17
|
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -------------------------- |
|
|
18
18
|
| `skip` | `test.skip('title', fn)`, `test.describe.skip`, `test.skip()` in a body, `testInfo.skip()`, `test.step.skip`, `step.skip()` in a step | `// SKIP:` | yes |
|
|
19
19
|
| `fixme` | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | `// FIXME:` | yes |
|
|
20
|
-
| `todo` | Jest's, Vitest's and `node:test`'s `test.todo`, and a Mocha test with only a title, `it('title')`
|
|
20
|
+
| `todo` | Jest's, Vitest's and `node:test`'s `test.todo`, and a Mocha or Jasmine test with only a title, `it('title')` | `// TODO:` | yes, except in Playwright |
|
|
21
21
|
| `new` | the `@new` tag | `// NEW:` | with `lifecycleTags: true` |
|
|
22
22
|
| `quarantined` | the `@quarantined` tag | `// QUARANTINED:` | with `lifecycleTags: true` |
|
|
23
23
|
| `fail`, `slow` | `test.fail(...)`, `test.slow()`, `testInfo.fail()`, `testInfo.slow()` | `// FAIL:`, `// SLOW:` | no |
|
|
@@ -27,7 +27,8 @@ In Jest and Vitest, `xit`, `xdescribe`, Vitest's `{ skip: true }`, `skipIf(true)
|
|
|
27
27
|
`context.skip('note')`, in a test or in `beforeEach`, skip a test too, and `test.failing` and `test.fails` put it in the `fail` state. In Mocha, Cypress and WebdriverIO, `xit`, `xdescribe` and
|
|
28
28
|
`this.skip()` in a test or hook skip a test too, and Cypress's `{ browser: '!webkit' }` skips it in other
|
|
29
29
|
browsers, which counts as a conditional skip. In `node:test`, `{ skip: 'reason' }`, `t.skip()` and `this.skip()` skip a test, `{ todo: true }`,
|
|
30
|
-
`t.todo()` and `this.todo()` make it a todo, `t.test()` subtests count as tests, and `test.expectFailure` puts it in the `fail` state.
|
|
30
|
+
`t.todo()` and `this.todo()` make it a todo, `t.test()` subtests count as tests, and `test.expectFailure` puts it in the `fail` state. In
|
|
31
|
+
Jasmine, `xit`, `xdescribe`, and `pending()` or `notApplicable()` in a test, `beforeEach` or `afterEach` skip a test. The
|
|
31
32
|
[README](../../README.md#frameworks) lists every form.
|
|
32
33
|
|
|
33
34
|
Tags are read from the title (`'places a limit order @new'`) and from `{ tag: '@new' }` or `{ tag: ['@new', '@smoke'] }`,
|
|
@@ -213,6 +214,14 @@ import { test } from 'node:test';
|
|
|
213
214
|
test('liquidates below maintenance', { skip: 'broker sandbox is down' }, () => {});
|
|
214
215
|
```
|
|
215
216
|
|
|
217
|
+
<!-- example: invalid settings={"framework":"jasmine"} -->
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
it('streams level 2 quotes', () => {
|
|
221
|
+
pending('needs the market data feed');
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
216
225
|
<!-- example: invalid settings={"framework":"cypress","lifecycleTags":true} -->
|
|
217
226
|
|
|
218
227
|
```js
|
|
@@ -299,6 +308,19 @@ test('nets margin across accounts', (t) => {
|
|
|
299
308
|
});
|
|
300
309
|
```
|
|
301
310
|
|
|
311
|
+
<!-- example: valid settings={"framework":"jasmine"} -->
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
describe('order entry', () => {
|
|
315
|
+
// SKIP: TRADE-93
|
|
316
|
+
xit('routes an order to the dark pool', () => {});
|
|
317
|
+
|
|
318
|
+
it('prints the trade confirmation', () => {
|
|
319
|
+
if (isNode) pending('needs a browser');
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
302
324
|
## Options
|
|
303
325
|
|
|
304
326
|
This rule has no options of its own. It reads the shared options described in the
|
package/docs/upgrading.md
CHANGED
|
@@ -90,7 +90,7 @@ becomes `extends: ['test-governance/playwright']`.
|
|
|
90
90
|
A config that still reads `configs.recommended` fails when it loads:
|
|
91
91
|
|
|
92
92
|
```text
|
|
93
|
-
eslint-plugin-test-governance: 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
|
|
93
|
+
eslint-plugin-test-governance: 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, configs['node-test'] or configs.jasmine. See https://github.com/Huldoser/eslint-plugin-test-governance/blob/main/docs/upgrading.md
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
### The framework option
|
|
@@ -121,7 +121,7 @@ Without it, the config fails when it loads:
|
|
|
121
121
|
|
|
122
122
|
```text
|
|
123
123
|
eslint-plugin-test-governance: invalid options:
|
|
124
|
-
- framework is required: set it to "playwright", "jest", "vitest", "mocha", "cypress", "webdriverio"
|
|
124
|
+
- framework is required: set it to "playwright", "jest", "vitest", "mocha", "cypress", "webdriverio", "node-test" or "jasmine", or use configs.playwright, configs.jest, configs.vitest, configs.mocha, configs.cypress, configs.webdriverio, configs['node-test'] or configs.jasmine
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
If you write `settings['test-governance']` yourself instead of calling `configure()`, add `framework`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-test-governance",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "ESLint rules that require a ticket comment above skipped, fixme, todo and tagged Playwright, Jest, Vitest, Mocha, Cypress, WebdriverIO
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "ESLint rules that require a ticket comment above skipped, fixme, todo and tagged Playwright, Jest, Vitest, Mocha, Cypress, WebdriverIO, node:test and Jasmine tests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
7
7
|
"eslintplugin",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"cypress",
|
|
14
14
|
"webdriverio",
|
|
15
15
|
"node-test",
|
|
16
|
+
"jasmine",
|
|
16
17
|
"testing"
|
|
17
18
|
],
|
|
18
19
|
"homepage": "https://github.com/Huldoser/eslint-plugin-test-governance#readme",
|