eslint-plugin-test-governance 0.3.0 → 0.4.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +76 -1
  2. package/README.md +166 -64
  3. package/dist/index.d.ts +14 -12
  4. package/dist/index.js +22 -12
  5. package/dist/rules/marker-matches-state.d.ts +1 -2
  6. package/dist/rules/marker-matches-state.js +7 -7
  7. package/dist/rules/no-conflicting-states.d.ts +1 -2
  8. package/dist/rules/no-conflicting-states.js +12 -20
  9. package/dist/rules/no-orphaned-marker.d.ts +1 -2
  10. package/dist/rules/no-orphaned-marker.js +15 -10
  11. package/dist/rules/require-ticket-in-comments.d.ts +1 -2
  12. package/dist/rules/require-ticket-in-comments.js +13 -18
  13. package/dist/rules/require-ticket.d.ts +1 -2
  14. package/dist/rules/require-ticket.js +18 -10
  15. package/dist/utils/analyze.d.ts +17 -14
  16. package/dist/utils/analyze.js +370 -153
  17. package/dist/utils/constants.d.ts +5 -3
  18. package/dist/utils/constants.js +4 -3
  19. package/dist/utils/create-rule.d.ts +9 -10
  20. package/dist/utils/create-rule.js +5 -11
  21. package/dist/utils/distance.d.ts +0 -1
  22. package/dist/utils/distance.js +0 -1
  23. package/dist/utils/errors.d.ts +0 -1
  24. package/dist/utils/errors.js +0 -1
  25. package/dist/utils/fix.d.ts +0 -3
  26. package/dist/utils/fix.js +0 -8
  27. package/dist/utils/frameworks.d.ts +59 -0
  28. package/dist/utils/frameworks.js +102 -0
  29. package/dist/utils/options.d.ts +26 -11
  30. package/dist/utils/options.js +13 -9
  31. package/dist/utils/schema.d.ts +1 -2
  32. package/dist/utils/schema.js +2 -2
  33. package/dist/utils/tickets.d.ts +0 -1
  34. package/dist/utils/tickets.js +4 -4
  35. package/dist/utils/validate.d.ts +0 -1
  36. package/dist/utils/validate.js +3 -3
  37. package/docs/rules/marker-matches-state.md +18 -10
  38. package/docs/rules/no-conflicting-states.md +14 -8
  39. package/docs/rules/no-orphaned-marker.md +31 -17
  40. package/docs/rules/require-ticket-in-comments.md +26 -22
  41. package/docs/rules/require-ticket.md +107 -47
  42. package/package.json +7 -10
@@ -26,27 +26,27 @@ Incorrect:
26
26
  <!-- example: invalid settings={"lifecycleTags":true} -->
27
27
 
28
28
  ```js
29
- test('filters by category @new @unstable', async () => {});
29
+ test('places a limit order @new @unstable', async () => {});
30
30
  ```
31
31
 
32
32
  <!-- example: invalid settings={"lifecycleTags":true} -->
33
33
 
34
34
  ```js
35
- // SKIP: WEB-9
36
- // NEW: WEB-9
37
- test.skip('filters by category @new', async () => {});
35
+ // SKIP: TRADE-9
36
+ // NEW: TRADE-9
37
+ test.skip('places a limit order @new', async () => {});
38
38
  ```
39
39
 
40
40
  <!-- example: invalid settings={"lifecycleTags":true} -->
41
41
 
42
42
  ```js
43
- test('invoice download @New', async () => {});
43
+ test('exports the trade history as CSV @New', async () => {});
44
44
  ```
45
45
 
46
46
  <!-- example: invalid settings={"lifecycleTags":true} -->
47
47
 
48
48
  ```js
49
- test('loyalty points @unstabel', async () => {});
49
+ test('shows dividend payments in the account history @unstabel', async () => {});
50
50
  ```
51
51
 
52
52
  Correct:
@@ -54,9 +54,15 @@ Correct:
54
54
  <!-- example: valid settings={"lifecycleTags":true} -->
55
55
 
56
56
  ```js
57
- test('filters by category @unstable', async () => {});
57
+ test('places a limit order @unstable', async () => {});
58
58
  ```
59
59
 
60
60
  ## Options
61
61
 
62
- Uses the shared options described in the [README](../../README.md#options).
62
+ This rule has no options of its own. It reads the shared options described in the
63
+ [README](../../README.md#options). These change what it reports:
64
+
65
+ - `lifecycleTags`, or `states` for `new` and `unstable`: the `@new` checks run when the `new` state
66
+ is on, and `@new` together with `@unstable` is reported when both are on.
67
+ - `customStates`: the tags of custom states get the same case and typo checks.
68
+ - `framework` and `testFunctions`: which functions count as the framework's `test`.
@@ -22,14 +22,18 @@ one from this.
22
22
  A `FIXME:` or `TODO:` comment that starts with a valid ticket is treated by where it sits:
23
23
 
24
24
  - Above a helper or any other code that isn't a test, it is a tracked work comment and is allowed.
25
- - Above a test, describe or skip call that is not in the `fixme` state, it reads as a leftover marker
26
- and is reported, since `// FIXME: WEB-12` above a test usually means the test was fixed and the
27
- marker forgotten. Move a work comment into the test body, or onto the line of code it is about.
25
+ - Above a test, describe or skip call that is not in the `fixme` state, a `FIXME:` comment reads as a
26
+ leftover marker and is reported, since `// FIXME: TRADE-12` above a test usually means the test was
27
+ fixed and the marker forgotten. Move a work comment into the test body, or onto the line of code it
28
+ is about.
29
+ - Above a Jest or Vitest test that is not a todo, a `TODO:` comment is a tracked work comment about
30
+ the test and is allowed. `TODO` is the everyday keyword for such comments, so there it is far more
31
+ often a note than the forgotten marker of a `test.todo`.
28
32
 
29
33
  Other markers that read as a sentence, such as `// SKIP: this one is flaky`, are left alone too: the rule
30
34
  counts a comment as prose when its first word is not a valid ticket and more text follows.
31
35
 
32
- Files that don't use Playwright, such as application code, are not checked.
36
+ Files without tests, such as application code, are not checked.
33
37
 
34
38
  The rule offers a suggestion to delete the comment.
35
39
 
@@ -40,16 +44,16 @@ Incorrect:
40
44
  <!-- example: invalid settings={"lifecycleTags":true} -->
41
45
 
42
46
  ```js
43
- // NEW: WEB-80 promoted last sprint
44
- test('saved addresses', async () => {});
47
+ // NEW: TRADE-80 promoted last sprint
48
+ test('saves a watchlist', async () => {});
45
49
  ```
46
50
 
47
51
  <!-- example: invalid -->
48
52
 
49
53
  ```js
50
- test('checkout', async ({ page }) => {
51
- // SKIP: WEB-81
52
- await page.goto('/checkout');
54
+ test('places a limit order', async ({ page }) => {
55
+ // SKIP: TRADE-81
56
+ await page.goto('/orders');
53
57
  });
54
58
  ```
55
59
 
@@ -58,27 +62,37 @@ Correct:
58
62
  <!-- example: valid settings={"lifecycleTags":true} -->
59
63
 
60
64
  ```js
61
- test('saved addresses', async () => {});
65
+ test('saves a watchlist', async () => {});
62
66
  ```
63
67
 
64
68
  <!-- example: valid -->
65
69
 
66
70
  ```js
67
- test('checkout', async ({ page }) => {
68
- // FIXME: the banner sometimes covers the button on slow machines
69
- await page.goto('/checkout');
71
+ test('places a limit order', async ({ page }) => {
72
+ // FIXME: the price field sometimes loses focus on slow machines
73
+ await page.goto('/orders');
70
74
  });
71
75
  ```
72
76
 
73
77
  <!-- example: valid -->
74
78
 
75
79
  ```js
76
- // FIXME: WEB-81
77
- test.fixme('checkout', async ({ page }) => {
78
- await page.goto('/checkout');
80
+ // FIXME: TRADE-81
81
+ test.fixme('places a limit order', async ({ page }) => {
82
+ await page.goto('/orders');
79
83
  });
80
84
  ```
81
85
 
82
86
  ## Options
83
87
 
84
- Uses the shared options described in the [README](../../README.md#options).
88
+ This rule has no options of its own. It reads the shared options described in the
89
+ [README](../../README.md#options). These change what it reports:
90
+
91
+ - `lifecycleTags`, `states` and `customStates`: which keywords are markers. `// NEW:` is a marker
92
+ only while the `new` state is on, so with `lifecycleTags` off it is an ordinary comment.
93
+ - `ticket` and `placeholders`: whether a comment starts with a valid ticket, which separates a
94
+ leftover marker from a sentence or a work comment.
95
+ - `allowBlankLine`: whether a marker separated from the code below it by a blank line still belongs
96
+ to that code.
97
+ - `framework` and `testFunctions`: which functions count as the framework's `test`, and so which
98
+ files are test files.
@@ -13,12 +13,12 @@ A `// TODO: clean this up` comment is a promise nobody tracks. This rule makes e
13
13
  source of truth and nothing is forgotten in a comment.
14
14
 
15
15
  ```ts
16
- // TODO: WEB-512 ✓
17
- // FIXME: WEB-77, WEB-78 ✓ several tickets, separated by commas
18
- // TODO(WEB-512) ✓ the ticket can go in parentheses
19
- // TODO(alice): refactor this ✖ a name is not a ticket
20
- // TODO: refactor this ✖ no ticket
21
- // FIXME: WEB-77 flaky locator ✖ only tickets are allowed (allowNotes: true allows the note)
16
+ // TODO: TRADE-512 ✓
17
+ // FIXME: TRADE-77, TRADE-78 ✓ several tickets, separated by commas
18
+ // TODO(TRADE-512) ✓ the ticket can go in parentheses
19
+ // TODO(alice): refactor this ✖ a name is not a ticket
20
+ // TODO: refactor this ✖ no ticket
21
+ // FIXME: TRADE-77 flaky locator ✖ only tickets are allowed (allowNotes: true allows the note)
22
22
  ```
23
23
 
24
24
  How comments are read:
@@ -26,13 +26,14 @@ How comments are read:
26
26
  - The keyword starts the comment (or a line of a block comment). `TODO` and `FIXME` in capitals
27
27
  count with or without a colon. Other casings, such as `todo:`, need the colon, so a sentence that
28
28
  happens to start with "Todo" is left alone.
29
- - The tickets can also go in parentheses straight after the keyword, `TODO(WEB-512)`, a style many
30
- teams already use. Anything after the parentheses is a note, as it would be after `TODO: WEB-512`.
29
+ - The tickets can also go in parentheses after the keyword, `TODO(TRADE-512)` or `TODO (TRADE-512)`, a
30
+ style many teams already use. Anything after the parentheses is a note, as it would be after `TODO: TRADE-512`.
31
31
  - The tickets follow the same rules as markers: the shared ticket format, placeholders such as `TBD`
32
32
  rejected, and no text after the tickets unless `allowNotes` is set.
33
- - A `// FIXME:` marker directly above a `test.fixme(...)` is checked by
34
- [`require-ticket`](require-ticket.md) instead, so it is reported once.
35
- - Only files that use Playwright (a Playwright import or a call to a test function) are checked.
33
+ - A `// FIXME:` marker directly above a `test.fixme(...)`, or a `// TODO:` marker above a Jest or
34
+ Vitest `test.todo(...)`, is checked by [`require-ticket`](require-ticket.md) instead, so it is
35
+ reported once.
36
+ - Only test files (a test framework import or a call to a test function) are checked.
36
37
  Application code is left alone, even if your config applies the plugin to it.
37
38
 
38
39
  ## Examples
@@ -42,9 +43,9 @@ Incorrect:
42
43
  <!-- example: invalid -->
43
44
 
44
45
  ```js
45
- test('checkout', async ({ page }) => {
46
+ test('places a limit order', async ({ page }) => {
46
47
  // FIXME: this locator breaks on mobile
47
- await page.click('#pay');
48
+ await page.click('#buy');
48
49
  });
49
50
  ```
50
51
 
@@ -52,7 +53,7 @@ test('checkout', async ({ page }) => {
52
53
 
53
54
  ```js
54
55
  // TODO: TBD
55
- test('refund', async ({ page }) => {});
56
+ test('cancels an open order', async ({ page }) => {});
56
57
  ```
57
58
 
58
59
  Correct:
@@ -60,17 +61,17 @@ Correct:
60
61
  <!-- example: valid -->
61
62
 
62
63
  ```js
63
- test('checkout', async ({ page }) => {
64
- // FIXME: WEB-81
65
- await page.click('#pay');
64
+ test('places a limit order', async ({ page }) => {
65
+ // FIXME: TRADE-81
66
+ await page.click('#buy');
66
67
  });
67
68
  ```
68
69
 
69
70
  <!-- example: valid settings={"comments":{"keywords":["FIXME"]}} -->
70
71
 
71
72
  ```js
72
- // TODO: add a refund test once the API is stable
73
- test('checkout', async ({ page }) => {});
73
+ // TODO: add a test for cancelled orders once the API is stable
74
+ test('places a limit order', async ({ page }) => {});
74
75
  ```
75
76
 
76
77
  ## Options
@@ -78,7 +79,10 @@ test('checkout', async ({ page }) => {});
78
79
  This rule has no options of its own. It reads the shared options described in the
79
80
  [README](../../README.md#options):
80
81
 
81
- - `comments`: the keywords to check, `{ keywords: ['FIXME', 'TODO'] }` by default. Use
82
- `{ keywords: ['FIXME'] }` to allow free-form `TODO`s, add your own such as `HACK`, or set
83
- `comments: false` to turn the check off. Turning the rule off in your config works too.
82
+ - `comments`: the keywords to check, `{ keywords: ['FIXME', 'TODO'] }` by default. Your list
83
+ replaces the default one: `{ keywords: ['FIXME'] }` allows free-form `TODO`s, and
84
+ `{ keywords: ['FIXME', 'TODO', 'HACK'] }` checks `HACK` too. `comments: false` turns the check
85
+ off, and so does turning the rule off in your config.
84
86
  - `ticket`, `placeholders` and `allowNotes` work as they do for markers.
87
+ - `framework` and `testFunctions`: which functions count as the framework's `test`, and so which
88
+ files are test files.
@@ -13,51 +13,59 @@ at the ticket that explains it, so disabled tests show up in planning instead of
13
13
 
14
14
  A test is in a state when it is:
15
15
 
16
- | State | Put there by | Marker | On by default |
17
- | -------------- | ------------------------------------------------------------------------------------------ | ---------------------- | -------------------------- |
18
- | `skip` | `test.skip('title', fn)`, `test.describe.skip`, `test.skip()` in a body, `testInfo.skip()` | `// SKIP:` | yes |
19
- | `fixme` | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | `// FIXME:` | yes |
20
- | `new` | the `@new` tag | `// NEW:` | with `lifecycleTags: true` |
21
- | `unstable` | the `@unstable` tag | `// UNSTABLE:` | with `lifecycleTags: true` |
22
- | `fail`, `slow` | `test.fail(...)`, `test.slow()` | `// FAIL:`, `// SLOW:` | no |
23
- | custom | any tag you list in `customStates` | your keyword | when configured |
24
-
25
- Tags are read from the title (`'checkout @new'`) and from `{ tag: '@new' }` or `{ tag: ['@new', '@smoke'] }`.
16
+ | State | Put there by | Marker | On by default |
17
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -------------------------- |
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
+ | `fixme` | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | `// FIXME:` | yes |
20
+ | `todo` | Jest's and Vitest's `test.todo` | `// TODO:` | yes, in Jest and Vitest |
21
+ | `new` | the `@new` tag | `// NEW:` | with `lifecycleTags: true` |
22
+ | `unstable` | the `@unstable` tag | `// UNSTABLE:` | with `lifecycleTags: true` |
23
+ | `fail`, `slow` | `test.fail(...)`, `test.slow()`, `testInfo.fail()`, `testInfo.slow()` | `// FAIL:`, `// SLOW:` | no |
24
+ | custom | any tag you list in `customStates` | your keyword | when configured |
25
+
26
+ In Jest and Vitest, `xit`, `xdescribe`, Vitest's `{ skip: true }` and `context.skip()` skip a test
27
+ too, and `test.failing` and `test.fails` put it in the `fail` state. The
28
+ [README](../../README.md#jest-and-vitest) lists every form.
29
+
30
+ Tags are read from the title (`'places a limit order @new'`) and from `{ tag: '@new' }` or `{ tag: ['@new', '@smoke'] }`.
26
31
  Tags on a `describe` apply to every test inside it.
27
32
 
28
33
  ## Marker format
29
34
 
30
35
  ```text
31
- // SKIP: WEB-123
32
- // SKIP: WEB-123, WEB-124
33
- /* FIXME: https://github.com/acme/web/issues/4821 */
36
+ // SKIP: TRADE-123
37
+ // SKIP: TRADE-123, TRADE-124
38
+ /* FIXME: https://github.com/acme/trading-engine/issues/4821 */
34
39
  ```
35
40
 
36
41
  - The marker goes in the comment block directly above the test, describe or `test.skip()` call.
37
42
  Other comments, such as `eslint-disable-next-line`, may sit in the same block. A blank line ends
38
43
  the block unless `allowBlankLine` is set.
39
- - The keyword is uppercase and followed by a colon. `// skip: WEB-1` is reported with a hint.
44
+ - The keyword is uppercase and followed by a colon. `// skip: TRADE-1` is reported with a hint.
40
45
  - After the colon come one or more tickets separated by commas, and nothing else. The ticket is the
41
46
  source of truth, so the reason for a skip belongs in the ticket, not in the comment.
42
- `// SKIP: WEB-123 flaky on CI` is reported, with a suggestion that removes the extra text. Teams
47
+ `// SKIP: TRADE-123 flaky on CI` is reported, with a suggestion that removes the extra text. Teams
43
48
  that want short notes can set `allowNotes: true`.
44
49
  - A marker on a skipped or tagged `describe` covers every test inside it.
45
50
  - A `test.skip()` inside a test body can have its marker above the call or above the test.
46
- - Ticket IDs in test titles (`'SDQA-52: logout'`) are never read as tickets.
51
+ - A skipped step, `test.step.skip(...)` or `step.skip()` in its body, can have its marker above the
52
+ call, above the step or above a skipped test around it. A `test.skip()` inside a step skips the
53
+ whole test, so its marker goes above the call or above the test.
54
+ - Ticket IDs in test titles (`'TRADE-52: closes all positions'`) are never read as tickets.
47
55
 
48
56
  How ticket lists are read:
49
57
 
50
- | Marker | Result |
51
- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
52
- | `// SKIP: WEB-1, WEB-2` or `// SKIP: WEB-1,WEB-2` | two tickets |
53
- | `// SKIP: WEB-1 , WEB-2 ` | two tickets; extra spaces are fine |
54
- | `// SKIP: https://acme.atlassian.net/browse/WEB-1?focusedCommentId=5#comment-5` | one ticket; query strings and fragments are part of a URL |
55
- | `// SKIP: WEB-1 flaky on CI`, `// SKIP: WEB-1: flaky` | extra text `flaky on CI` / `: flaky` |
56
- | `// SKIP: WEB-1.` or `// SKIP: WEB-1:` | one ticket; a full stop or colon at the end is fine |
57
- | `// SKIP: WEB-1,` | extra text `,` |
58
- | `// SKIP: WEB-1 and WEB-2` | extra text `and WEB-2`; separate tickets with commas |
59
- | `// SKIP: WEB-1;WEB-2`, `// SKIP: WEB-1/WEB-2` | invalid ticket, since only commas separate tickets |
60
- | `// SKIP: flaky on CI` | invalid ticket `flaky`: the first word must be a ticket |
58
+ | Marker | Result |
59
+ | --------------------------------------------------------------------------------- | --------------------------------------------------------- |
60
+ | `// SKIP: TRADE-1, TRADE-2` or `// SKIP: TRADE-1,TRADE-2` | two tickets |
61
+ | `// SKIP: TRADE-1 , TRADE-2 ` | two tickets; extra spaces are fine |
62
+ | `// SKIP: https://acme.atlassian.net/browse/TRADE-1?focusedCommentId=5#comment-5` | one ticket; query strings and fragments are part of a URL |
63
+ | `// SKIP: TRADE-1 flaky on CI`, `// SKIP: TRADE-1: flaky` | extra text `flaky on CI` / `: flaky` |
64
+ | `// SKIP: TRADE-1.` or `// SKIP: TRADE-1:` | one ticket; a full stop or colon at the end is fine |
65
+ | `// SKIP: TRADE-1,` | extra text `,` |
66
+ | `// SKIP: TRADE-1 and TRADE-2` | extra text `and TRADE-2`; separate tickets with commas |
67
+ | `// SKIP: TRADE-1;TRADE-2`, `// SKIP: TRADE-1/TRADE-2` | invalid ticket, since only commas separate tickets |
68
+ | `// SKIP: flaky on CI` | invalid ticket `flaky`: the first word must be a ticket |
61
69
 
62
70
  In a block comment, only the marker line is checked, so a JSDoc block can explain the skip on other
63
71
  lines. Extra text is reported only once the tickets on that line are valid.
@@ -66,11 +74,34 @@ Conditional skips such as `test.skip(browserName === 'webkit', 'Not supported')`
66
74
  permanent platform limit, so they need no ticket unless you set `requireTicketForConditional: true`.
67
75
  A runtime skip under an `if`, a `switch` case, a ternary or `&&` counts as conditional too, since
68
76
  `if (!enabled) test.skip()` does the same as `test.skip(!enabled)`. So does a skip in a `catch` block,
69
- such as `catch { test.skip(true, 'Mail server is down') }`, and a skip after an early exit such as
77
+ such as `catch { test.skip(true, 'Market data feed is down') }`, and a skip after an early exit such as
70
78
  `if (ready) return;` or `if (!ok) throw error;` earlier in the same function.
71
79
 
72
80
  If one test or describe has several markers for the same state, each must be valid: in
73
- `// SKIP: WEB-1` followed by `// SKIP: nope`, the second line is reported.
81
+ `// SKIP: TRADE-1` followed by `// SKIP: nope`, the second line is reported.
82
+
83
+ ## Dynamic titles
84
+
85
+ Tags can only be read from a title that is text: a string or a template literal such as
86
+ `` `buys ${symbol} @new` ``. A title built another way, such as `'buys ' + symbol` or a variable, may
87
+ hide a tag. With `reportDynamicTitles: true`, the rule reports those titles whenever a tag state
88
+ (`@new`, `@unstable` or a custom state) is on, so no tagged test slips past.
89
+
90
+ <!-- example: invalid settings={"lifecycleTags":true,"reportDynamicTitles":true} -->
91
+
92
+ ```js
93
+ for (const symbol of ['AAPL', 'MSFT']) {
94
+ test('buys ' + symbol, async ({ page }) => {});
95
+ }
96
+ ```
97
+
98
+ <!-- example: valid settings={"lifecycleTags":true,"reportDynamicTitles":true} -->
99
+
100
+ ```js
101
+ for (const symbol of ['AAPL', 'MSFT']) {
102
+ test(`buys ${symbol}`, async ({ page }) => {});
103
+ }
104
+ ```
74
105
 
75
106
  ## Examples
76
107
 
@@ -79,34 +110,40 @@ Incorrect:
79
110
  <!-- example: invalid -->
80
111
 
81
112
  ```js
82
- test.skip('pays with PayPal', async ({ page }) => {});
113
+ test.skip('rejects a margin order above the buying power', async ({ page }) => {});
83
114
  ```
84
115
 
85
116
  <!-- example: invalid -->
86
117
 
87
118
  ```js
88
119
  // SKIP: flaky on CI
89
- test.skip('pays with PayPal', async ({ page }) => {});
120
+ test.skip('rejects a margin order above the buying power', async ({ page }) => {});
90
121
  ```
91
122
 
92
123
  <!-- example: invalid -->
93
124
 
94
125
  ```js
95
126
  // SKIP: TODO
96
- test.skip('pays with PayPal', async ({ page }) => {});
127
+ test.skip('rejects a margin order above the buying power', async ({ page }) => {});
97
128
  ```
98
129
 
99
130
  <!-- example: invalid -->
100
131
 
101
132
  ```js
102
- // SKIP: WEB-481 payment sandbox is down
103
- test.skip('pays with PayPal', async ({ page }) => {});
133
+ // SKIP: TRADE-481 broker sandbox is down
134
+ test.skip('rejects a margin order above the buying power', async ({ page }) => {});
104
135
  ```
105
136
 
106
137
  <!-- example: invalid settings={"lifecycleTags":true} -->
107
138
 
108
139
  ```js
109
- test('one-click reorder @new', async ({ page }) => {});
140
+ test('closes all positions with one click @new', async ({ page }) => {});
141
+ ```
142
+
143
+ <!-- example: invalid settings={"framework":"jest"} -->
144
+
145
+ ```js
146
+ it.todo('caps position size at 2% of equity');
110
147
  ```
111
148
 
112
149
  Correct:
@@ -114,24 +151,24 @@ Correct:
114
151
  <!-- example: valid -->
115
152
 
116
153
  ```js
117
- // SKIP: WEB-481
118
- test.skip('pays with PayPal', async ({ page }) => {});
154
+ // SKIP: TRADE-481
155
+ test.skip('rejects a margin order above the buying power', async ({ page }) => {});
119
156
  ```
120
157
 
121
158
  <!-- example: valid -->
122
159
 
123
160
  ```js
124
- // FIXME: WEB-12
125
- test.describe.fixme('tax rules', () => {
126
- test('per region', async () => {});
127
- test('per product', async () => {});
161
+ // FIXME: TRADE-12
162
+ test.describe.fixme('risk limits', () => {
163
+ test('stops trading at the daily loss limit', async () => {});
164
+ test('caps position size at 2% of equity', async () => {});
128
165
  });
129
166
  ```
130
167
 
131
168
  <!-- example: valid -->
132
169
 
133
170
  ```js
134
- test('opens the dashboard', async ({ page, browserName }) => {
171
+ test('draws the price chart', async ({ page, browserName }) => {
135
172
  test.skip(browserName === 'webkit', 'Layout differs on WebKit by design');
136
173
  });
137
174
  ```
@@ -139,18 +176,41 @@ test('opens the dashboard', async ({ page, browserName }) => {
139
176
  <!-- example: valid settings={"allowNotes":true} -->
140
177
 
141
178
  ```js
142
- // SKIP: WEB-481 payment sandbox is down
143
- test.skip('pays with PayPal', async ({ page }) => {});
179
+ // SKIP: TRADE-481 broker sandbox is down
180
+ test.skip('rejects a margin order above the buying power', async ({ page }) => {});
144
181
  ```
145
182
 
146
183
  <!-- example: valid settings={"lifecycleTags":true} -->
147
184
 
148
185
  ```js
149
- // NEW: WEB-77
150
- test('one-click reorder', { tag: '@new' }, async ({ page }) => {});
186
+ // NEW: TRADE-77
187
+ test('closes all positions with one click', { tag: '@new' }, async ({ page }) => {});
188
+ ```
189
+
190
+ <!-- example: valid settings={"framework":"vitest"} -->
191
+
192
+ ```js
193
+ import { describe, test } from 'vitest';
194
+
195
+ describe('risk limits', () => {
196
+ // TODO: TRADE-90
197
+ test.todo('caps position size at 2% of equity');
198
+
199
+ test.skipIf(process.env.CI)('streams live quotes', () => {});
200
+ });
151
201
  ```
152
202
 
153
203
  ## Options
154
204
 
155
205
  This rule has no options of its own. It reads the shared options described in the
156
- [README](../../README.md#options), usually set once with `testGovernance.configure({...})`.
206
+ [README](../../README.md#options), usually set once with `testGovernance.configure({...})`. These
207
+ change what it reports:
208
+
209
+ - `ticket` and `placeholders`: which tickets are accepted.
210
+ - `lifecycleTags`, `states` and `customStates`: which states need a marker, what the marker is
211
+ called, and, for a state with its own `ticket`, which tickets it accepts.
212
+ - `requireTicketForConditional`: conditional skips need a ticket too.
213
+ - `allowBlankLine`: blank lines may separate a marker from its test.
214
+ - `allowNotes`: text may follow the tickets.
215
+ - `reportDynamicTitles`: report titles whose tags can't be read (see [Dynamic titles](#dynamic-titles)).
216
+ - `framework` and `testFunctions`: which functions count as the framework's `test`.
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "eslint-plugin-test-governance",
3
- "version": "0.3.0",
4
- "description": "ESLint rules that require a ticket comment above skipped, fixme and tagged Playwright tests.",
3
+ "version": "0.4.0",
4
+ "description": "ESLint rules that require a ticket comment above skipped, fixme, todo and tagged Playwright, Jest and Vitest tests.",
5
5
  "keywords": [
6
6
  "eslint",
7
7
  "eslintplugin",
8
8
  "eslint-plugin",
9
9
  "playwright",
10
+ "jest",
11
+ "vitest",
10
12
  "testing"
11
13
  ],
12
14
  "homepage": "https://github.com/Huldoser/eslint-plugin-test-governance#readme",
@@ -35,7 +37,6 @@
35
37
  "types": "./dist/index.d.ts",
36
38
  "files": [
37
39
  "dist",
38
- "!dist/**/*.map",
39
40
  "docs",
40
41
  "CHANGELOG.md"
41
42
  ],
@@ -45,8 +46,8 @@
45
46
  "scripts": {
46
47
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
47
48
  "typecheck": "tsc -p tsconfig.json",
48
- "test": "vitest run",
49
- "test:coverage": "vitest run --coverage",
49
+ "test": "node --test 'tests/**/*.test.ts'",
50
+ "test:coverage": "node --test --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-branches=100 --test-coverage-functions=100 --test-coverage-lines=100 'tests/**/*.test.ts'",
50
51
  "test:pack": "node scripts/pack-smoke.mjs",
51
52
  "docs": "npm run build && eslint-doc-generator",
52
53
  "docs:check": "npm run build && eslint-doc-generator --check",
@@ -63,15 +64,11 @@
63
64
  "@types/node": "^26.6.3",
64
65
  "@typescript-eslint/parser": "^8.71.0",
65
66
  "@typescript-eslint/utils": "^8.71.0",
66
- "@vitest/coverage-v8": "^5.0.2",
67
67
  "eslint": "^10.11.0",
68
- "eslint-config-prettier": "^10.1.8",
69
68
  "eslint-doc-generator": "^3.7.1",
70
69
  "eslint-plugin-eslint-plugin": "^7.6.2",
71
- "globals": "^17.13.0",
72
70
  "prettier": "^3.9.9",
73
71
  "typescript": "6.0",
74
- "typescript-eslint": "^8.71.0",
75
- "vitest": "^5.0.2"
72
+ "typescript-eslint": "^8.71.0"
76
73
  }
77
74
  }