@japa/runner 4.3.1 → 4.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.
@@ -16,8 +16,11 @@ import ms from "ms";
16
16
  import { ErrorsPrinter } from "@japa/errors-printer";
17
17
 
18
18
  // src/helpers.ts
19
+ import string from "@poppinss/string";
19
20
  import useColors from "@poppinss/colors";
21
+ import { fileURLToPath } from "url";
20
22
  import supportsColor from "supports-color";
23
+ import { parse } from "error-stack-parser-es";
21
24
  var colors = supportsColor.stdout ? useColors.ansi() : useColors.silent();
22
25
  var icons = process.platform === "win32" && !process.env.WT_SESSION ? {
23
26
  tick: "\u221A",
@@ -40,6 +43,47 @@ var icons = process.platform === "win32" && !process.env.WT_SESSION ? {
40
43
  branch: "\u2514\u2500\u2500",
41
44
  squareSmallFilled: "\u25FC"
42
45
  };
46
+ function formatPinnedTest(test) {
47
+ let fileName = "";
48
+ let line = 0;
49
+ let column = 0;
50
+ try {
51
+ test.options.meta.abort("Finding pinned test location");
52
+ } catch (error) {
53
+ const frame = parse(error).find(
54
+ (f) => f.fileName && f.lineNumber !== void 0 && f.columnNumber !== void 0 && !f.fileName.includes("node:") && !f.fileName.includes("ext:") && !f.fileName.includes("node_modules/")
55
+ );
56
+ if (frame) {
57
+ fileName = frame.fileName.startsWith("file:") ? string.toUnixSlash(fileURLToPath(frame.fileName)) : string.toUnixSlash(frame.fileName);
58
+ line = frame.lineNumber;
59
+ column = frame.columnNumber;
60
+ }
61
+ }
62
+ return `${colors.yellow(` \u2043 ${test.title}`)}
63
+ ${colors.dim(` ${fileName}:${line}:${column}`)}`;
64
+ }
65
+ function printPinnedTests(runner) {
66
+ let pinnedTests = [];
67
+ runner.suites.forEach((suite) => {
68
+ suite.stack.forEach((testOrGroup) => {
69
+ if (testOrGroup instanceof Group) {
70
+ testOrGroup.tests.forEach(($test) => {
71
+ if ($test.isPinned) {
72
+ pinnedTests.push(formatPinnedTest($test));
73
+ }
74
+ });
75
+ } else if (testOrGroup.isPinned) {
76
+ pinnedTests.push(formatPinnedTest(testOrGroup));
77
+ }
78
+ });
79
+ });
80
+ if (pinnedTests.length) {
81
+ console.log(colors.bgYellow().black(` ${pinnedTests.length} pinned test(s) found `));
82
+ pinnedTests.forEach((row) => console.log(row));
83
+ } else {
84
+ console.log(colors.bgYellow().black(` No pinned tests found `));
85
+ }
86
+ }
43
87
 
44
88
  // modules/core/reporters/base.ts
45
89
  var BaseReporter = class {
@@ -218,7 +262,7 @@ var TestContext = class extends BaseTestContext {
218
262
  };
219
263
  }
220
264
  };
221
- var Test = class extends BaseTest {
265
+ var Test2 = class extends BaseTest {
222
266
  /**
223
267
  * @inheritdoc
224
268
  */
@@ -278,18 +322,19 @@ var Group = class extends BaseGroup {
278
322
  };
279
323
  var Suite = class extends BaseSuite {
280
324
  };
281
- var Runner = class extends BaseRunner {
325
+ var Runner2 = class extends BaseRunner {
282
326
  };
283
327
 
284
328
  export {
285
- colors,
286
- icons,
287
329
  BaseReporter,
288
330
  Emitter,
289
331
  Refiner,
290
332
  TestContext,
291
- Test,
333
+ Test2 as Test,
292
334
  Group,
293
335
  Suite,
294
- Runner
336
+ Runner2 as Runner,
337
+ colors,
338
+ icons,
339
+ printPinnedTests
295
340
  };
@@ -2,7 +2,7 @@ import {
2
2
  BaseReporter,
3
3
  colors,
4
4
  icons
5
- } from "./chunk-PCBL2VZP.js";
5
+ } from "./chunk-L7YZLDZD.js";
6
6
 
7
7
  // src/reporters/dot.ts
8
8
  var DotReporter = class extends BaseReporter {
@@ -3,14 +3,14 @@ import {
3
3
  github,
4
4
  ndjson,
5
5
  spec
6
- } from "./chunk-U3BSXCEH.js";
6
+ } from "./chunk-RFKFNXTE.js";
7
7
  import {
8
8
  Group,
9
9
  Refiner,
10
10
  Test,
11
11
  TestContext,
12
12
  colors
13
- } from "./chunk-PCBL2VZP.js";
13
+ } from "./chunk-L7YZLDZD.js";
14
14
 
15
15
  // src/debug.ts
16
16
  import { debuglog } from "util";
@@ -261,10 +261,11 @@ var GlobalHooks = class {
261
261
  import getopts from "getopts";
262
262
  var OPTIONS = {
263
263
  string: ["tests", "groups", "tags", "files", "timeout", "retries", "reporters", "bailLayer"],
264
- boolean: ["help", "matchAll", "failed", "bail"],
264
+ boolean: ["help", "matchAll", "failed", "bail", "listPinned"],
265
265
  alias: {
266
266
  forceExit: "force-exit",
267
267
  matchAll: "match-all",
268
+ listPinned: "list-pinned",
268
269
  bailLayer: "bail-layer",
269
270
  help: "h"
270
271
  }
@@ -276,6 +277,7 @@ ${colors.green("--tests")} ${colors.dim("Filter tests by the
276
277
  ${colors.green("--groups")} ${colors.dim("Filter tests by the group title")}
277
278
  ${colors.green("--tags")} ${colors.dim("Filter tests by tags")}
278
279
  ${colors.green("--match-all")} ${colors.dim("Run tests that matches all the supplied tags")}
280
+ ${colors.green("--list-pinned")} ${colors.dim("List pinned tests")}
279
281
  ${colors.green("--files")} ${colors.dim("Filter tests by the file name")}
280
282
  ${colors.green("--force-exit")} ${colors.dim("Forcefully exit the process")}
281
283
  ${colors.green("--timeout")} ${colors.dim("Define default timeout for all tests")}
@@ -470,11 +472,15 @@ var ConfigManager = class {
470
472
 
471
473
  // src/create_test.ts
472
474
  var contextBuilder = (testInstance) => new TestContext(testInstance);
473
- function createTest(title, emitter, refiner, options) {
475
+ function createTest(title, emitter, refiner, debuggingError, options) {
474
476
  const testInstance = new Test(title, contextBuilder, emitter, refiner, options.group);
475
477
  testInstance.options.meta.suite = options.suite;
476
478
  testInstance.options.meta.group = options.group;
477
479
  testInstance.options.meta.fileName = options.file;
480
+ testInstance.options.meta.abort = (message) => {
481
+ debuggingError.message = message;
482
+ throw debuggingError;
483
+ };
478
484
  if (options.timeout !== void 0) {
479
485
  testInstance.timeout(options.timeout);
480
486
  }
@@ -5,13 +5,13 @@ import {
5
5
  Planner,
6
6
  createTest,
7
7
  createTestGroup
8
- } from "../chunk-Y57JXJ7G.js";
9
- import "../chunk-U3BSXCEH.js";
8
+ } from "../chunk-TLYU3GFT.js";
9
+ import "../chunk-RFKFNXTE.js";
10
10
  import {
11
11
  Emitter,
12
12
  Runner,
13
13
  Suite
14
- } from "../chunk-PCBL2VZP.js";
14
+ } from "../chunk-L7YZLDZD.js";
15
15
  import "../chunk-2KG3PWR4.js";
16
16
 
17
17
  // factories/runner.ts
@@ -59,10 +59,11 @@ var RunnerFactory = class {
59
59
  async runTest(title, callback) {
60
60
  return this.runSuites((emitter, refiner, file) => {
61
61
  const defaultSuite = new Suite("default", emitter, refiner);
62
- createTest(title, emitter, refiner, {
62
+ const debuggingError = new Error();
63
+ createTest(title, emitter, refiner, debuggingError, {
63
64
  suite: defaultSuite,
64
65
  file
65
- }).run(callback);
66
+ }).run(callback, debuggingError);
66
67
  return [defaultSuite];
67
68
  });
68
69
  }
@@ -107,38 +108,38 @@ function createUnitTestsSuite(emitter, refiner, file) {
107
108
  suite,
108
109
  file
109
110
  });
110
- createTest("A top level test inside a suite", emitter, refiner, {
111
+ createTest("A top level test inside a suite", emitter, refiner, new Error(), {
111
112
  suite,
112
113
  file
113
114
  }).run(() => {
114
115
  });
115
- createTest("add two numbers", emitter, refiner, { group, file }).run(() => {
116
+ createTest("add two numbers", emitter, refiner, new Error(), { group, file }).run(() => {
116
117
  assert.equal(2 + 2, 4);
117
118
  });
118
- createTest("add three numbers", emitter, refiner, {
119
+ createTest("add three numbers", emitter, refiner, new Error(), {
119
120
  group,
120
121
  file
121
122
  }).run(() => {
122
123
  assert.equal(2 + 2 + 2, 6);
123
124
  });
124
- createTest("add group of numbers", emitter, refiner, { group, file });
125
- createTest("use math.js lib", emitter, refiner, { group, file }).skip(
125
+ createTest("add group of numbers", emitter, refiner, new Error(), { group, file });
126
+ createTest("use math.js lib", emitter, refiner, new Error(), { group, file }).skip(
126
127
  true,
127
128
  "Library work pending"
128
129
  );
129
- createTest("add multiple numbers", emitter, refiner, {
130
+ createTest("add multiple numbers", emitter, refiner, new Error(), {
130
131
  file,
131
132
  group
132
133
  }).run(() => {
133
134
  assert.equal(2 + 2 + 2 + 2, 6);
134
135
  });
135
- createTest("add floating numbers", emitter, refiner, { group, file }).run(() => {
136
+ createTest("add floating numbers", emitter, refiner, new Error(), { group, file }).run(() => {
136
137
  assert.equal(2 + 2.2 + 2.1, 6);
137
138
  }).fails("Have to add support for floating numbers");
138
- createTest("regression test that is passing", emitter, refiner, { group, file }).run(() => {
139
+ createTest("regression test that is passing", emitter, refiner, new Error(), { group, file }).run(() => {
139
140
  assert.equal(2 + 2.2 + 2.1, 2 + 2.2 + 2.1);
140
141
  }).fails("Have to add support for floating numbers");
141
- createTest("A test with an error that is not an AssertionError", emitter, refiner, {
142
+ createTest("A test with an error that is not an AssertionError", emitter, refiner, new Error(), {
142
143
  group,
143
144
  file
144
145
  }).run(() => {
@@ -152,32 +153,32 @@ function createFunctionalTestsSuite(emitter, refiner, file) {
152
153
  suite,
153
154
  file
154
155
  });
155
- createTest("Validate user data", emitter, refiner, {
156
+ createTest("Validate user data", emitter, refiner, new Error(), {
156
157
  group,
157
158
  file
158
159
  }).run(() => {
159
160
  });
160
- createTest("Disallow duplicate emails", emitter, refiner, {
161
+ createTest("Disallow duplicate emails", emitter, refiner, new Error(), {
161
162
  group,
162
163
  file
163
164
  }).run(() => {
164
165
  });
165
- createTest("Disallow duplicate emails across tenants", emitter, refiner, {
166
+ createTest("Disallow duplicate emails across tenants", emitter, refiner, new Error(), {
166
167
  group,
167
168
  file
168
169
  }).run(() => {
169
170
  const users = ["", ""];
170
171
  assert.equal(users.length, 1);
171
172
  });
172
- createTest("Normalize email before persisting it", emitter, refiner, {
173
+ createTest("Normalize email before persisting it", emitter, refiner, new Error(), {
173
174
  group,
174
175
  file
175
176
  }).skip(true, "Have to build a normalizer");
176
- createTest("Send email verification mail", emitter, refiner, {
177
+ createTest("Send email verification mail", emitter, refiner, new Error(), {
177
178
  group,
178
179
  file
179
180
  });
180
- createTest("Test that times out", emitter, refiner, {
181
+ createTest("Test that times out", emitter, refiner, new Error(), {
181
182
  group,
182
183
  file
183
184
  }).run(() => {
@@ -192,10 +193,16 @@ function createFunctionalTestsSuite(emitter, refiner, file) {
192
193
  usersListGroup.setup(() => {
193
194
  throw new Error("Unable to cleanup database");
194
195
  });
195
- createTest("A test that will never run because the group hooks fails", emitter, refiner, {
196
- group: usersListGroup
197
- });
198
- createTest("A top level test inside functional suite", emitter, refiner, {
196
+ createTest(
197
+ "A test that will never run because the group hooks fails",
198
+ emitter,
199
+ refiner,
200
+ new Error(),
201
+ {
202
+ group: usersListGroup
203
+ }
204
+ );
205
+ createTest("A top level test inside functional suite", emitter, refiner, new Error(), {
199
206
  suite,
200
207
  file
201
208
  }).run(() => {
package/build/index.js CHANGED
@@ -7,14 +7,15 @@ import {
7
7
  createTestGroup,
8
8
  debug_default,
9
9
  validator_default
10
- } from "./chunk-Y57JXJ7G.js";
11
- import "./chunk-U3BSXCEH.js";
10
+ } from "./chunk-TLYU3GFT.js";
11
+ import "./chunk-RFKFNXTE.js";
12
12
  import {
13
13
  Emitter,
14
14
  Runner,
15
15
  Suite,
16
- colors
17
- } from "./chunk-PCBL2VZP.js";
16
+ colors,
17
+ printPinnedTests
18
+ } from "./chunk-L7YZLDZD.js";
18
19
  import "./chunk-2KG3PWR4.js";
19
20
 
20
21
  // index.ts
@@ -144,7 +145,14 @@ var executionPlanState = {
144
145
  };
145
146
  function test(title, callback) {
146
147
  validator_default.ensureIsInPlanningPhase(executionPlanState.phase);
147
- const testInstance = createTest(title, emitter, runnerConfig.refiner, executionPlanState);
148
+ const debuggingError = new Error();
149
+ const testInstance = createTest(
150
+ title,
151
+ emitter,
152
+ runnerConfig.refiner,
153
+ debuggingError,
154
+ executionPlanState
155
+ );
148
156
  testInstance.setup((t) => {
149
157
  activeTest = t;
150
158
  return () => {
@@ -152,7 +160,7 @@ function test(title, callback) {
152
160
  };
153
161
  });
154
162
  if (callback) {
155
- testInstance.run(callback, new Error());
163
+ testInstance.run(callback, debuggingError);
156
164
  }
157
165
  return testInstance;
158
166
  }
@@ -220,7 +228,9 @@ async function run() {
220
228
  runner.onSuite(config.configureSuite);
221
229
  debug_default("executing global hooks");
222
230
  globalHooks.apply(config);
223
- await globalHooks.setup(runner);
231
+ if (!cliArgs.listPinned) {
232
+ await globalHooks.setup(runner);
233
+ }
224
234
  for (let suite of suites) {
225
235
  debug_default("initiating suite %s", suite.name);
226
236
  executionPlanState.suite = new Suite(suite.name, emitter, config.refiner);
@@ -241,6 +251,14 @@ async function run() {
241
251
  }
242
252
  executionPlanState.suite = void 0;
243
253
  }
254
+ if (cliArgs.listPinned) {
255
+ printPinnedTests(runner);
256
+ if (config.forceExit) {
257
+ debug_default("force exiting process");
258
+ process.exit();
259
+ }
260
+ return;
261
+ }
244
262
  executionPlanState.phase = "executing";
245
263
  exceptionsManager.monitor();
246
264
  await runner.start();
@@ -7,7 +7,7 @@ import {
7
7
  Suite,
8
8
  Test,
9
9
  TestContext
10
- } from "../../chunk-PCBL2VZP.js";
10
+ } from "../../chunk-L7YZLDZD.js";
11
11
  import "../../chunk-2KG3PWR4.js";
12
12
  export {
13
13
  BaseReporter,
@@ -2,7 +2,7 @@ import { Emitter, Group, Refiner, Suite, Test } from '../modules/core/main.js';
2
2
  /**
3
3
  * Create a new instance of the Test
4
4
  */
5
- export declare function createTest(title: string, emitter: Emitter, refiner: Refiner, options: {
5
+ export declare function createTest(title: string, emitter: Emitter, refiner: Refiner, debuggingError: Error, options: {
6
6
  group?: Group;
7
7
  suite?: Suite;
8
8
  file?: string;
@@ -1,4 +1,5 @@
1
1
  import { Colors } from '@poppinss/colors/types';
2
+ import { Runner, Test } from '../modules/core/main.js';
2
3
  export declare const colors: Colors;
3
4
  /**
4
5
  * A collection of platform specific icons
@@ -14,3 +15,12 @@ export declare const icons: {
14
15
  branch: string;
15
16
  squareSmallFilled: string;
16
17
  };
18
+ /**
19
+ * Returns a formatted string to print the information about
20
+ * a pinned test
21
+ */
22
+ export declare function formatPinnedTest(test: Test): string;
23
+ /**
24
+ * Prints a summary of all the pinned tests
25
+ */
26
+ export declare function printPinnedTests(runner: Runner): void;
@@ -0,0 +1,9 @@
1
+ import type { PluginFn } from '../types.js';
2
+ /**
3
+ * Disallows pinned tests by throwing an error before the runner
4
+ * starts executing the tests.
5
+ */
6
+ export declare function disallowPinnedTests(options?: {
7
+ disallow?: boolean;
8
+ errorMessage?: string;
9
+ }): PluginFn;
@@ -0,0 +1 @@
1
+ export { disallowPinnedTests } from './disallow_pinned_tests.js';
@@ -0,0 +1,31 @@
1
+ import "../../chunk-2KG3PWR4.js";
2
+
3
+ // src/plugins/disallow_pinned_tests.ts
4
+ import string from "@poppinss/string";
5
+ function disallowPinnedTests(options) {
6
+ const disallow = options?.disallow ?? true;
7
+ const errorMessage = options?.errorMessage ?? 'Pinning tests are disallowed by the "disallowPinnedTests" plugin. Use the "--list-pinned" flag to list pinned tests';
8
+ const pluginFn = async function disallowPinnedTestsPluginFn({ runner, emitter }) {
9
+ if (!disallow) {
10
+ return;
11
+ }
12
+ function disallowPinned(test) {
13
+ if (test.isPinned) {
14
+ test.options.meta.abort(string.interpolate(errorMessage, { test: test.title }));
15
+ process.exitCode = 1;
16
+ }
17
+ }
18
+ emitter.on("runner:start", () => {
19
+ runner.onSuite((suite) => {
20
+ suite.onGroup((group) => {
21
+ group.tap(disallowPinned);
22
+ });
23
+ suite.onTest(disallowPinned);
24
+ });
25
+ });
26
+ };
27
+ return pluginFn;
28
+ }
29
+ export {
30
+ disallowPinnedTests
31
+ };
@@ -3,8 +3,8 @@ import {
3
3
  github,
4
4
  ndjson,
5
5
  spec
6
- } from "../../chunk-U3BSXCEH.js";
7
- import "../../chunk-PCBL2VZP.js";
6
+ } from "../../chunk-RFKFNXTE.js";
7
+ import "../../chunk-L7YZLDZD.js";
8
8
  import "../../chunk-2KG3PWR4.js";
9
9
  export {
10
10
  dot,
@@ -35,6 +35,7 @@ export type CLIArgs = {
35
35
  failed?: boolean;
36
36
  help?: boolean;
37
37
  matchAll?: boolean;
38
+ listPinned?: boolean;
38
39
  bail?: boolean;
39
40
  bailLayer?: string;
40
41
  } & Record<string, string | string[] | boolean>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@japa/runner",
3
3
  "description": "A simple yet powerful testing framework for Node.js",
4
- "version": "4.3.1",
4
+ "version": "4.4.0",
5
5
  "engines": {
6
6
  "node": ">=18.16.0"
7
7
  },
@@ -16,6 +16,7 @@
16
16
  ".": "./build/index.js",
17
17
  "./types": "./build/src/types.js",
18
18
  "./reporters": "./build/src/reporters/main.js",
19
+ "./plugins": "./build/src/plugins/main.js",
19
20
  "./factories": "./build/factories/main.js",
20
21
  "./core": "./build/modules/core/main.js"
21
22
  },
@@ -35,38 +36,40 @@
35
36
  "quick:test": "glob -c \"node --import=ts-node-maintained/register/esm --enable-source-maps --test-reporter=spec --test\" \"tests/*.spec.ts\""
36
37
  },
37
38
  "devDependencies": {
38
- "@adonisjs/eslint-config": "^2.1.0",
39
+ "@adonisjs/eslint-config": "^2.1.2",
39
40
  "@adonisjs/prettier-config": "^1.4.5",
40
41
  "@adonisjs/tsconfig": "^1.4.1",
41
42
  "@release-it/conventional-changelog": "^10.0.1",
42
- "@swc/core": "1.13.1",
43
+ "@swc/core": "1.13.3",
43
44
  "@types/chai": "^5.2.2",
44
45
  "@types/ms": "^2.1.0",
45
- "@types/node": "^24.0.15",
46
+ "@types/node": "^24.2.1",
46
47
  "c8": "^10.1.3",
47
48
  "chai": "^5.2.1",
48
- "cross-env": "^7.0.3",
49
+ "cross-env": "^10.0.0",
49
50
  "del-cli": "^6.0.0",
50
- "eslint": "^9.31.0",
51
+ "eslint": "^9.33.0",
51
52
  "glob": "^11.0.3",
52
53
  "prettier": "^3.6.2",
53
54
  "release-it": "^19.0.4",
54
- "ts-node-maintained": "^10.9.5",
55
+ "ts-node-maintained": "^10.9.6",
55
56
  "tsup": "^8.5.0",
56
- "typescript": "^5.8.3"
57
+ "typescript": "^5.9.2"
57
58
  },
58
59
  "dependencies": {
59
60
  "@japa/core": "^10.3.0",
60
- "@japa/errors-printer": "^4.1.2",
61
+ "@japa/errors-printer": "^4.1.3",
61
62
  "@poppinss/colors": "^4.1.5",
62
63
  "@poppinss/hooks": "^7.2.6",
64
+ "@poppinss/string": "^1.7.0",
65
+ "error-stack-parser-es": "^1.0.5",
63
66
  "fast-glob": "^3.3.3",
64
67
  "find-cache-directory": "^6.0.0",
65
68
  "getopts": "^2.3.0",
66
69
  "ms": "^2.1.3",
67
70
  "serialize-error": "^12.0.0",
68
71
  "slash": "^5.1.0",
69
- "supports-color": "^10.0.0"
72
+ "supports-color": "^10.1.0"
70
73
  },
71
74
  "homepage": "https://github.com/japa/runner#readme",
72
75
  "repository": {
@@ -91,6 +94,7 @@
91
94
  "entry": [
92
95
  "./index.ts",
93
96
  "./src/types.ts",
97
+ "./src/plugins/main.ts",
94
98
  "./src/reporters/main.ts",
95
99
  "./factories/main.ts",
96
100
  "./modules/core/main.ts"