@quenty/cli-output-helpers 1.17.2 → 1.18.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 +4 -0
- package/dist/outputHelper.d.ts +11 -0
- package/dist/outputHelper.d.ts.map +1 -1
- package/dist/outputHelper.js +18 -0
- package/dist/outputHelper.js.map +1 -1
- package/dist/reporting/aggregated-batch-reporter.d.ts +31 -0
- package/dist/reporting/aggregated-batch-reporter.d.ts.map +1 -0
- package/dist/reporting/aggregated-batch-reporter.js +40 -0
- package/dist/reporting/aggregated-batch-reporter.js.map +1 -0
- package/dist/reporting/github/formatting.d.ts.map +1 -1
- package/dist/reporting/github/formatting.js +37 -32
- package/dist/reporting/github/formatting.js.map +1 -1
- package/dist/reporting/grouped-reporter.d.ts.map +1 -1
- package/dist/reporting/grouped-reporter.js +7 -28
- package/dist/reporting/grouped-reporter.js.map +1 -1
- package/dist/reporting/index.d.ts +2 -0
- package/dist/reporting/index.d.ts.map +1 -1
- package/dist/reporting/index.js +2 -0
- package/dist/reporting/index.js.map +1 -1
- package/dist/reporting/reporter.d.ts +17 -0
- package/dist/reporting/reporter.d.ts.map +1 -1
- package/dist/reporting/reporter.js.map +1 -1
- package/dist/reporting/result-status.d.ts +101 -0
- package/dist/reporting/result-status.d.ts.map +1 -0
- package/dist/reporting/result-status.js +138 -0
- package/dist/reporting/result-status.js.map +1 -0
- package/dist/reporting/result-status.test.d.ts +2 -0
- package/dist/reporting/result-status.test.d.ts.map +1 -0
- package/dist/reporting/result-status.test.js +108 -0
- package/dist/reporting/result-status.test.js.map +1 -0
- package/dist/reporting/spinner-reporter.d.ts +1 -0
- package/dist/reporting/spinner-reporter.d.ts.map +1 -1
- package/dist/reporting/spinner-reporter.js +18 -33
- package/dist/reporting/spinner-reporter.js.map +1 -1
- package/dist/reporting/summary-table-reporter.d.ts +7 -2
- package/dist/reporting/summary-table-reporter.d.ts.map +1 -1
- package/dist/reporting/summary-table-reporter.js +16 -27
- package/dist/reporting/summary-table-reporter.js.map +1 -1
- package/package.json +2 -2
- package/src/outputHelper.ts +22 -0
- package/src/reporting/aggregated-batch-reporter.ts +45 -0
- package/src/reporting/github/formatting.ts +52 -41
- package/src/reporting/grouped-reporter.ts +20 -42
- package/src/reporting/index.ts +13 -0
- package/src/reporting/reporter.ts +18 -0
- package/src/reporting/result-status.test.ts +149 -0
- package/src/reporting/result-status.ts +227 -0
- package/src/reporting/spinner-reporter.ts +40 -39
- package/src/reporting/summary-table-reporter.ts +29 -36
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summary-table-reporter.js","sourceRoot":"","sources":["../../src/reporting/summary-table-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAsB,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AAElE,OAAO,
|
|
1
|
+
{"version":3,"file":"summary-table-reporter.js","sourceRoot":"","sources":["../../src/reporting/summary-table-reporter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/cli-output-helpers",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Helpers to generate Nevermore package and game templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"engines": {
|
|
60
60
|
"node": ">=16"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "f75f93d5b2ac51cf7da3b7599788dcce31fd8a29"
|
|
63
63
|
}
|
package/src/outputHelper.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
3
|
+
import { isCI } from './cli-utils.js';
|
|
3
4
|
|
|
4
5
|
export type BoxOptions = {
|
|
5
6
|
centered?: boolean;
|
|
@@ -230,6 +231,27 @@ export class OutputHelper {
|
|
|
230
231
|
console.log(this._hasAnsi(message) ? message : this.formatWarning(message));
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Open a collapsible section, so a run of related output can be folded away.
|
|
236
|
+
*
|
|
237
|
+
* In CI this is a workflow command; elsewhere it is a plain header, since a
|
|
238
|
+
* `::group::` line is only noise in a terminal that cannot fold it. Always
|
|
239
|
+
* pair with {@link endGroup} — an unclosed group swallows everything printed
|
|
240
|
+
* after it.
|
|
241
|
+
*/
|
|
242
|
+
public static startGroup(title: string, useGroups = isCI()): void {
|
|
243
|
+
console.log(
|
|
244
|
+
useGroups ? `::group::${title}` : this.formatDim(`── ${title} ──`)
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Close the section opened by {@link startGroup}. */
|
|
249
|
+
public static endGroup(useGroups = isCI()): void {
|
|
250
|
+
if (useGroups) {
|
|
251
|
+
console.log('::endgroup::');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
233
255
|
/**
|
|
234
256
|
* Logs hint to the console
|
|
235
257
|
* @param message Message to write
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { OutputHelper } from '../outputHelper.js';
|
|
2
|
+
import { BaseReporter } from './reporter.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Batch progress for a run that is one execution rather than many.
|
|
6
|
+
*
|
|
7
|
+
* An aggregated batch has no per-package moment to report: every package starts
|
|
8
|
+
* in the same tick, before the task that runs them exists, and finishes in the
|
|
9
|
+
* same tick when it returns. A reporter built around per-package callbacks can
|
|
10
|
+
* only invent structure that is not there — which is what produced one empty
|
|
11
|
+
* group per package, and filed the shared upload under whichever package's
|
|
12
|
+
* async context happened to reach the promise first.
|
|
13
|
+
*
|
|
14
|
+
* So this one says nothing per package. The run is printed once, by whoever
|
|
15
|
+
* holds it, and the summary table recaps at the end.
|
|
16
|
+
*/
|
|
17
|
+
export class AggregatedBatchReporter extends BaseReporter {
|
|
18
|
+
private _actionVerb: string;
|
|
19
|
+
private _total: number;
|
|
20
|
+
|
|
21
|
+
constructor(total: number, options?: { actionVerb?: string }) {
|
|
22
|
+
super();
|
|
23
|
+
this._total = total;
|
|
24
|
+
this._actionVerb = options?.actionVerb ?? 'Processing';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override async startAsync(): Promise<void> {
|
|
28
|
+
OutputHelper.info(
|
|
29
|
+
`${this._actionVerb} ${this._total} packages in one batch`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Print a run that has already happened: its output, then what reading that
|
|
35
|
+
* output turned up.
|
|
36
|
+
*
|
|
37
|
+
* Taken as rendered lines rather than as a log to render, so the decision of
|
|
38
|
+
* what a line should look like stays in one place and this stays a printer.
|
|
39
|
+
*/
|
|
40
|
+
printRun(renderedLines: string[]): void {
|
|
41
|
+
for (const line of renderedLines) {
|
|
42
|
+
console.log(line);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -15,12 +15,14 @@ import {
|
|
|
15
15
|
type IStateTracker,
|
|
16
16
|
type PackageState,
|
|
17
17
|
} from '../state/state-tracker.js';
|
|
18
|
+
import { formatProgressInline } from '../progress-format.js';
|
|
18
19
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
formatStatusText,
|
|
21
|
+
resolveResultStatus,
|
|
22
|
+
statusIcon,
|
|
23
|
+
tallyCaveats,
|
|
24
|
+
type ResultStatus,
|
|
25
|
+
} from '../result-status.js';
|
|
24
26
|
|
|
25
27
|
// ── Public types ────────────────────────────────────────────────────────────
|
|
26
28
|
|
|
@@ -145,26 +147,37 @@ export function formatResultStatus(
|
|
|
145
147
|
failureLabel: string,
|
|
146
148
|
expectsTestCounts = false
|
|
147
149
|
): string {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
const status = resolveResultStatus(pkg, {
|
|
151
|
+
successLabel,
|
|
152
|
+
failureLabel,
|
|
153
|
+
expectsTestCounts,
|
|
154
|
+
});
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
// Bolded verdict, plain caveats: a reader scans a column of these for the
|
|
157
|
+
// verdict, and bolding the qualifier too would flatten the difference between
|
|
158
|
+
// them. Everything else — which states exist, what they are called, what
|
|
159
|
+
// counts as a warning — is the resolver's.
|
|
160
|
+
const verdict =
|
|
161
|
+
status.severity === 'failure'
|
|
162
|
+
? status.failedPhase
|
|
163
|
+
? `**${status.label}** at ${status.failedPhase}`
|
|
164
|
+
: `**${status.label}**`
|
|
165
|
+
: status.label;
|
|
166
|
+
const caveats = status.caveats.length > 0 ? ` - ${caveatText(status)}` : '';
|
|
167
|
+
const head = [verdict, status.progress].filter(Boolean).join(' ');
|
|
168
|
+
|
|
169
|
+
return `${statusIcon(
|
|
170
|
+
status.severity,
|
|
171
|
+
'emoji'
|
|
172
|
+
)} ${head}${caveats} (${formatDurationMs(pkg.durationMs)})`;
|
|
173
|
+
}
|
|
161
174
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
175
|
+
/** The caveat half of a status line, without the verdict in front of it. */
|
|
176
|
+
function caveatText(status: ResultStatus): string {
|
|
177
|
+
return formatStatusText({ ...status, label: '', progress: '' }).replace(
|
|
178
|
+
/^ *- */,
|
|
179
|
+
''
|
|
180
|
+
);
|
|
168
181
|
}
|
|
169
182
|
|
|
170
183
|
export function getActionsRunUrl(): string | undefined {
|
|
@@ -292,7 +305,7 @@ export function formatGithubTableBody(
|
|
|
292
305
|
statusText = formatResultStatus(
|
|
293
306
|
pkg.result!,
|
|
294
307
|
config.successLabel ?? 'Passed',
|
|
295
|
-
config.failureLabel ?? '
|
|
308
|
+
config.failureLabel ?? 'FAILED',
|
|
296
309
|
config.expectsTestCounts ?? false
|
|
297
310
|
);
|
|
298
311
|
break;
|
|
@@ -314,18 +327,19 @@ export function formatGithubTableBody(
|
|
|
314
327
|
if (allDone) {
|
|
315
328
|
const passed = packages.filter((p) => p.status === 'passed').length;
|
|
316
329
|
const failed = packages.filter((p) => p.status === 'failed').length;
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
330
|
+
// Tallied off the same statuses the rows were drawn from, so a line here
|
|
331
|
+
// cannot contradict the column above it — which it could when each was
|
|
332
|
+
// counted separately. Read from the final result, not the live progress:
|
|
333
|
+
// progress is cleared once a package resolves.
|
|
334
|
+
const statuses = packages
|
|
335
|
+
.filter((p) => p.result !== undefined)
|
|
336
|
+
.map((p) =>
|
|
337
|
+
resolveResultStatus(p.result!, {
|
|
338
|
+
successLabel: config.successLabel ?? 'Passed',
|
|
339
|
+
failureLabel: config.failureLabel ?? 'FAILED',
|
|
340
|
+
expectsTestCounts: config.expectsTestCounts ?? false,
|
|
341
|
+
})
|
|
342
|
+
);
|
|
329
343
|
const verb = config.summaryVerb ?? 'tested';
|
|
330
344
|
const unit = packages.length === 1 ? 'package' : 'packages';
|
|
331
345
|
footer = `**${
|
|
@@ -333,11 +347,8 @@ export function formatGithubTableBody(
|
|
|
333
347
|
} ${unit} ${verb}, ${passed} passed, ${failed} failed** in ${formatDurationMs(
|
|
334
348
|
elapsedMs
|
|
335
349
|
)}`;
|
|
336
|
-
|
|
337
|
-
footer += `\n⚠️ ${
|
|
338
|
-
}
|
|
339
|
-
if (unreported > 0) {
|
|
340
|
-
footer += `\n⚠️ ${unreported} package(s) reported no test counts — a pass here proves nothing about whether tests ran`;
|
|
350
|
+
for (const { message } of tallyCaveats(statuses)) {
|
|
351
|
+
footer += `\n⚠️ ${message}`;
|
|
341
352
|
}
|
|
342
353
|
} else {
|
|
343
354
|
const done = packages.filter(
|
|
@@ -3,10 +3,11 @@ import { formatDurationMs, isCI } from '../cli-utils.js';
|
|
|
3
3
|
import { type PackageResult, BaseReporter } from './reporter.js';
|
|
4
4
|
import { type IStateTracker } from './state/state-tracker.js';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
colorStatus,
|
|
7
|
+
formatStatusText,
|
|
8
|
+
resolveResultStatus,
|
|
9
|
+
statusIcon,
|
|
10
|
+
} from './result-status.js';
|
|
10
11
|
|
|
11
12
|
export interface GroupedReporterOptions {
|
|
12
13
|
showLogs: boolean;
|
|
@@ -79,45 +80,22 @@ export class GroupedReporter extends BaseReporter {
|
|
|
79
80
|
|
|
80
81
|
const showLogs = this._options.showLogs || !result.success;
|
|
81
82
|
const duration = formatDurationMs(result.durationMs);
|
|
82
|
-
const successLabel = this._options.successLabel ?? 'Passed';
|
|
83
|
-
const failureLabel =
|
|
84
|
-
result.failureLabel ?? this._options.failureLabel ?? 'FAILED';
|
|
85
83
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
? OutputHelper.formatWarning(`${label} ⚠`)
|
|
102
|
-
: OutputHelper.formatSuccess(label);
|
|
103
|
-
const icon =
|
|
104
|
-
empty || unverified
|
|
105
|
-
? OutputHelper.formatWarning('⚠')
|
|
106
|
-
: OutputHelper.formatSuccess('✓');
|
|
107
|
-
console.log(
|
|
108
|
-
` ${icon} ${formatted} ${OutputHelper.formatDim(`(${duration})`)}`
|
|
109
|
-
);
|
|
110
|
-
} else {
|
|
111
|
-
const failedPhase = result.failedPhase;
|
|
112
|
-
const label = failedPhase
|
|
113
|
-
? `${failureLabel} at ${failedPhase}`
|
|
114
|
-
: failureLabel;
|
|
115
|
-
console.log(
|
|
116
|
-
` ${OutputHelper.formatError('✗')} ${OutputHelper.formatError(
|
|
117
|
-
label
|
|
118
|
-
)} ${OutputHelper.formatDim(`(${duration})`)}`
|
|
119
|
-
);
|
|
120
|
-
}
|
|
84
|
+
const status = resolveResultStatus(result, {
|
|
85
|
+
successLabel: this._options.successLabel,
|
|
86
|
+
failureLabel: this._options.failureLabel,
|
|
87
|
+
expectsTestCounts: this._options.expectsTestCounts,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(
|
|
91
|
+
` ${colorStatus(
|
|
92
|
+
statusIcon(status.severity, 'ascii'),
|
|
93
|
+
status.severity
|
|
94
|
+
)} ${colorStatus(
|
|
95
|
+
formatStatusText(status),
|
|
96
|
+
status.severity
|
|
97
|
+
)} ${OutputHelper.formatDim(`(${duration})`)}`
|
|
98
|
+
);
|
|
121
99
|
|
|
122
100
|
if (showLogs) {
|
|
123
101
|
if (result.logs) {
|
package/src/reporting/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export {
|
|
|
26
26
|
WatchResultReporter,
|
|
27
27
|
type WatchResultReporterOptions,
|
|
28
28
|
} from './watch-result-reporter.js';
|
|
29
|
+
export { AggregatedBatchReporter } from './aggregated-batch-reporter.js';
|
|
29
30
|
export { CompositeResultReporter } from './composite-result-reporter.js';
|
|
30
31
|
export {
|
|
31
32
|
buildResultReporter,
|
|
@@ -52,6 +53,18 @@ export {
|
|
|
52
53
|
isEmptyTestRun,
|
|
53
54
|
summarizeFailure,
|
|
54
55
|
} from './progress-format.js';
|
|
56
|
+
export {
|
|
57
|
+
colorStatus,
|
|
58
|
+
formatStatusText,
|
|
59
|
+
resolveResultStatus,
|
|
60
|
+
statusIcon,
|
|
61
|
+
tallyCaveats,
|
|
62
|
+
type ResolveResultStatusOptions,
|
|
63
|
+
type ResultCaveat,
|
|
64
|
+
type ResultSeverity,
|
|
65
|
+
type ResultStatus,
|
|
66
|
+
type ResultStatusInput,
|
|
67
|
+
} from './result-status.js';
|
|
55
68
|
|
|
56
69
|
// State tracking
|
|
57
70
|
export {
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
* and end in a terminal state (passed / failed).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Something that makes a result less than it appears.
|
|
10
|
+
*
|
|
11
|
+
* All three are compatible with a pass, which is the point: a run that passed
|
|
12
|
+
* without proving anything must not render as one that did. Declared here
|
|
13
|
+
* rather than beside the resolver that reads them, so a result type can carry
|
|
14
|
+
* one without depending on how it is drawn.
|
|
15
|
+
*/
|
|
16
|
+
export type ResultCaveat = 'no-counts' | 'logs-lost' | 'empty-run';
|
|
17
|
+
|
|
8
18
|
/** Execution phases a package can move through. */
|
|
9
19
|
export type JobPhase =
|
|
10
20
|
| 'waiting'
|
|
@@ -71,6 +81,14 @@ export interface PackageResult {
|
|
|
71
81
|
failedPhase?: JobPhase;
|
|
72
82
|
/** Per-result override of the reporter's default failure label. */
|
|
73
83
|
failureLabel?: string;
|
|
84
|
+
/**
|
|
85
|
+
* What qualifies this result, for facts the reporters cannot derive.
|
|
86
|
+
*
|
|
87
|
+
* `empty-run` and `no-counts` come from the counts themselves and are worked
|
|
88
|
+
* out by `resolveResultStatus`; anything here is something only the producer
|
|
89
|
+
* knows, such as a run whose log output the engine dropped.
|
|
90
|
+
*/
|
|
91
|
+
caveats?: ResultCaveat[];
|
|
74
92
|
}
|
|
75
93
|
|
|
76
94
|
/** Summary of a complete batch run. */
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
formatStatusText,
|
|
4
|
+
resolveResultStatus,
|
|
5
|
+
statusIcon,
|
|
6
|
+
tallyCaveats,
|
|
7
|
+
type ResultStatusInput,
|
|
8
|
+
} from './result-status.js';
|
|
9
|
+
|
|
10
|
+
function counts(passed: number, total: number): ResultStatusInput {
|
|
11
|
+
return {
|
|
12
|
+
success: true,
|
|
13
|
+
progressSummary: {
|
|
14
|
+
kind: 'test-counts',
|
|
15
|
+
passed,
|
|
16
|
+
failed: total - passed,
|
|
17
|
+
total,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('resolveResultStatus', () => {
|
|
23
|
+
it('calls a clean pass a success', () => {
|
|
24
|
+
const status = resolveResultStatus(counts(68, 68));
|
|
25
|
+
|
|
26
|
+
expect(status.severity).toBe('success');
|
|
27
|
+
expect(formatStatusText(status)).toBe('Passed (68/68)');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('keeps a failure a failure however it is qualified', () => {
|
|
31
|
+
// A caveat narrows what a result proves; it never softens a failure into a
|
|
32
|
+
// warning, or a red row would go amber for having lost its logs.
|
|
33
|
+
const status = resolveResultStatus({
|
|
34
|
+
...counts(3, 4),
|
|
35
|
+
success: false,
|
|
36
|
+
caveats: ['logs-lost'],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(status.severity).toBe('failure');
|
|
40
|
+
expect(formatStatusText(status)).toBe('FAILED (3/4) - logs lost');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('demotes a pass that carries a caveat to a warning', () => {
|
|
44
|
+
const status = resolveResultStatus({
|
|
45
|
+
...counts(35, 35),
|
|
46
|
+
caveats: ['logs-lost'],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(status.severity).toBe('warning');
|
|
50
|
+
expect(formatStatusText(status)).toBe('Passed (35/35) - logs lost');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('flags a pass that ran no tests', () => {
|
|
54
|
+
const status = resolveResultStatus(counts(0, 0));
|
|
55
|
+
|
|
56
|
+
expect(status.severity).toBe('warning');
|
|
57
|
+
expect(status.caveats).toEqual(['empty-run']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('calls a pass with no counts unverified, when counts were expected', () => {
|
|
61
|
+
// The claim being avoided: a green row with nothing behind it is
|
|
62
|
+
// indistinguishable from one that tested something.
|
|
63
|
+
const status = resolveResultStatus(
|
|
64
|
+
{ success: true },
|
|
65
|
+
{ expectsTestCounts: true }
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(status.severity).toBe('warning');
|
|
69
|
+
expect(formatStatusText(status)).toBe(
|
|
70
|
+
'Unverified - no test counts reported'
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('says nothing about missing counts where none were expected', () => {
|
|
75
|
+
// A deploy has no test counts and is not suspect for lacking them.
|
|
76
|
+
const status = resolveResultStatus({ success: true });
|
|
77
|
+
|
|
78
|
+
expect(status.severity).toBe('success');
|
|
79
|
+
expect(status.caveats).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('reports every caveat that applies, not just the first', () => {
|
|
83
|
+
const status = resolveResultStatus(
|
|
84
|
+
{ ...counts(0, 0), caveats: ['logs-lost'] },
|
|
85
|
+
{ expectsTestCounts: true }
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(status.caveats).toEqual(['logs-lost', 'empty-run']);
|
|
89
|
+
expect(formatStatusText(status)).toBe(
|
|
90
|
+
'Passed (0/0) - logs lost, ran 0 tests'
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('names the phase a failure happened in', () => {
|
|
95
|
+
const status = resolveResultStatus({
|
|
96
|
+
success: false,
|
|
97
|
+
failedPhase: 'uploading',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(formatStatusText(status)).toBe('FAILED at uploading');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('takes the labels the caller uses for its own kind of run', () => {
|
|
104
|
+
const status = resolveResultStatus(
|
|
105
|
+
{ success: true },
|
|
106
|
+
{ successLabel: 'Deployed' }
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(formatStatusText(status)).toBe('Deployed');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('tallyCaveats', () => {
|
|
114
|
+
it('counts each caveat across a run', () => {
|
|
115
|
+
const statuses = [
|
|
116
|
+
resolveResultStatus({ ...counts(5, 5), caveats: ['logs-lost'] }),
|
|
117
|
+
resolveResultStatus({ ...counts(9, 9), caveats: ['logs-lost'] }),
|
|
118
|
+
resolveResultStatus(counts(0, 0)),
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
expect(tallyCaveats(statuses)).toEqual([
|
|
122
|
+
{
|
|
123
|
+
caveat: 'logs-lost',
|
|
124
|
+
count: 2,
|
|
125
|
+
message: expect.stringContaining('2 package(s) lost their log output'),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
caveat: 'empty-run',
|
|
129
|
+
count: 1,
|
|
130
|
+
message: '1 package(s) ran 0 tests — check test discovery',
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('says nothing when every result is clean', () => {
|
|
136
|
+
expect(tallyCaveats([resolveResultStatus(counts(3, 3))])).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('statusIcon', () => {
|
|
141
|
+
it('uses emoji for markdown and single-width glyphs for a terminal', () => {
|
|
142
|
+
// A variation-selector emoji is two columns wide, which breaks the aligned
|
|
143
|
+
// status column in the summary table.
|
|
144
|
+
expect(statusIcon('warning', 'emoji')).toBe('⚠️');
|
|
145
|
+
expect(statusIcon('warning', 'ascii')).toBe('⚠');
|
|
146
|
+
expect(statusIcon('success', 'ascii')).toBe('✓');
|
|
147
|
+
expect(statusIcon('failure', 'ascii')).toBe('✗');
|
|
148
|
+
});
|
|
149
|
+
});
|