@japa/runner 4.4.0 → 5.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.
@@ -1,340 +0,0 @@
1
- // modules/core/main.ts
2
- import {
3
- Emitter,
4
- Refiner,
5
- Test as BaseTest,
6
- Suite as BaseSuite,
7
- Group as BaseGroup,
8
- Runner as BaseRunner,
9
- TestContext as BaseTestContext
10
- } from "@japa/core";
11
- import { inspect } from "util";
12
- import { AssertionError } from "assert";
13
-
14
- // modules/core/reporters/base.ts
15
- import ms from "ms";
16
- import { ErrorsPrinter } from "@japa/errors-printer";
17
-
18
- // src/helpers.ts
19
- import string from "@poppinss/string";
20
- import useColors from "@poppinss/colors";
21
- import { fileURLToPath } from "url";
22
- import supportsColor from "supports-color";
23
- import { parse } from "error-stack-parser-es";
24
- var colors = supportsColor.stdout ? useColors.ansi() : useColors.silent();
25
- var icons = process.platform === "win32" && !process.env.WT_SESSION ? {
26
- tick: "\u221A",
27
- cross: "\xD7",
28
- bullet: "*",
29
- nodejs: "\u2666",
30
- pointer: ">",
31
- info: "i",
32
- warning: "\u203C",
33
- branch: " -",
34
- squareSmallFilled: "[\u2588]"
35
- } : {
36
- tick: "\u2714",
37
- cross: "\u2716",
38
- bullet: "\u25CF",
39
- nodejs: "\u2B22",
40
- pointer: "\u276F",
41
- info: "\u2139",
42
- warning: "\u26A0",
43
- branch: "\u2514\u2500\u2500",
44
- squareSmallFilled: "\u25FC"
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
- }
87
-
88
- // modules/core/reporters/base.ts
89
- var BaseReporter = class {
90
- runner;
91
- /**
92
- * Path to the file for which the tests are getting executed
93
- */
94
- currentFileName;
95
- /**
96
- * Suite for which the tests are getting executed
97
- */
98
- currentSuiteName;
99
- /**
100
- * Group for which the tests are getting executed
101
- */
102
- currentGroupName;
103
- options;
104
- constructor(options = {}) {
105
- this.options = Object.assign({ stackLinesCount: 2 }, options);
106
- }
107
- /**
108
- * Pretty prints the aggregates
109
- */
110
- printAggregates(summary) {
111
- const tests = [];
112
- if (summary.aggregates.passed) {
113
- tests.push(colors.green(`${summary.aggregates.passed} passed`));
114
- }
115
- if (summary.aggregates.failed) {
116
- tests.push(colors.red(`${summary.aggregates.failed} failed`));
117
- }
118
- if (summary.aggregates.todo) {
119
- tests.push(colors.cyan(`${summary.aggregates.todo} todo`));
120
- }
121
- if (summary.aggregates.skipped) {
122
- tests.push(colors.yellow(`${summary.aggregates.skipped} skipped`));
123
- }
124
- if (summary.aggregates.regression) {
125
- tests.push(colors.magenta(`${summary.aggregates.regression} regression`));
126
- }
127
- this.runner.summaryBuilder.use(() => {
128
- return [
129
- {
130
- key: colors.dim("Tests"),
131
- value: `${tests.join(", ")} ${colors.dim(`(${summary.aggregates.total})`)}`
132
- },
133
- {
134
- key: colors.dim("Time"),
135
- value: colors.dim(ms(summary.duration))
136
- }
137
- ];
138
- });
139
- console.log(this.runner.summaryBuilder.build().join("\n"));
140
- }
141
- /**
142
- * Aggregates errors tree to a flat array
143
- */
144
- aggregateErrors(summary) {
145
- const errorsList = [];
146
- summary.failureTree.forEach((suite) => {
147
- suite.errors.forEach((error) => errorsList.push({ title: suite.name, ...error }));
148
- suite.children.forEach((testOrGroup) => {
149
- if (testOrGroup.type === "test") {
150
- testOrGroup.errors.forEach((error) => {
151
- errorsList.push({ title: `${suite.name} / ${testOrGroup.title}`, ...error });
152
- });
153
- return;
154
- }
155
- testOrGroup.errors.forEach((error) => {
156
- errorsList.push({ title: testOrGroup.name, ...error });
157
- });
158
- testOrGroup.children.forEach((test) => {
159
- test.errors.forEach((error) => {
160
- errorsList.push({ title: `${testOrGroup.name} / ${test.title}`, ...error });
161
- });
162
- });
163
- });
164
- });
165
- return errorsList;
166
- }
167
- /**
168
- * Pretty print errors
169
- */
170
- async printErrors(summary) {
171
- if (!summary.failureTree.length) {
172
- return;
173
- }
174
- const errorPrinter = new ErrorsPrinter({
175
- framesMaxLimit: this.options.framesMaxLimit
176
- });
177
- errorPrinter.printSectionHeader("ERRORS");
178
- await errorPrinter.printErrors(this.aggregateErrors(summary));
179
- }
180
- /**
181
- * Handlers to capture events
182
- */
183
- onTestStart(_) {
184
- }
185
- onTestEnd(_) {
186
- }
187
- onGroupStart(_) {
188
- }
189
- onGroupEnd(_) {
190
- }
191
- onSuiteStart(_) {
192
- }
193
- onSuiteEnd(_) {
194
- }
195
- async start(_) {
196
- }
197
- async end(_) {
198
- }
199
- /**
200
- * Print tests summary
201
- */
202
- async printSummary(summary) {
203
- await this.printErrors(summary);
204
- console.log("");
205
- if (summary.aggregates.total === 0 && !summary.hasError) {
206
- console.log(colors.bgYellow().black(" NO TESTS EXECUTED "));
207
- return;
208
- }
209
- if (summary.hasError) {
210
- console.log(colors.bgRed().black(" FAILED "));
211
- } else {
212
- console.log(colors.bgGreen().black(" PASSED "));
213
- }
214
- console.log("");
215
- this.printAggregates(summary);
216
- }
217
- /**
218
- * Invoked by the tests runner when tests are about to start
219
- */
220
- boot(runner, emitter) {
221
- this.runner = runner;
222
- emitter.on("test:start", (payload) => {
223
- this.currentFileName = payload.meta.fileName;
224
- this.onTestStart(payload);
225
- });
226
- emitter.on("test:end", (payload) => {
227
- this.onTestEnd(payload);
228
- });
229
- emitter.on("group:start", (payload) => {
230
- this.currentGroupName = payload.title;
231
- this.currentFileName = payload.meta.fileName;
232
- this.onGroupStart(payload);
233
- });
234
- emitter.on("group:end", (payload) => {
235
- this.currentGroupName = void 0;
236
- this.onGroupEnd(payload);
237
- });
238
- emitter.on("suite:start", (payload) => {
239
- this.currentSuiteName = payload.name;
240
- this.onSuiteStart(payload);
241
- });
242
- emitter.on("suite:end", (payload) => {
243
- this.currentSuiteName = void 0;
244
- this.onSuiteEnd(payload);
245
- });
246
- emitter.on("runner:start", async (payload) => {
247
- await this.start(payload);
248
- });
249
- emitter.on("runner:end", async (payload) => {
250
- await this.end(payload);
251
- });
252
- }
253
- };
254
-
255
- // modules/core/main.ts
256
- var TestContext = class extends BaseTestContext {
257
- constructor(test) {
258
- super();
259
- this.test = test;
260
- this.cleanup = (cleanupCallback) => {
261
- test.cleanup(cleanupCallback);
262
- };
263
- }
264
- };
265
- var Test2 = class extends BaseTest {
266
- /**
267
- * @inheritdoc
268
- */
269
- static executedCallbacks = [];
270
- /**
271
- * @inheritdoc
272
- */
273
- static executingCallbacks = [];
274
- /**
275
- * Assert the test throws an exception with a certain error message
276
- * and optionally is an instance of a given Error class.
277
- */
278
- throws(message, errorConstructor) {
279
- const errorInPoint = new AssertionError({});
280
- const existingExecutor = this.options.executor;
281
- if (!existingExecutor) {
282
- throw new Error('Cannot use "test.throws" method without a test callback');
283
- }
284
- this.options.executor = async (...args) => {
285
- let raisedException;
286
- try {
287
- await existingExecutor(...args);
288
- } catch (error) {
289
- raisedException = error;
290
- }
291
- if (!raisedException) {
292
- errorInPoint.message = "Expected test to throw an exception";
293
- throw errorInPoint;
294
- }
295
- if (errorConstructor && !(raisedException instanceof errorConstructor)) {
296
- errorInPoint.message = `Expected test to throw "${inspect(errorConstructor)}"`;
297
- throw errorInPoint;
298
- }
299
- const exceptionMessage = raisedException.message;
300
- if (!exceptionMessage || typeof exceptionMessage !== "string") {
301
- errorInPoint.message = "Expected test to throw an exception with message property";
302
- throw errorInPoint;
303
- }
304
- if (typeof message === "string") {
305
- if (exceptionMessage !== message) {
306
- errorInPoint.message = `Expected test to throw "${message}". Instead received "${raisedException.message}"`;
307
- errorInPoint.actual = raisedException.message;
308
- errorInPoint.expected = message;
309
- throw errorInPoint;
310
- }
311
- return;
312
- }
313
- if (!message.test(exceptionMessage)) {
314
- errorInPoint.message = `Expected test error to match "${message}" regular expression`;
315
- throw errorInPoint;
316
- }
317
- };
318
- return this;
319
- }
320
- };
321
- var Group = class extends BaseGroup {
322
- };
323
- var Suite = class extends BaseSuite {
324
- };
325
- var Runner2 = class extends BaseRunner {
326
- };
327
-
328
- export {
329
- BaseReporter,
330
- Emitter,
331
- Refiner,
332
- TestContext,
333
- Test2 as Test,
334
- Group,
335
- Suite,
336
- Runner2 as Runner,
337
- colors,
338
- icons,
339
- printPinnedTests
340
- };
@@ -1,347 +0,0 @@
1
- import {
2
- BaseReporter,
3
- colors,
4
- icons
5
- } from "./chunk-L7YZLDZD.js";
6
-
7
- // src/reporters/dot.ts
8
- var DotReporter = class extends BaseReporter {
9
- /**
10
- * When a test ended
11
- */
12
- onTestEnd(payload) {
13
- let output = "";
14
- if (payload.isTodo) {
15
- output = colors.cyan(icons.info);
16
- } else if (payload.hasError) {
17
- output = colors.red(icons.cross);
18
- } else if (payload.isSkipped) {
19
- output = colors.yellow(icons.bullet);
20
- } else if (payload.isFailing) {
21
- output = colors.magenta(icons.squareSmallFilled);
22
- } else {
23
- output = colors.green(icons.tick);
24
- }
25
- process.stdout.write(`${output}`);
26
- }
27
- /**
28
- * When test runner ended
29
- */
30
- async end() {
31
- console.log("");
32
- await this.printSummary(this.runner.getSummary());
33
- }
34
- };
35
-
36
- // src/reporters/spec.ts
37
- import ms from "ms";
38
- import { relative } from "path";
39
- var SpecReporter = class extends BaseReporter {
40
- /**
41
- * Tracking if the first event we get is for a test without any parent group
42
- * We need this to decide the display style for tests without groups.
43
- */
44
- #isFirstLoneTest = true;
45
- /**
46
- * Returns the icon for the test
47
- */
48
- #getTestIcon(payload) {
49
- if (payload.isTodo) {
50
- return colors.cyan(icons.info);
51
- }
52
- if (payload.hasError) {
53
- return colors.red(icons.cross);
54
- }
55
- if (payload.isSkipped) {
56
- return colors.yellow(icons.bullet);
57
- }
58
- if (payload.isFailing) {
59
- return colors.magenta(icons.squareSmallFilled);
60
- }
61
- return colors.green(icons.tick);
62
- }
63
- /**
64
- * Returns the test message
65
- */
66
- #getTestMessage(payload) {
67
- const message = payload.title.expanded;
68
- if (payload.isTodo) {
69
- return colors.blue(message);
70
- }
71
- if (payload.hasError) {
72
- return colors.red(message);
73
- }
74
- if (payload.isSkipped) {
75
- return colors.yellow(message);
76
- }
77
- if (payload.isFailing) {
78
- return colors.magenta(message);
79
- }
80
- return colors.grey(message);
81
- }
82
- /**
83
- * Returns the subtext message for the test
84
- */
85
- #getSubText(payload) {
86
- if (payload.isSkipped && payload.skipReason) {
87
- return colors.dim(`${icons.branch} ${colors.italic(payload.skipReason)}`);
88
- }
89
- if (!payload.isFailing) {
90
- return;
91
- }
92
- if (payload.hasError) {
93
- const message = payload.errors.find((error) => error.phase === "test")?.error.message ?? `Test marked with ".fails()" must finish with an error`;
94
- return colors.dim(`${icons.branch} ${colors.italic(message)}`);
95
- }
96
- if (payload.failReason) {
97
- return colors.dim(`${icons.branch} ${colors.italic(payload.failReason)}`);
98
- }
99
- const testErrorMessage = payload.errors.find((error) => error.phase === "test");
100
- if (testErrorMessage && testErrorMessage.error) {
101
- return colors.dim(`${icons.branch} ${colors.italic(testErrorMessage.error.message)}`);
102
- }
103
- }
104
- /**
105
- * Returns the filename relative from the current working dir
106
- */
107
- #getRelativeFilename(fileName) {
108
- return relative(process.cwd(), fileName);
109
- }
110
- /**
111
- * Prints the test details
112
- */
113
- #printTest(payload) {
114
- const icon = this.#getTestIcon(payload);
115
- const message = this.#getTestMessage(payload);
116
- const prefix = payload.isPinned ? colors.yellow("[PINNED] ") : "";
117
- const indentation = this.currentFileName || this.currentGroupName ? " " : "";
118
- const duration = colors.dim(`(${ms(Number(payload.duration.toFixed(2)))})`);
119
- const retries = payload.retryAttempt && payload.retryAttempt > 1 ? colors.dim(`(x${payload.retryAttempt}) `) : "";
120
- let subText = this.#getSubText(payload);
121
- subText = subText ? `
122
- ${indentation} ${subText}` : "";
123
- console.log(`${indentation}${icon} ${prefix}${retries}${message} ${duration}${subText}`);
124
- }
125
- /**
126
- * Prints the group name
127
- */
128
- #printGroup(payload) {
129
- const title = this.currentSuiteName !== "default" ? `${this.currentSuiteName} / ${payload.title}` : payload.title;
130
- const suffix = this.currentFileName ? colors.dim(` (${this.#getRelativeFilename(this.currentFileName)})`) : "";
131
- console.log(`
132
- ${title}${suffix}`);
133
- }
134
- onTestStart() {
135
- if (this.currentFileName && this.#isFirstLoneTest) {
136
- console.log(`
137
- ${colors.dim(this.#getRelativeFilename(this.currentFileName))}`);
138
- }
139
- this.#isFirstLoneTest = false;
140
- }
141
- onTestEnd(payload) {
142
- this.#printTest(payload);
143
- }
144
- onGroupStart(payload) {
145
- this.#isFirstLoneTest = false;
146
- this.#printGroup(payload);
147
- }
148
- onGroupEnd() {
149
- this.#isFirstLoneTest = true;
150
- }
151
- async end() {
152
- const summary = this.runner.getSummary();
153
- await this.printSummary(summary);
154
- }
155
- };
156
-
157
- // src/reporters/ndjson.ts
158
- import { relative as relative2 } from "path";
159
- import { serializeError } from "serialize-error";
160
- var NdJSONReporter = class extends BaseReporter {
161
- /**
162
- * Returns the filename relative from the current working dir
163
- */
164
- #getRelativeFilename(fileName) {
165
- return relative2(process.cwd(), fileName);
166
- }
167
- /**
168
- * Serialize errors to JSON
169
- */
170
- #serializeErrors(errors) {
171
- return errors.map((error) => ({
172
- phase: error.phase,
173
- error: serializeError(error.error)
174
- }));
175
- }
176
- onTestEnd(payload) {
177
- console.log(
178
- JSON.stringify({
179
- event: "test:end",
180
- filePath: this.currentFileName,
181
- relativePath: this.currentFileName ? this.#getRelativeFilename(this.currentFileName) : void 0,
182
- title: payload.title,
183
- duration: payload.duration,
184
- failReason: payload.failReason,
185
- isFailing: payload.isFailing,
186
- skipReason: payload.skipReason,
187
- isSkipped: payload.isSkipped,
188
- isTodo: payload.isTodo,
189
- isPinned: payload.isPinned,
190
- retryAttempt: payload.retryAttempt,
191
- retries: payload.retries,
192
- errors: this.#serializeErrors(payload.errors)
193
- })
194
- );
195
- }
196
- onGroupStart(payload) {
197
- console.log(
198
- JSON.stringify({
199
- event: "group:start",
200
- title: payload.title
201
- })
202
- );
203
- }
204
- onGroupEnd(payload) {
205
- JSON.stringify({
206
- event: "group:end",
207
- title: payload.title,
208
- errors: this.#serializeErrors(payload.errors)
209
- });
210
- }
211
- onSuiteStart(payload) {
212
- console.log(
213
- JSON.stringify({
214
- event: "suite:start",
215
- ...payload
216
- })
217
- );
218
- }
219
- onSuiteEnd(payload) {
220
- console.log(
221
- JSON.stringify({
222
- event: "suite:end",
223
- name: payload.name,
224
- hasError: payload.hasError,
225
- errors: this.#serializeErrors(payload.errors)
226
- })
227
- );
228
- }
229
- async end() {
230
- const summary = this.runner.getSummary();
231
- console.log(
232
- JSON.stringify({
233
- aggregates: summary.aggregates,
234
- duration: summary.duration,
235
- failedTestsTitles: summary.failedTestsTitles,
236
- hasError: summary.hasError
237
- })
238
- );
239
- }
240
- };
241
-
242
- // src/reporters/github.ts
243
- import slash from "slash";
244
- import { relative as relative3 } from "path";
245
- import { stripVTControlCharacters } from "util";
246
- import { ErrorsPrinter } from "@japa/errors-printer";
247
- var GithubReporter = class extends BaseReporter {
248
- /**
249
- * Performs string escape on annotation message as per
250
- * https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
251
- */
252
- escapeMessage(value) {
253
- return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
254
- }
255
- /**
256
- * Performs string escape on annotation properties as per
257
- * https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85
258
- */
259
- escapeProperty(value) {
260
- return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
261
- }
262
- /**
263
- * Formats the message as per the Github annotations spec.
264
- * https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
265
- */
266
- formatMessage({
267
- command,
268
- properties,
269
- message
270
- }) {
271
- let result = `::${command}`;
272
- Object.entries(properties).forEach(([k, v], i) => {
273
- result += i === 0 ? " " : ",";
274
- result += `${k}=${this.escapeProperty(v)}`;
275
- });
276
- result += `::${this.escapeMessage(message)}`;
277
- return result;
278
- }
279
- /**
280
- * Prints Github annotation for a given error
281
- */
282
- async getErrorAnnotation(printer, error) {
283
- const parsedError = await printer.parseError(error.error);
284
- if (!("frames" in parsedError)) {
285
- return;
286
- }
287
- const mainFrame = parsedError.frames.find((frame) => frame.type === "app");
288
- if (!mainFrame) {
289
- return;
290
- }
291
- return this.formatMessage({
292
- command: "error",
293
- properties: {
294
- file: slash(relative3(process.cwd(), mainFrame.fileName)),
295
- title: error.title,
296
- line: String(mainFrame.lineNumber),
297
- column: String(mainFrame.columnNumber)
298
- },
299
- message: stripVTControlCharacters(parsedError.message)
300
- });
301
- }
302
- async end() {
303
- const summary = this.runner.getSummary();
304
- const errorsList = this.aggregateErrors(summary);
305
- const errorPrinter = new ErrorsPrinter(this.options);
306
- for (let error of errorsList) {
307
- const formatted = await this.getErrorAnnotation(errorPrinter, error);
308
- if (formatted) {
309
- console.log(`
310
- ${formatted}`);
311
- }
312
- }
313
- }
314
- };
315
-
316
- // src/reporters/main.ts
317
- var spec = (options) => {
318
- return {
319
- name: "spec",
320
- handler: (...args) => new SpecReporter(options).boot(...args)
321
- };
322
- };
323
- var dot = (options) => {
324
- return {
325
- name: "dot",
326
- handler: (...args) => new DotReporter(options).boot(...args)
327
- };
328
- };
329
- var ndjson = (options) => {
330
- return {
331
- name: "ndjson",
332
- handler: (...args) => new NdJSONReporter(options).boot(...args)
333
- };
334
- };
335
- var github = (options) => {
336
- return {
337
- name: "github",
338
- handler: (...args) => new GithubReporter(options).boot(...args)
339
- };
340
- };
341
-
342
- export {
343
- spec,
344
- dot,
345
- ndjson,
346
- github
347
- };