jest-doctor 0.0.3 → 0.0.4
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/dist/createEnvMixin.d.ts +1 -0
- package/dist/createEnvMixin.js +7 -2
- package/dist/env/jsdom.d.ts +1 -0
- package/dist/env/node.d.ts +1 -0
- package/dist/patch/console.js +1 -1
- package/dist/patch/fakeTimers.js +2 -2
- package/dist/patch/promise.js +1 -1
- package/dist/patch/promiseConcurrency.d.ts +3 -0
- package/dist/patch/promiseConcurrency.js +26 -0
- package/dist/patch/race.d.ts +1 -0
- package/dist/patch/race.js +2 -0
- package/dist/patch/timers.js +2 -2
- package/dist/reporter.js +0 -1
- package/dist/requireEnvironment.d.ts +1 -0
- package/dist/types.d.ts +4 -1
- package/dist/utils/createAsyncHookCleaner.js +6 -2
- package/dist/utils/createAsyncHookDetector.js +5 -3
- package/dist/utils/getStack.d.ts +1 -1
- package/dist/utils/getStack.js +2 -2
- package/dist/utils/normalizeOptions.js +2 -0
- package/dist/utils/reportLeaks.js +13 -0
- package/package.json +1 -1
package/dist/createEnvMixin.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ declare const createEnvMixin: <EnvironmentConstructor extends JestDoctorConstruc
|
|
|
55
55
|
totalDelay: number;
|
|
56
56
|
};
|
|
57
57
|
tearDownError?: Error;
|
|
58
|
+
asyncIdToPromise: Map<number, Promise<unknown>>;
|
|
58
59
|
setup(): Promise<void>;
|
|
59
60
|
handleEvent(event: unknown, state: unknown): Promise<void>;
|
|
60
61
|
handleTestEvent: JestEnvironment["handleTestEvent"];
|
package/dist/createEnvMixin.js
CHANGED
|
@@ -22,6 +22,7 @@ const getAllAfterEach_1 = __importDefault(require("./utils/getAllAfterEach"));
|
|
|
22
22
|
const normalizeOptions_1 = __importDefault(require("./utils/normalizeOptions"));
|
|
23
23
|
const getReporterTmpDir_1 = __importDefault(require("./utils/getReporterTmpDir"));
|
|
24
24
|
const promise_1 = __importDefault(require("./patch/promise"));
|
|
25
|
+
const promiseConcurrency_1 = __importDefault(require("./patch/promiseConcurrency"));
|
|
25
26
|
const createEnvMixin = (Environment) => {
|
|
26
27
|
// @ts-expect-error strange ts rule where constructor arguments should be any[] where again TypeScript complains
|
|
27
28
|
return class Base extends Environment {
|
|
@@ -38,6 +39,7 @@ const createEnvMixin = (Environment) => {
|
|
|
38
39
|
testPath;
|
|
39
40
|
aggregatedReport;
|
|
40
41
|
tearDownError;
|
|
42
|
+
asyncIdToPromise = new Map();
|
|
41
43
|
constructor(config, context) {
|
|
42
44
|
super(config, context);
|
|
43
45
|
this.testPath = context.testPath.replace(/\W/g, '_');
|
|
@@ -72,8 +74,11 @@ const createEnvMixin = (Environment) => {
|
|
|
72
74
|
if (report.console) {
|
|
73
75
|
(0, console_1.default)(this, report.console);
|
|
74
76
|
}
|
|
75
|
-
if (report.promises
|
|
76
|
-
(0,
|
|
77
|
+
if (report.promises) {
|
|
78
|
+
(0, promiseConcurrency_1.default)(this);
|
|
79
|
+
if (report.promises.patch === 'promise') {
|
|
80
|
+
(0, promise_1.default)(this);
|
|
81
|
+
}
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
// unfortunately @jest/types doesn't export the necessary types,
|
package/dist/env/jsdom.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ declare const _default: {
|
|
|
33
33
|
totalDelay: number;
|
|
34
34
|
};
|
|
35
35
|
tearDownError?: Error;
|
|
36
|
+
asyncIdToPromise: Map<number, Promise<unknown>>;
|
|
36
37
|
setup(): Promise<void>;
|
|
37
38
|
handleEvent(event: unknown, state: unknown): Promise<void>;
|
|
38
39
|
handleTestEvent: import("@jest/environment").JestEnvironment["handleTestEvent"];
|
package/dist/env/node.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ declare const _default: {
|
|
|
33
33
|
totalDelay: number;
|
|
34
34
|
};
|
|
35
35
|
tearDownError?: Error;
|
|
36
|
+
asyncIdToPromise: Map<number, Promise<unknown>>;
|
|
36
37
|
setup(): Promise<void>;
|
|
37
38
|
handleEvent(event: unknown, state: unknown): Promise<void>;
|
|
38
39
|
handleTestEvent: import("@jest/environment").JestEnvironment["handleTestEvent"];
|
package/dist/patch/console.js
CHANGED
|
@@ -24,7 +24,7 @@ const patchConsole = (that, consoleOptions) => {
|
|
|
24
24
|
if (!(0, exports.isIgnored)(message, consoleOptions.ignore)) {
|
|
25
25
|
that.leakRecords.get(that.currentTestName)?.console.push({
|
|
26
26
|
method: consoleMethod,
|
|
27
|
-
stack: (0, getStack_1.default)(env.console[consoleMethod]),
|
|
27
|
+
stack: (0, getStack_1.default)(env.console[consoleMethod], 'Console.' + consoleMethod),
|
|
28
28
|
testName: that.currentTestName,
|
|
29
29
|
message,
|
|
30
30
|
});
|
package/dist/patch/fakeTimers.js
CHANGED
|
@@ -30,7 +30,7 @@ const patchFakeTimers = (that) => {
|
|
|
30
30
|
fakeTimeout?.set(timerId, {
|
|
31
31
|
type: 'fakeTimeout',
|
|
32
32
|
delay: delay || 0,
|
|
33
|
-
stack: (0, getStack_1.default)(that.global.setTimeout),
|
|
33
|
+
stack: (0, getStack_1.default)(that.global.setTimeout, 'setTimeout'),
|
|
34
34
|
testName: that.currentTestName,
|
|
35
35
|
});
|
|
36
36
|
return timerId;
|
|
@@ -40,7 +40,7 @@ const patchFakeTimers = (that) => {
|
|
|
40
40
|
that.leakRecords.get(that.currentTestName)?.fakeTimers.set(intervalId, {
|
|
41
41
|
type: 'fakeInterval',
|
|
42
42
|
delay: delay || 0,
|
|
43
|
-
stack: (0, getStack_1.default)(that.global.setInterval),
|
|
43
|
+
stack: (0, getStack_1.default)(that.global.setInterval, 'setInterval'),
|
|
44
44
|
testName: that.currentTestName,
|
|
45
45
|
});
|
|
46
46
|
return intervalId;
|
package/dist/patch/promise.js
CHANGED
|
@@ -17,7 +17,7 @@ const patchPromise = (that) => {
|
|
|
17
17
|
const promiseLeaks = that.leakRecords.get(that.currentTestName)?.promises;
|
|
18
18
|
promiseLeaks?.set(this, {
|
|
19
19
|
testName: that.currentTestName,
|
|
20
|
-
stack: (0, getStack_1.default)(that.global.Promise),
|
|
20
|
+
stack: (0, getStack_1.default)(that.global.Promise, 'Promise'),
|
|
21
21
|
});
|
|
22
22
|
executor((value) => {
|
|
23
23
|
promiseLeaks?.delete(this);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const patchPromiseConcurrency = (that) => {
|
|
4
|
+
const env = that.global;
|
|
5
|
+
const concurrencyFactor = (fn) => (concurrentPromises) => {
|
|
6
|
+
const promises = that.leakRecords.get(that.currentTestName)?.promises;
|
|
7
|
+
// remove all related promises is a concurrent promise ends
|
|
8
|
+
const removePromises = () => {
|
|
9
|
+
if (promises) {
|
|
10
|
+
concurrentPromises.forEach((racePromise) => {
|
|
11
|
+
promises.delete(racePromise);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return fn(concurrentPromises).then((returnValues) => {
|
|
16
|
+
removePromises();
|
|
17
|
+
return returnValues;
|
|
18
|
+
}, (error) => {
|
|
19
|
+
removePromises();
|
|
20
|
+
throw error;
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
env.Promise.race = concurrencyFactor(env.Promise.race.bind(env.Promise));
|
|
24
|
+
env.Promise.any = concurrencyFactor(env.Promise.any.bind(env.Promise));
|
|
25
|
+
};
|
|
26
|
+
exports.default = patchPromiseConcurrency;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare const patchRace: () => void;
|
package/dist/patch/timers.js
CHANGED
|
@@ -22,7 +22,7 @@ const timers = (that) => {
|
|
|
22
22
|
leakRecord?.timers.set(timerId, {
|
|
23
23
|
type: 'timeout',
|
|
24
24
|
delay: delay || 0,
|
|
25
|
-
stack: (0, getStack_1.default)(env.setTimeout),
|
|
25
|
+
stack: (0, getStack_1.default)(env.setTimeout, 'fake setTimeout'),
|
|
26
26
|
testName: owner,
|
|
27
27
|
});
|
|
28
28
|
return timerId;
|
|
@@ -41,7 +41,7 @@ const timers = (that) => {
|
|
|
41
41
|
that.leakRecords.get(owner)?.timers.set(intervalId, {
|
|
42
42
|
type: 'interval',
|
|
43
43
|
delay: delay || 0,
|
|
44
|
-
stack: (0, getStack_1.default)(env.setInterval),
|
|
44
|
+
stack: (0, getStack_1.default)(env.setInterval, 'fake setInterval'),
|
|
45
45
|
testName: that.currentTestName,
|
|
46
46
|
});
|
|
47
47
|
return intervalId;
|
package/dist/reporter.js
CHANGED
|
@@ -34,6 +34,7 @@ declare const requireEnvironment: (envName: string) => {
|
|
|
34
34
|
totalDelay: number;
|
|
35
35
|
};
|
|
36
36
|
tearDownError?: Error;
|
|
37
|
+
asyncIdToPromise: Map<number, Promise<unknown>>;
|
|
37
38
|
setup(): Promise<void>;
|
|
38
39
|
handleEvent(event: unknown, state: unknown): Promise<void>;
|
|
39
40
|
handleTestEvent: import("@jest/environment").JestEnvironment["handleTestEvent"];
|
package/dist/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface ConsoleRecord {
|
|
|
18
18
|
message: string;
|
|
19
19
|
}
|
|
20
20
|
export interface LeakRecord {
|
|
21
|
-
promises: Map<
|
|
21
|
+
promises: Map<Promise<unknown>, PromiseRecord>;
|
|
22
22
|
timers: Map<NodeJS.Timeout, TimerRecord>;
|
|
23
23
|
console: ConsoleRecord[];
|
|
24
24
|
totalDelay: number;
|
|
@@ -58,6 +58,7 @@ export interface NormalizedOptions {
|
|
|
58
58
|
patch: Patch;
|
|
59
59
|
};
|
|
60
60
|
};
|
|
61
|
+
verbose: boolean;
|
|
61
62
|
delayThreshold: number;
|
|
62
63
|
timerIsolation: TimerIsolation;
|
|
63
64
|
clearTimers: boolean;
|
|
@@ -78,6 +79,7 @@ export interface RawOptions {
|
|
|
78
79
|
fakeTimers?: OnError;
|
|
79
80
|
promises?: RawPromise;
|
|
80
81
|
};
|
|
82
|
+
verbose?: boolean;
|
|
81
83
|
delayThreshold?: number;
|
|
82
84
|
timerIsolation?: TimerIsolation;
|
|
83
85
|
clearTimers?: boolean;
|
|
@@ -100,5 +102,6 @@ export interface JestDoctorEnvironment {
|
|
|
100
102
|
currentAfterEachCount: number;
|
|
101
103
|
options: NormalizedOptions;
|
|
102
104
|
aggregatedReport: AggregatedReport;
|
|
105
|
+
asyncIdToPromise: Map<number, Promise<unknown>>;
|
|
103
106
|
}
|
|
104
107
|
export {};
|
|
@@ -7,8 +7,12 @@ const createAsyncHookCleaner = (that) => {
|
|
|
7
7
|
const owner = that.promiseOwner.get(asyncId);
|
|
8
8
|
if (!owner)
|
|
9
9
|
return;
|
|
10
|
-
that.
|
|
11
|
-
|
|
10
|
+
const promise = that.asyncIdToPromise.get(asyncId);
|
|
11
|
+
if (promise) {
|
|
12
|
+
that.leakRecords.get(owner)?.promises.delete(promise);
|
|
13
|
+
that.promiseOwner.delete(asyncId);
|
|
14
|
+
that.asyncIdToPromise.delete(asyncId);
|
|
15
|
+
}
|
|
12
16
|
},
|
|
13
17
|
});
|
|
14
18
|
};
|
|
@@ -6,13 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
7
7
|
const getStack_1 = __importDefault(require("./getStack"));
|
|
8
8
|
const createAsyncHookDetector = (that) => {
|
|
9
|
-
const init = (asyncId, type) => {
|
|
9
|
+
const init = (asyncId, type, _, resource) => {
|
|
10
10
|
if (type !== 'PROMISE')
|
|
11
11
|
return;
|
|
12
|
+
const promise = resource;
|
|
12
13
|
const owner = that.currentTestName;
|
|
13
14
|
that.promiseOwner.set(asyncId, owner);
|
|
14
|
-
that.
|
|
15
|
-
|
|
15
|
+
that.asyncIdToPromise.set(asyncId, promise);
|
|
16
|
+
that.leakRecords.get(owner)?.promises.set(promise, {
|
|
17
|
+
stack: (0, getStack_1.default)(init, 'Promise'),
|
|
16
18
|
testName: owner,
|
|
17
19
|
});
|
|
18
20
|
};
|
package/dist/utils/getStack.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const getStack: (stackFrom: Function) => string;
|
|
1
|
+
declare const getStack: (stackFrom: Function, prefix: string) => string;
|
|
2
2
|
export default getStack;
|
package/dist/utils/getStack.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const getStack = (stackFrom) => {
|
|
3
|
+
const getStack = (stackFrom, prefix) => {
|
|
4
4
|
const error = {
|
|
5
5
|
stack: '',
|
|
6
6
|
};
|
|
7
7
|
Error.captureStackTrace(error, stackFrom);
|
|
8
|
-
return error.stack;
|
|
8
|
+
return prefix + ' ' + error.stack;
|
|
9
9
|
};
|
|
10
10
|
exports.default = getStack;
|
|
@@ -15,6 +15,7 @@ const DEFAULTS = {
|
|
|
15
15
|
patch: 'async_hooks',
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
|
+
verbose: false,
|
|
18
19
|
delayThreshold: 0,
|
|
19
20
|
timerIsolation: 'afterEach',
|
|
20
21
|
clearTimers: true,
|
|
@@ -57,6 +58,7 @@ function normalizeOptions(raw) {
|
|
|
57
58
|
fakeTimers: report.fakeTimers ?? DEFAULTS.report.fakeTimers,
|
|
58
59
|
promises: normalizePromise(report.promises),
|
|
59
60
|
},
|
|
61
|
+
verbose: raw.verbose ?? DEFAULTS.verbose,
|
|
60
62
|
delayThreshold: raw.delayThreshold ?? DEFAULTS.delayThreshold,
|
|
61
63
|
timerIsolation: raw.timerIsolation ?? DEFAULTS.timerIsolation,
|
|
62
64
|
clearTimers: raw.clearTimers ?? DEFAULTS.clearTimers,
|
|
@@ -33,6 +33,19 @@ const reportLeaks = (that, leakRecord) => {
|
|
|
33
33
|
that.aggregatedReport.fakeTimers += leakRecord.fakeTimers.size;
|
|
34
34
|
that.aggregatedReport.totalDelay += leakRecord.totalDelay;
|
|
35
35
|
}
|
|
36
|
+
if (that.options.verbose) {
|
|
37
|
+
const messages = [];
|
|
38
|
+
const addLeak = (leak) => {
|
|
39
|
+
messages.push(leak.stack);
|
|
40
|
+
};
|
|
41
|
+
leakRecord.promises.forEach(addLeak);
|
|
42
|
+
leakRecord.timers.forEach(addLeak);
|
|
43
|
+
leakRecord.fakeTimers.forEach(addLeak);
|
|
44
|
+
leakRecord.console.forEach(addLeak);
|
|
45
|
+
if (messages.length) {
|
|
46
|
+
node_console_1.default.log(messages.join('\n'));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
36
49
|
try {
|
|
37
50
|
if (leakRecord.console.length) {
|
|
38
51
|
const message = [
|