fail-on-console 1.0.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 +8 -0
- package/README.md +120 -0
- package/index.d.ts +79 -0
- package/index.js +117 -0
- package/package.json +28 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Fail on Console
|
|
2
|
+
|
|
3
|
+
Prevent console noise from obscuring test results.
|
|
4
|
+
|
|
5
|
+
This utility fails tests whenever unexpected `console` logs, warnings, or errors are triggered. It is compatible with Vitest and Jest.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Framework Agnostic**: Integrates with Vitest, Jest, or any framework exposing standard lifecycle hooks and `expect.getState()`.
|
|
10
|
+
- **Zero Dependencies**: Pure, lightweight JavaScript with a minimal footprint.
|
|
11
|
+
- **Browser Ready**: Contains no Node.js API dependencies, allowing it to run inside Vitest Browser Mode.
|
|
12
|
+
- **Configurable Targets**: Allows explicit selection of which console methods to monitor.
|
|
13
|
+
- **Flexible Allowlist**: Suppresses known or expected console outputs globally, per suite, or per individual test. Exceptions can be matched against strings, regular expressions, or custom predicate functions.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
# pnpm
|
|
19
|
+
pnpm add --save-dev fail-on-console
|
|
20
|
+
|
|
21
|
+
# npm
|
|
22
|
+
npm install --save-dev fail-on-console
|
|
23
|
+
|
|
24
|
+
# yarn
|
|
25
|
+
yarn add --dev fail-on-console
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
`setup` should be called once in a global setup file, accepting the lifecycle hooks and `expect` utility from the testing framework.
|
|
31
|
+
|
|
32
|
+
### With Vitest
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {beforeEach, afterEach, expect} from "vitest";
|
|
36
|
+
import {setup} from "fail-on-console";
|
|
37
|
+
|
|
38
|
+
setup({beforeEach, afterEach, expect});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### With Jest
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import {beforeEach, afterEach, expect} from "@jest/globals";
|
|
45
|
+
import {setup} from "fail-on-console";
|
|
46
|
+
|
|
47
|
+
setup({beforeEach, afterEach, expect});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Customizing Monitored Methods
|
|
51
|
+
|
|
52
|
+
By default, `debug` is not monitored but `error`, `warn`, `info`, and `log` are. This can be customized by passing a `methods` array:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
setup({
|
|
56
|
+
beforeEach,
|
|
57
|
+
afterEach,
|
|
58
|
+
expect,
|
|
59
|
+
// Fail on console.error, console.warn and console.debug.
|
|
60
|
+
methods: ["error", "warn", "debug"]
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Suppressing Expected Logs
|
|
65
|
+
|
|
66
|
+
If a specific test or third-party dependency intentionally logs to the console, `allowConsole` can be used to allow the test to pass.
|
|
67
|
+
|
|
68
|
+
`allowConsole` can be invoked globally, inside a `describe` block, or inside a specific `test`/`it` block.
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
import {allowConsole} from "fail-on-console";
|
|
72
|
+
|
|
73
|
+
// Allow a substring.
|
|
74
|
+
allowConsole("warn", "third-party library warning");
|
|
75
|
+
|
|
76
|
+
// Allow a Regular Expression.
|
|
77
|
+
allowConsole("error", /^Warning: Each child in a list/);
|
|
78
|
+
|
|
79
|
+
// Allow with a custom predicate function
|
|
80
|
+
allowConsole("log", (message) => message.startsWith("[analytics]"));
|
|
81
|
+
|
|
82
|
+
// An array of mixed matchers
|
|
83
|
+
allowConsole("error", ["known warning", /deprecated/, (msg) => msg.includes("third-party")]);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### `setup(options)`
|
|
89
|
+
|
|
90
|
+
Initializes console spies that monitor active tests.
|
|
91
|
+
|
|
92
|
+
- `options.beforeEach`: The framework's `beforeEach` hook.
|
|
93
|
+
- `options.afterEach`: The framework's `afterEach` hook.
|
|
94
|
+
- `options.expect`: The framework's `expect` object (must expose `getState()`).
|
|
95
|
+
- `options.methods`: _(Optional)_ Array of `console` methods to track. Defaults to `["error", "warn", "info", "log"]`.
|
|
96
|
+
|
|
97
|
+
### `allowConsole(method, rules)`
|
|
98
|
+
|
|
99
|
+
Registers a temporary or global allowlist rule for a monitored console method.
|
|
100
|
+
|
|
101
|
+
- `method`: `"error" | "warn" | "info" | "log" | "debug"`
|
|
102
|
+
- `rules`: A single rule or an array of rules. A rule can be:
|
|
103
|
+
- `string`: Allowed if the console message contains this substring.
|
|
104
|
+
- `RegExp`: Allowed if the regex tests true against the message.
|
|
105
|
+
- `Function`: A predicate `(message: string) => boolean` returning `true` to allow the message.
|
|
106
|
+
|
|
107
|
+
## Limitations
|
|
108
|
+
|
|
109
|
+
`fail-on-console` is not compatible with concurrent asynchronous tests (e.g., `test.concurrent`). Because concurrent tests execute simultaneously within the same environment context, console logs cannot be isolated reliably per individual test. For suites requiring specific log suppression, tests must be run sequentially.
|
|
110
|
+
|
|
111
|
+
## Credits & Prior Art
|
|
112
|
+
|
|
113
|
+
This package is inspired by and builds on the excellent foundation laid by:
|
|
114
|
+
|
|
115
|
+
- [jest-fail-on-console](https://github.com/ValentinH/jest-fail-on-console) by Valentin Hervieu
|
|
116
|
+
- [vitest-fail-on-console](https://github.com/thomasbrodusch/vitest-fail-on-console) by Thomas Brodusch
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type ConsoleMethod = "error" | "warn" | "info" | "log" | "debug";
|
|
2
|
+
|
|
3
|
+
export interface ExpectStateLike {
|
|
4
|
+
currentTestName?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ExpectLike {
|
|
8
|
+
getState(): ExpectStateLike;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface LifecycleHookLike {
|
|
12
|
+
(fn: () => void): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TestApi {
|
|
16
|
+
beforeEach: LifecycleHookLike;
|
|
17
|
+
afterEach: LifecycleHookLike;
|
|
18
|
+
expect: ExpectLike;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SetupOptions extends TestApi {
|
|
22
|
+
methods?: ConsoleMethod[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AllowPredicate {
|
|
26
|
+
(message: string): boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type AllowRule = string | RegExp | AllowPredicate;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Installs console spies that fail the current test if any monitored console
|
|
33
|
+
* method is called. Call once at the top of the test setup file, passing the
|
|
34
|
+
* lifecycle hooks and expect function from the test framework. Compatible with
|
|
35
|
+
* any Jest-compatible API (Jest, Vitest, etc.).
|
|
36
|
+
* @param {Object} options
|
|
37
|
+
* @param {Function} options.beforeEach The beforeEach hook from the test framework.
|
|
38
|
+
* @param {Function} options.afterEach The afterEach hook from the test framework.
|
|
39
|
+
* @param {Object} options.expect The expect object from the test framework. Must expose getState().
|
|
40
|
+
* @param {string[]} [options.methods=["error","warn","info","log"]] - Console methods to monitor.
|
|
41
|
+
* @example
|
|
42
|
+
* // Jest
|
|
43
|
+
* import {beforeEach, afterEach, expect} from "@jest/globals";
|
|
44
|
+
* import {setup} from "fail-on-console";
|
|
45
|
+
*
|
|
46
|
+
* setup({beforeEach, afterEach, expect});
|
|
47
|
+
* @example
|
|
48
|
+
* // Vitest
|
|
49
|
+
* import {beforeEach, afterEach, expect} from "vitest";
|
|
50
|
+
* import {setup} from "fail-on-console";
|
|
51
|
+
*
|
|
52
|
+
* setup({beforeEach, afterEach, expect});
|
|
53
|
+
*/
|
|
54
|
+
export function setup(options: SetupOptions): void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Allows specific console calls to pass. Console exceptions can be configured
|
|
58
|
+
* globally, within a describe block or inside a single test.
|
|
59
|
+
* @param {string} method The console method to allow: "error", "warn", "info",
|
|
60
|
+
* "log", "debug", or "assert".
|
|
61
|
+
* @param {string|RegExp|Function|Array<string|RegExp|Function>} rules One or
|
|
62
|
+
* more matchers. A message is allowed if any matcher matches it. A string
|
|
63
|
+
* matches when the message contains it. A RegExp matches when it tests true
|
|
64
|
+
* against the message. A function receives the message and returns true to
|
|
65
|
+
* allow it.
|
|
66
|
+
* @example
|
|
67
|
+
* // Single string - allow any warn containing this substring
|
|
68
|
+
* allowConsole("warn", "third-party library warning");
|
|
69
|
+
* @example
|
|
70
|
+
* // RegExp - allow errors matching a pattern
|
|
71
|
+
* allowConsole("error", /^Warning: Each child in a list/);
|
|
72
|
+
* @example
|
|
73
|
+
* // Predicate - allow logs from a specific source
|
|
74
|
+
* allowConsole("log", (message) => message.startsWith("[analytics]"));
|
|
75
|
+
* @example
|
|
76
|
+
* // Mixed array - allow multiple matchers at once
|
|
77
|
+
* allowConsole("error", ["known warning", /deprecated/, (m) => m.includes("third-party")]);
|
|
78
|
+
*/
|
|
79
|
+
export function allowConsole(method: ConsoleMethod, rules: AllowRule | AllowRule[]): void;
|
package/index.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
let testApi;
|
|
2
|
+
const defaultMethods = ["error", "warn", "info", "log"];
|
|
3
|
+
const allowed = new Map();
|
|
4
|
+
// %s string, %d/%i integer, %o object, %f float
|
|
5
|
+
const printfPattern = /%[sdiof]/g;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Basic implementation of node:util/format for console message formatting.
|
|
9
|
+
* Covers only the most common uses. Does not handle all specifiers (such as
|
|
10
|
+
* object expansion) or other edge cases.
|
|
11
|
+
* @param {...*} args The list of arguments passed to the console method.
|
|
12
|
+
* @returns {string} The formatted message string.
|
|
13
|
+
*/
|
|
14
|
+
function format(...args) {
|
|
15
|
+
const [format, ...values] = args;
|
|
16
|
+
|
|
17
|
+
if (typeof format !== "string") {
|
|
18
|
+
return args.map(String).join(" ");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let valueIndex = 0;
|
|
22
|
+
const result = format.replace(printfPattern, function (match) {
|
|
23
|
+
const value = values[valueIndex];
|
|
24
|
+
const formatted = valueIndex < values.length ? String(value) : match;
|
|
25
|
+
valueIndex += 1;
|
|
26
|
+
|
|
27
|
+
return formatted;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (valueIndex < values.length) {
|
|
31
|
+
return result + " " + values.slice(valueIndex).map(String).join(" ");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isAllowed(message, rule) {
|
|
38
|
+
if (typeof rule === "string") {
|
|
39
|
+
return message.includes(rule);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (typeof rule === "function") {
|
|
43
|
+
return rule(message);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return rule.test(message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setup({beforeEach, afterEach, expect, methods = defaultMethods}) {
|
|
50
|
+
if (testApi) {
|
|
51
|
+
throw new Error("fail-on-console: Call setup() only once.");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
testApi = {beforeEach, afterEach, expect};
|
|
55
|
+
|
|
56
|
+
beforeEach(() => allowed.clear());
|
|
57
|
+
|
|
58
|
+
methods.forEach(function (method) {
|
|
59
|
+
const original = console[method];
|
|
60
|
+
const calls = [];
|
|
61
|
+
|
|
62
|
+
beforeEach(function () {
|
|
63
|
+
calls.length = 0;
|
|
64
|
+
|
|
65
|
+
console[method] = function consoleOverride(...args) {
|
|
66
|
+
const message = format(...args);
|
|
67
|
+
const rules = allowed.get(method);
|
|
68
|
+
|
|
69
|
+
if (rules?.some((rule) => isAllowed(message, rule))) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const call = {message, stack: ""};
|
|
74
|
+
Error.captureStackTrace?.(call, consoleOverride);
|
|
75
|
+
|
|
76
|
+
calls.push(call);
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
afterEach(function () {
|
|
81
|
+
console[method] = original;
|
|
82
|
+
|
|
83
|
+
if (!calls.length) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const detail = calls.map(({message, stack}) => `${message}\n${stack}`).join("\n\n");
|
|
88
|
+
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Expected test not to call console.${method}().\n\n${detail}\n\nIf expected, use allowConsole("${method}", ...) to add an exception.`
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function allowConsole(method, rules) {
|
|
97
|
+
if (!testApi) {
|
|
98
|
+
throw new Error("fail-on-console: Call setup() before using allowConsole().");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const normalized = Array.isArray(rules) ? rules : [rules];
|
|
102
|
+
const isInsideTest = !!testApi.expect.getState().currentTestName;
|
|
103
|
+
|
|
104
|
+
function addRules() {
|
|
105
|
+
const existing = allowed.get(method) ?? [];
|
|
106
|
+
allowed.set(method, [...existing, ...normalized]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isInsideTest) {
|
|
110
|
+
addRules();
|
|
111
|
+
} else {
|
|
112
|
+
testApi.beforeEach(addRules);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
exports.setup = setup;
|
|
117
|
+
exports.allowConsole = allowConsole;
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fail-on-console",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Prevent console noise from obscuring test results.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"./CHANGELOG.md",
|
|
9
|
+
"./index.d.ts"
|
|
10
|
+
],
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@commitlint/config-conventional": "^21.2.0",
|
|
13
|
+
"@vitest/coverage-v8": "4.1.9",
|
|
14
|
+
"commitlint": "^21.2.0",
|
|
15
|
+
"jest": "^30.4.2",
|
|
16
|
+
"prettier": "^3.8.4",
|
|
17
|
+
"prettier-plugin-packagejson": "^3.0.2",
|
|
18
|
+
"vitest": "^4.1.9"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"commit:check": "commitlint --from-last-tag",
|
|
22
|
+
"format": "prettier --write .",
|
|
23
|
+
"format:check": "prettier --check .",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:compat": "jest",
|
|
26
|
+
"test:cover": "vitest run --coverage"
|
|
27
|
+
}
|
|
28
|
+
}
|