jest-doctor 0.0.1

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.
@@ -0,0 +1,2 @@
1
+ declare const getStack: (stackFrom: Function) => string;
2
+ export default getStack;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const getStack = (stackFrom) => {
4
+ const error = {
5
+ stack: '',
6
+ };
7
+ Error.captureStackTrace(error, stackFrom);
8
+ return error.stack;
9
+ };
10
+ exports.default = getStack;
11
+ //# sourceMappingURL=getStack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getStack.js","sourceRoot":"","sources":["../../src/utils/getStack.ts"],"names":[],"mappings":";;AAAA,MAAM,QAAQ,GAAG,CAAC,SAAmB,EAAE,EAAE;IACvC,MAAM,KAAK,GAAG;QACZ,KAAK,EAAE,EAAE;KACV,CAAC;IAEF,KAAK,CAAC,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAE1C,OAAO,KAAK,CAAC,KAAK,CAAC;AACrB,CAAC,CAAC;AAEF,kBAAe,QAAQ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "jest-doctor",
3
+ "version": "0.0.1",
4
+ "main": "./dist/index.js",
5
+ "description": "jest environment for leak detection",
6
+ "license": "MIT",
7
+ "type": "commonjs",
8
+ "homepage": "https://github.com/stephan-dum/jest-doctor",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/stephan-dum/jest-doctor.git"
12
+ },
13
+ "exports": {
14
+ "./package.json": "./package.json",
15
+ "./createEnvMixin": "./dist/createEnvMixin.js",
16
+ "./require": "./dist/requireEnvironment.js",
17
+ "./env/node": "./dist/env/node.js",
18
+ "./env/jsdom": "./dist/env/jsdom.js",
19
+ "./reporter": "./dist/reporter.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "readme.md"
24
+ ],
25
+ "types": "./dist/types.d.ts",
26
+ "devDependencies": {
27
+ "@dev/eslint": "1.0.0",
28
+ "@dev/tsconfig": "1.0.0",
29
+ "@jest/reporters": "30.2.0",
30
+ "@swc/core": "^1.15.8",
31
+ "@swc/jest": "^0.2.39",
32
+ "@testing-library/dom": "^10.4.1",
33
+ "@testing-library/jest-dom": "^6.9.1",
34
+ "@testing-library/react": "^16.3.1",
35
+ "@types/cross-spawn": "^6.0.6",
36
+ "@types/jest": "30.0.0",
37
+ "@types/node": "24.3.3",
38
+ "@types/react": "^19.2.8",
39
+ "@types/react-dom": "^19.2.3",
40
+ "c8": "^10.1.3",
41
+ "cross-env": "^10.1.0",
42
+ "cross-spawn": "^7.0.6",
43
+ "eslint": "^9.39.2",
44
+ "istanbul-lib-report": "^3.0.1",
45
+ "jest": "30.2.0",
46
+ "memfs": "^4.52.0",
47
+ "nyc": "^17.1.0",
48
+ "react": "^19.2.3",
49
+ "react-dom": "^19.2.3",
50
+ "ts-jest": "^29.4.6",
51
+ "typescript": "5.9.2"
52
+ },
53
+ "dependencies": {
54
+ "chalk": "4.1.2",
55
+ "jest-environment-jsdom": "30.2.0",
56
+ "jest-environment-node": "30.2.0"
57
+ },
58
+ "scripts": {
59
+ "typecheck": "tsc",
60
+ "build": "tsc --project tsconfig.build.json",
61
+ "dev": "yarn typecheck --watch & yarn build --watch",
62
+ "lint": "eslint .",
63
+ "test:unit": "jest --coverage",
64
+ "test:matrix": "node ./e2e/runMatrix.mjs",
65
+ "test": "yarn matrix && yarn test:unit && yarn coverage:merge",
66
+ "coverage:merge": "nyc report --reporter html --reporter text"
67
+ }
68
+ }
package/readme.md ADDED
@@ -0,0 +1,142 @@
1
+ # jest-doctor
2
+
3
+ **jest-doctor** is a custom Jest environment that **detects async leaks and test isolation bugs**.
4
+
5
+ It fails tests deterministically when they:
6
+ - Leave unresolved Promises
7
+ - leave open (fake) timers or intervals
8
+ - Use real timers or intervals and their total delay reach a certain threshold
9
+ - Emit console output
10
+
11
+ Jest-doctor is intentionally strict. If your test leaks async work, that is a bug, even if Jest normally ignores it.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install --save-dev jest-doctor
17
+ ```
18
+ or
19
+ ```bash
20
+ yarn add -D jest-doctor
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ Add one of the provided environments to your `jest.config.js`, in addition, the reporter provides a list ordered by severity and a total count overview.
26
+
27
+ ```js
28
+ export default {
29
+ testEnvironment: 'jest-doctor/env/node',
30
+ // optional
31
+ reporters: ['default', 'jest-doctor/reporter']
32
+ };
33
+ ```
34
+
35
+ Out-of-the-box jest-doctor supports node and jsdom environments. But you can also [build your own environment](./docs/build_your_own_environment.md).
36
+
37
+ ### Configuration
38
+
39
+ The environment can be configured through jest config `testEnvironmentOptions`:
40
+ - **report**: an object defining which leaks should be tracked and reported
41
+ - **timers**: `false | 'warn' | 'trow'` (default `throw`) whether normal setTimeout and setInterval should be reported and how
42
+ - **fakeTimers**: `false | 'warn' | 'trow'` (default `throw`) same as timers but for fake api
43
+ - **promises**: `false` or object
44
+ - **onError**: `'warn' | 'trow'` (default `throw`) indicating if promises should be reported and how
45
+ - **patch**: `'async_hooks' | 'promise'` (default `async_hooks`) controls how to patch promises
46
+ - **async_hooks**: uses node async_hook API to detect any promise generation, robust solution but with an overhead.
47
+ - **promise**: overwrites the global Promise object which is faster but will not detect: internal promises, async await, native API promises!
48
+ - **console**: `false` or object (default object)
49
+ - **onError**: `'warn' | 'trow'` (default `throw`) how to handle reporting
50
+ - **methods**: `keyof Console` (default all methods) which console methods should be tracked
51
+ - **ignore**: `string | regexp | Array<string | regexp>` (default: []) allows to excluded console output from tracking
52
+ - **timerIsolation**: `'afterEach' | 'immediate'` (default: `'afterEach'`)
53
+ - **immediate**: report and clear timers directly after each test / hook block
54
+ - **afterEach**: `beforeAll`, `beforeEach` and `afterAll` are immediate but `test` and `afterEach` block defer reporting and cleanup until the last `afterEach` block is executed (or directly after the test if there are no `afterEach` blocks). This allows an easier clean up for example react testing framework registers an unmount function in an `afterEach` block to clean up.
55
+ - **delayThreshold**: `number` (default: `0`) the delay in milliseconds of all `setTimeout` and `setInterval` callback that get executed is add up. If this the sum is higher then the threshold at the end when reporting an error is throw, otherwise a warning is logged.
56
+ - **clearTimers**: `boolean` (default: `true`) whether to clear all timers base on `timerIsolation`
57
+
58
+ here is an example how the configuration could look like:
59
+ ```js
60
+ export default {
61
+ testEnvironmentOptions: {
62
+ report: {
63
+ console: {
64
+ onError: 'warn',
65
+ methods: ["log", "warn", "error"],
66
+ ignore: /Third party message/,
67
+ },
68
+ timers: 'warn',
69
+ fakeTimers: 'warn',
70
+ promises: {
71
+ onErro: 'warn',
72
+ },
73
+ },
74
+ delayThreshold: 1000,
75
+ timerIsolation: 'afterEach',
76
+ clearTimers: true,
77
+ },
78
+ };
79
+ ```
80
+
81
+ the reporter can be configured by the standard jest reporter config syntax
82
+ - **tmpDir**: `string` (default: `.tmp`) where to store reports from the environment to be read by the reporter
83
+
84
+ ```js
85
+ export default {
86
+ reporters: [
87
+ 'default',
88
+ ['jest-doctor/reporter', { tmpDir: 'custom-dir' }]
89
+ ],
90
+ }
91
+ ```
92
+
93
+ ## Limitations
94
+ - it.concurrent is replaced with a sync version
95
+ - legacy fake timers are not mocked
96
+ - test and hook blocks do not support done callback or generators
97
+ - promises that resolve within the next tick cannot be tracked, for example:
98
+ ```js
99
+ Promise.resolve().then(() => {
100
+ /* i am not tracked as unresolved */
101
+ });
102
+ ```
103
+ - injectGlobals must be used for totalDelay and test attribution to work, because imports from jest can not be patched! Also this could give a wrong sense of confidence because one test could have open timers or promises that resolve while running other tests and are not present in the final report.
104
+ ```js
105
+ import { expect, it, describe, beforeEach /*...*/ } from '@jest/globals';
106
+ ```
107
+
108
+ - console, setTimeout / setInterval can also be imported and will not participate in leak detection in these cases, but this can also serve as exit hatch if needed.
109
+ ```js
110
+ import { setTimeout, setInterval } from 'node:timers';
111
+ import console from 'node:console';
112
+ ```
113
+
114
+ ## Recommendations
115
+ - use eslint to
116
+ - detect floating promises
117
+ - disallow setTimeout / setInterval in test files
118
+ - disallow console usage
119
+ - enable fake timers globally in config (be aware that there might be some issues ie axe needs real timers)
120
+ ```js
121
+ afterEach(async () => {
122
+ jest.useRealTimers();
123
+ await axe();
124
+ jest.useFakeTimers();
125
+ });
126
+ ```
127
+
128
+ ## Tested against
129
+ - Jest 27, 28, 29, 30
130
+ - node 22, 24
131
+
132
+ # FAQ
133
+
134
+ ### Why is jest-doctor so strict?
135
+ Because flaky tests cost more than broken builds.
136
+
137
+ ### Does this slow tests down?
138
+ Slightly. Overhead is intentional and bounded.
139
+
140
+ ### Why does console output fail tests?
141
+ It pollutes the console and is often a strong indicator that something is wrong.
142
+ Tests should always spy on console and assert on the output.