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 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.8.1...HEAD
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
  [![CI](https://github.com/Huldoser/eslint-plugin-test-governance/actions/workflows/ci.yml/badge.svg)](https://github.com/Huldoser/eslint-plugin-test-governance/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/npm/l/eslint-plugin-test-governance)](LICENSE)
6
6
 
7
- ESLint rules that keep a Playwright, Jest, Vitest, Mocha, Cypress, WebdriverIO or `node:test` suite honest. Every skipped, fixme, todo, `@new`
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` or `configs['node-test']` for
39
- Node's built-in runner, and point it at those test files with `files`:
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 Mocha config, `test`, `it` and `describe` belong
57
- to that framework, whether they are globals or imported from `@jest/globals`, `vitest` or `mocha`, the
58
- way the Jest, Vitest and Mocha ESLint plugins are set up. `node:test` has no globals, so in its config
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 framework
63
- isn't supported yet. Puppeteer has no test functions of its own, so use the config of the runner its
64
- tests run on: `configs.jest` for jest-puppeteer, or `configs.vitest`, `configs.mocha` or
65
- `configs['node-test']`.
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']`, and Mocha, Cypress and WebdriverIO to `['it', 'specify', 'test']`. Your list replaces the default one, so include those names if you still need them. See below. |
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()`, and other
370
- runners such as Jasmine and Bun.
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 or configs['node-test']. See https://github.com/Huldoser/eslint-plugin-test-governance/blob/main/docs/upgrading.md");
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;
@@ -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[];
@@ -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
- /** What a body can change the test's state with: Playwright's `testInfo` or Vitest's test context. */
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
- // `test.each(table)` passes the row to the body, and `test.for(cases)` passes it before the context.
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
- return { kind, members: [...root.modifiers, ...(playwrightDescribe ? path.slice(1) : path)], args };
878
+ const written = playwrightDescribe ? path.slice(1) : path;
879
+ return { kind, members: [...root.modifiers, ...written], written, args };
838
880
  };
839
- /** Whether a call with these members and arguments declares a test or describe of this kind. */
840
- const declares = ({ kind, members }, args) => {
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 members.every((m) => allowed.has(m)) && isDeclaration(sourceCode, framework, kind, args, members);
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()`, and node:test's `t.skip()` and `t.todo()`.
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
- return info?.method !== undefined ? runtimeSubject(call, info.method, info.step) : undefined;
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`). */
@@ -14,6 +14,7 @@ export const FRAMEWORK_NAMES = [
14
14
  'cypress',
15
15
  'webdriverio',
16
16
  'node-test',
17
+ 'jasmine',
17
18
  ];
18
19
  export const TICKET_PRESETS = [
19
20
  'any',
@@ -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()`. */
@@ -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(['vitest', '@jest/globals', 'node:test', 'bun:test', 'mocha', 'ava', 'tap', 'uvu']);
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) {
@@ -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'` or `'node-test'`. Required; each framework's config, such as `configs.mocha`, sets it.
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')` | `// TODO:` | yes, except in Playwright |
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. The
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 or configs['node-test']. See https://github.com/Huldoser/eslint-plugin-test-governance/blob/main/docs/upgrading.md
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" or "node-test", or use configs.playwright, configs.jest, configs.vitest, configs.mocha, configs.cypress, configs.webdriverio or configs['node-test']
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.8.1",
4
- "description": "ESLint rules that require a ticket comment above skipped, fixme, todo and tagged Playwright, Jest, Vitest, Mocha, Cypress, WebdriverIO and node:test tests.",
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",