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 ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2026-07-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * add initial fail-on-console implementation ([a414cbf](https://github.com/benquarmby/fail-on-console/commit/a414cbfb129443a2fd1d369d4f37c6b54fe90aa5))
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
+ }