@sharpee/transcript-tester 0.9.61-beta
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/.turbo/turbo-build.log +4 -0
- package/LICENSE +21 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +367 -0
- package/dist/cli.js.map +1 -0
- package/dist/condition-evaluator.d.ts +30 -0
- package/dist/condition-evaluator.d.ts.map +1 -0
- package/dist/condition-evaluator.js +314 -0
- package/dist/condition-evaluator.js.map +1 -0
- package/dist/fast-cli.d.ts +13 -0
- package/dist/fast-cli.d.ts.map +1 -0
- package/dist/fast-cli.js +363 -0
- package/dist/fast-cli.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/navigator.d.ts +27 -0
- package/dist/navigator.d.ts.map +1 -0
- package/dist/navigator.js +303 -0
- package/dist/navigator.js.map +1 -0
- package/dist/parser.d.ts +19 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +453 -0
- package/dist/parser.js.map +1 -0
- package/dist/reporter.d.ts +41 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +386 -0
- package/dist/reporter.js.map +1 -0
- package/dist/runner.d.ts +44 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +977 -0
- package/dist/runner.js.map +1 -0
- package/dist/story-loader.d.ts +31 -0
- package/dist/story-loader.d.ts.map +1 -0
- package/dist/story-loader.js +169 -0
- package/dist/story-loader.js.map +1 -0
- package/dist/types.d.ts +204 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist-esm/cli.d.ts +11 -0
- package/dist-esm/cli.d.ts.map +1 -0
- package/dist-esm/cli.js +332 -0
- package/dist-esm/cli.js.map +1 -0
- package/dist-esm/condition-evaluator.d.ts +30 -0
- package/dist-esm/condition-evaluator.d.ts.map +1 -0
- package/dist-esm/condition-evaluator.js +311 -0
- package/dist-esm/condition-evaluator.js.map +1 -0
- package/dist-esm/fast-cli.d.ts +13 -0
- package/dist-esm/fast-cli.d.ts.map +1 -0
- package/dist-esm/fast-cli.js +328 -0
- package/dist-esm/fast-cli.js.map +1 -0
- package/dist-esm/index.d.ts +17 -0
- package/dist-esm/index.d.ts.map +1 -0
- package/dist-esm/index.js +21 -0
- package/dist-esm/index.js.map +1 -0
- package/dist-esm/navigator.d.ts +27 -0
- package/dist-esm/navigator.d.ts.map +1 -0
- package/dist-esm/navigator.js +300 -0
- package/dist-esm/navigator.js.map +1 -0
- package/dist-esm/parser.d.ts +19 -0
- package/dist-esm/parser.d.ts.map +1 -0
- package/dist-esm/parser.js +415 -0
- package/dist-esm/parser.js.map +1 -0
- package/dist-esm/reporter.d.ts +41 -0
- package/dist-esm/reporter.d.ts.map +1 -0
- package/dist-esm/reporter.js +342 -0
- package/dist-esm/reporter.js.map +1 -0
- package/dist-esm/runner.d.ts +44 -0
- package/dist-esm/runner.d.ts.map +1 -0
- package/dist-esm/runner.js +941 -0
- package/dist-esm/runner.js.map +1 -0
- package/dist-esm/story-loader.d.ts +31 -0
- package/dist-esm/story-loader.d.ts.map +1 -0
- package/dist-esm/story-loader.js +131 -0
- package/dist-esm/story-loader.js.map +1 -0
- package/dist-esm/types.d.ts +204 -0
- package/dist-esm/types.d.ts.map +1 -0
- package/dist-esm/types.js +7 -0
- package/dist-esm/types.js.map +1 -0
- package/dist-npm/cli.d.ts +11 -0
- package/dist-npm/cli.d.ts.map +1 -0
- package/dist-npm/cli.js +367 -0
- package/dist-npm/cli.js.map +1 -0
- package/dist-npm/condition-evaluator.d.ts +30 -0
- package/dist-npm/condition-evaluator.d.ts.map +1 -0
- package/dist-npm/condition-evaluator.js +314 -0
- package/dist-npm/condition-evaluator.js.map +1 -0
- package/dist-npm/fast-cli.d.ts +13 -0
- package/dist-npm/fast-cli.d.ts.map +1 -0
- package/dist-npm/fast-cli.js +363 -0
- package/dist-npm/fast-cli.js.map +1 -0
- package/dist-npm/index.d.ts +17 -0
- package/dist-npm/index.d.ts.map +1 -0
- package/dist-npm/index.js +48 -0
- package/dist-npm/index.js.map +1 -0
- package/dist-npm/navigator.d.ts +27 -0
- package/dist-npm/navigator.d.ts.map +1 -0
- package/dist-npm/navigator.js +303 -0
- package/dist-npm/navigator.js.map +1 -0
- package/dist-npm/parser.d.ts +19 -0
- package/dist-npm/parser.d.ts.map +1 -0
- package/dist-npm/parser.js +453 -0
- package/dist-npm/parser.js.map +1 -0
- package/dist-npm/reporter.d.ts +41 -0
- package/dist-npm/reporter.d.ts.map +1 -0
- package/dist-npm/reporter.js +386 -0
- package/dist-npm/reporter.js.map +1 -0
- package/dist-npm/runner.d.ts +44 -0
- package/dist-npm/runner.d.ts.map +1 -0
- package/dist-npm/runner.js +977 -0
- package/dist-npm/runner.js.map +1 -0
- package/dist-npm/story-loader.d.ts +31 -0
- package/dist-npm/story-loader.d.ts.map +1 -0
- package/dist-npm/story-loader.js +169 -0
- package/dist-npm/story-loader.js.map +1 -0
- package/dist-npm/types.d.ts +204 -0
- package/dist-npm/types.d.ts.map +1 -0
- package/dist-npm/types.js +8 -0
- package/dist-npm/types.js.map +1 -0
- package/package.json +49 -0
- package/src/cli.ts +385 -0
- package/src/condition-evaluator.ts +382 -0
- package/src/fast-cli.ts +403 -0
- package/src/index.ts +26 -0
- package/src/navigator.ts +365 -0
- package/src/parser.ts +488 -0
- package/src/reporter.ts +409 -0
- package/src/runner.ts +1152 -0
- package/src/story-loader.ts +168 -0
- package/src/types.ts +244 -0
- package/tsconfig.esm.json +11 -0
- package/tsconfig.esm.tsbuildinfo +1 -0
- package/tsconfig.json +22 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/reporter.ts
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript Test Reporter
|
|
3
|
+
*
|
|
4
|
+
* Formats and displays test results with colors and diffs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import {
|
|
10
|
+
TranscriptResult,
|
|
11
|
+
TestRunResult,
|
|
12
|
+
CommandResult,
|
|
13
|
+
AssertionResult
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
// Use chalk for colors (chalk@4 for CommonJS compatibility)
|
|
17
|
+
import chalk from 'chalk';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Report options
|
|
21
|
+
*/
|
|
22
|
+
export interface ReporterOptions {
|
|
23
|
+
verbose?: boolean;
|
|
24
|
+
showDiff?: boolean;
|
|
25
|
+
color?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Report results of running a single transcript
|
|
30
|
+
*/
|
|
31
|
+
export function reportTranscript(
|
|
32
|
+
result: TranscriptResult,
|
|
33
|
+
options: ReporterOptions = {}
|
|
34
|
+
): void {
|
|
35
|
+
const { verbose = false } = options;
|
|
36
|
+
|
|
37
|
+
console.log();
|
|
38
|
+
console.log(chalk.bold(`Running: ${result.transcript.filePath}`));
|
|
39
|
+
|
|
40
|
+
if (result.transcript.header.title) {
|
|
41
|
+
console.log(chalk.gray(` "${result.transcript.header.title}"`));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log();
|
|
45
|
+
|
|
46
|
+
for (const cmd of result.commands) {
|
|
47
|
+
reportCommand(cmd, verbose);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Summary line
|
|
51
|
+
console.log();
|
|
52
|
+
reportTranscriptSummary(result);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Report a single command result
|
|
57
|
+
*/
|
|
58
|
+
function reportCommand(result: CommandResult, verbose: boolean): void {
|
|
59
|
+
const { command, passed, expectedFailure, skipped, actualOutput, error } = result;
|
|
60
|
+
|
|
61
|
+
// Command input
|
|
62
|
+
const inputDisplay = chalk.cyan(`> ${command.input}`);
|
|
63
|
+
|
|
64
|
+
// Status indicator
|
|
65
|
+
let status: string;
|
|
66
|
+
if (skipped) {
|
|
67
|
+
status = chalk.yellow('SKIP');
|
|
68
|
+
} else if (expectedFailure) {
|
|
69
|
+
status = chalk.magenta('EXPECTED FAIL');
|
|
70
|
+
} else if (passed) {
|
|
71
|
+
status = chalk.green('PASS');
|
|
72
|
+
} else {
|
|
73
|
+
status = chalk.red('FAIL');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Compact format
|
|
77
|
+
console.log(` ${inputDisplay.padEnd(50)} ${status}`);
|
|
78
|
+
|
|
79
|
+
// Verbose output
|
|
80
|
+
if (verbose || (!passed && !skipped && !expectedFailure)) {
|
|
81
|
+
if (error) {
|
|
82
|
+
console.log(chalk.red(` Error: ${error}`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Show assertion details
|
|
86
|
+
for (const ar of result.assertionResults) {
|
|
87
|
+
if (!ar.passed || verbose) {
|
|
88
|
+
const icon = ar.passed ? chalk.green('✓') : chalk.red('✗');
|
|
89
|
+
const msg = formatAssertion(ar);
|
|
90
|
+
console.log(` ${icon} ${msg}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Always show actual output in verbose mode, or for failures
|
|
95
|
+
if (verbose || (!passed && !skipped)) {
|
|
96
|
+
console.log(chalk.gray(' ─── Output ───'));
|
|
97
|
+
for (const line of actualOutput.split('\n')) {
|
|
98
|
+
if (line.trim()) {
|
|
99
|
+
console.log(chalk.white(` ${line}`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
console.log(chalk.gray(' ─────────────'));
|
|
103
|
+
|
|
104
|
+
// Show events in verbose mode
|
|
105
|
+
if (verbose && result.actualEvents && result.actualEvents.length > 0) {
|
|
106
|
+
console.log(chalk.gray(` ─── Events (${result.actualEvents.length}) ───`));
|
|
107
|
+
for (const event of result.actualEvents) {
|
|
108
|
+
const dataStr = Object.keys(event.data).length > 0
|
|
109
|
+
? ` ${chalk.gray(JSON.stringify(event.data))}`
|
|
110
|
+
: '';
|
|
111
|
+
console.log(chalk.blue(` • ${event.type}`) + dataStr);
|
|
112
|
+
}
|
|
113
|
+
console.log(chalk.gray(' ─────────────'));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Show diff for failures with expected output
|
|
118
|
+
if (!passed && !skipped && command.expectedOutput.length > 0) {
|
|
119
|
+
console.log();
|
|
120
|
+
console.log(chalk.gray(' Expected:'));
|
|
121
|
+
for (const line of command.expectedOutput) {
|
|
122
|
+
console.log(chalk.green(` + ${line}`));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Show reason for skips and expected failures
|
|
128
|
+
if (skipped || expectedFailure) {
|
|
129
|
+
const reason = result.assertionResults[0]?.assertion.reason;
|
|
130
|
+
if (reason) {
|
|
131
|
+
console.log(chalk.gray(` (${reason})`));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Format an assertion result for display
|
|
138
|
+
*/
|
|
139
|
+
function formatAssertion(result: AssertionResult): string {
|
|
140
|
+
const { assertion, message } = result;
|
|
141
|
+
|
|
142
|
+
switch (assertion.type) {
|
|
143
|
+
case 'ok':
|
|
144
|
+
return message || 'Exact match';
|
|
145
|
+
case 'ok-contains':
|
|
146
|
+
return message || `Contains "${assertion.value}"`;
|
|
147
|
+
case 'ok-not-contains':
|
|
148
|
+
return message || `Does not contain "${assertion.value}"`;
|
|
149
|
+
case 'ok-matches':
|
|
150
|
+
return message || `Matches ${assertion.pattern}`;
|
|
151
|
+
case 'fail':
|
|
152
|
+
return `Expected failure: ${assertion.reason}`;
|
|
153
|
+
case 'skip':
|
|
154
|
+
return `Skipped: ${assertion.reason || 'no reason given'}`;
|
|
155
|
+
case 'todo':
|
|
156
|
+
return `TODO: ${assertion.reason || 'not implemented'}`;
|
|
157
|
+
case 'event-count':
|
|
158
|
+
return message || `Event count: ${assertion.eventCount}`;
|
|
159
|
+
case 'event-assert': {
|
|
160
|
+
const prefix = assertion.assertTrue ? 'assertTrue' : 'assertFalse';
|
|
161
|
+
const posStr = assertion.eventPosition ? ` Event ${assertion.eventPosition}:` : '';
|
|
162
|
+
const dataStr = assertion.eventData ? ` ${JSON.stringify(assertion.eventData)}` : '';
|
|
163
|
+
return message || `${prefix}:${posStr} ${assertion.eventType}${dataStr}`;
|
|
164
|
+
}
|
|
165
|
+
case 'state-assert': {
|
|
166
|
+
const prefix = assertion.assertTrue ? 'assertTrue' : 'assertFalse';
|
|
167
|
+
return message || `${prefix}: ${assertion.stateExpression}`;
|
|
168
|
+
}
|
|
169
|
+
default:
|
|
170
|
+
return message || 'Unknown assertion';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Report transcript summary
|
|
176
|
+
*/
|
|
177
|
+
function reportTranscriptSummary(result: TranscriptResult): void {
|
|
178
|
+
const { passed, failed, expectedFailures, skipped, duration } = result;
|
|
179
|
+
|
|
180
|
+
const parts: string[] = [];
|
|
181
|
+
|
|
182
|
+
if (passed > 0) {
|
|
183
|
+
parts.push(chalk.green(`${passed} passed`));
|
|
184
|
+
}
|
|
185
|
+
if (failed > 0) {
|
|
186
|
+
parts.push(chalk.red(`${failed} failed`));
|
|
187
|
+
}
|
|
188
|
+
if (expectedFailures > 0) {
|
|
189
|
+
parts.push(chalk.magenta(`${expectedFailures} expected failures`));
|
|
190
|
+
}
|
|
191
|
+
if (skipped > 0) {
|
|
192
|
+
parts.push(chalk.yellow(`${skipped} skipped`));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(` ${parts.join(', ')} (${duration}ms)`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Report results of running multiple transcripts
|
|
200
|
+
* Note: Individual transcripts should already be reported as they run.
|
|
201
|
+
* This function only shows the aggregate summary.
|
|
202
|
+
*/
|
|
203
|
+
export function reportTestRun(result: TestRunResult, options: ReporterOptions = {}): void {
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
206
|
+
console.log();
|
|
207
|
+
|
|
208
|
+
// Overall summary
|
|
209
|
+
const { totalPassed, totalFailed, totalExpectedFailures, totalSkipped, totalDuration } = result;
|
|
210
|
+
const total = totalPassed + totalFailed + totalExpectedFailures + totalSkipped;
|
|
211
|
+
|
|
212
|
+
console.log(chalk.bold(`Total: ${total} tests in ${result.transcripts.length} transcripts`));
|
|
213
|
+
|
|
214
|
+
const parts: string[] = [];
|
|
215
|
+
if (totalPassed > 0) {
|
|
216
|
+
parts.push(chalk.green(`${totalPassed} passed`));
|
|
217
|
+
}
|
|
218
|
+
if (totalFailed > 0) {
|
|
219
|
+
parts.push(chalk.red(`${totalFailed} failed`));
|
|
220
|
+
}
|
|
221
|
+
if (totalExpectedFailures > 0) {
|
|
222
|
+
parts.push(chalk.magenta(`${totalExpectedFailures} expected failures`));
|
|
223
|
+
}
|
|
224
|
+
if (totalSkipped > 0) {
|
|
225
|
+
parts.push(chalk.yellow(`${totalSkipped} skipped`));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(parts.join(', '));
|
|
229
|
+
console.log(chalk.gray(`Duration: ${totalDuration}ms`));
|
|
230
|
+
console.log();
|
|
231
|
+
|
|
232
|
+
// Final status
|
|
233
|
+
if (totalFailed === 0) {
|
|
234
|
+
console.log(chalk.green.bold('✓ All tests passed!'));
|
|
235
|
+
} else {
|
|
236
|
+
console.log(chalk.red.bold(`✗ ${totalFailed} test(s) failed`));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get exit code based on results
|
|
242
|
+
*/
|
|
243
|
+
export function getExitCode(result: TestRunResult): number {
|
|
244
|
+
if (result.totalFailed > 0) {
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Generate a timestamp string for filenames
|
|
252
|
+
*/
|
|
253
|
+
export function generateTimestamp(): string {
|
|
254
|
+
const now = new Date();
|
|
255
|
+
const year = now.getFullYear();
|
|
256
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
257
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
258
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
259
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
260
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
261
|
+
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Write test results to a JSON file
|
|
266
|
+
*/
|
|
267
|
+
export function writeResultsToJson(
|
|
268
|
+
result: TestRunResult,
|
|
269
|
+
outputDir: string,
|
|
270
|
+
timestamp: string
|
|
271
|
+
): string {
|
|
272
|
+
// Ensure output directory exists
|
|
273
|
+
if (!fs.existsSync(outputDir)) {
|
|
274
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const filename = `results_${timestamp}.json`;
|
|
278
|
+
const filepath = path.join(outputDir, filename);
|
|
279
|
+
|
|
280
|
+
// Write JSON with serializable data (strip non-serializable like RegExp)
|
|
281
|
+
const serializableResult = JSON.parse(JSON.stringify(result, (key, value) => {
|
|
282
|
+
if (value instanceof RegExp) {
|
|
283
|
+
return value.toString();
|
|
284
|
+
}
|
|
285
|
+
return value;
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
fs.writeFileSync(filepath, JSON.stringify(serializableResult, null, 2));
|
|
289
|
+
return filepath;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Write a human-readable report to a text file
|
|
294
|
+
*/
|
|
295
|
+
export function writeReportToFile(
|
|
296
|
+
result: TestRunResult,
|
|
297
|
+
outputDir: string,
|
|
298
|
+
timestamp: string
|
|
299
|
+
): string {
|
|
300
|
+
// Ensure output directory exists
|
|
301
|
+
if (!fs.existsSync(outputDir)) {
|
|
302
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const filename = `report_${timestamp}.txt`;
|
|
306
|
+
const filepath = path.join(outputDir, filename);
|
|
307
|
+
|
|
308
|
+
const lines: string[] = [];
|
|
309
|
+
|
|
310
|
+
lines.push('=' .repeat(60));
|
|
311
|
+
lines.push('TRANSCRIPT TEST REPORT');
|
|
312
|
+
lines.push(`Generated: ${new Date().toISOString()}`);
|
|
313
|
+
lines.push('='.repeat(60));
|
|
314
|
+
lines.push('');
|
|
315
|
+
|
|
316
|
+
// Overall summary
|
|
317
|
+
const { totalPassed, totalFailed, totalExpectedFailures, totalSkipped, totalDuration } = result;
|
|
318
|
+
const total = totalPassed + totalFailed + totalExpectedFailures + totalSkipped;
|
|
319
|
+
|
|
320
|
+
lines.push(`Total: ${total} tests in ${result.transcripts.length} transcript(s)`);
|
|
321
|
+
lines.push(` Passed: ${totalPassed}`);
|
|
322
|
+
lines.push(` Failed: ${totalFailed}`);
|
|
323
|
+
lines.push(` Expected Failures: ${totalExpectedFailures}`);
|
|
324
|
+
lines.push(` Skipped: ${totalSkipped}`);
|
|
325
|
+
lines.push(` Duration: ${totalDuration}ms`);
|
|
326
|
+
lines.push('');
|
|
327
|
+
|
|
328
|
+
// Per-transcript details
|
|
329
|
+
for (const transcript of result.transcripts) {
|
|
330
|
+
lines.push('-'.repeat(60));
|
|
331
|
+
lines.push(`Transcript: ${transcript.transcript.filePath}`);
|
|
332
|
+
if (transcript.transcript.header.title) {
|
|
333
|
+
lines.push(` Title: ${transcript.transcript.header.title}`);
|
|
334
|
+
}
|
|
335
|
+
lines.push(` Results: ${transcript.passed} passed, ${transcript.failed} failed, ${transcript.expectedFailures} expected failures, ${transcript.skipped} skipped`);
|
|
336
|
+
lines.push('');
|
|
337
|
+
|
|
338
|
+
// Command details (only show failures in summary)
|
|
339
|
+
const failedCommands = transcript.commands.filter(c => !c.passed && !c.skipped && !c.expectedFailure);
|
|
340
|
+
if (failedCommands.length > 0) {
|
|
341
|
+
lines.push(' FAILURES:');
|
|
342
|
+
for (const cmd of failedCommands) {
|
|
343
|
+
lines.push(` Line ${cmd.command.lineNumber}: > ${cmd.command.input}`);
|
|
344
|
+
if (cmd.error) {
|
|
345
|
+
lines.push(` Error: ${cmd.error}`);
|
|
346
|
+
}
|
|
347
|
+
for (const ar of cmd.assertionResults) {
|
|
348
|
+
if (!ar.passed) {
|
|
349
|
+
lines.push(` - ${ar.message || formatAssertionPlain(ar.assertion)}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
lines.push(' Actual output:');
|
|
353
|
+
for (const line of cmd.actualOutput.split('\n')) {
|
|
354
|
+
if (line.trim()) {
|
|
355
|
+
lines.push(` ${line}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
lines.push('');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
lines.push('='.repeat(60));
|
|
364
|
+
if (totalFailed === 0) {
|
|
365
|
+
lines.push('ALL TESTS PASSED');
|
|
366
|
+
} else {
|
|
367
|
+
lines.push(`${totalFailed} TEST(S) FAILED`);
|
|
368
|
+
}
|
|
369
|
+
lines.push('='.repeat(60));
|
|
370
|
+
|
|
371
|
+
fs.writeFileSync(filepath, lines.join('\n'));
|
|
372
|
+
return filepath;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Format an assertion without chalk colors (for file output)
|
|
377
|
+
*/
|
|
378
|
+
function formatAssertionPlain(assertion: any): string {
|
|
379
|
+
switch (assertion.type) {
|
|
380
|
+
case 'ok':
|
|
381
|
+
return 'Exact match';
|
|
382
|
+
case 'ok-contains':
|
|
383
|
+
return `Contains "${assertion.value}"`;
|
|
384
|
+
case 'ok-not-contains':
|
|
385
|
+
return `Does not contain "${assertion.value}"`;
|
|
386
|
+
case 'ok-matches':
|
|
387
|
+
return `Matches ${assertion.pattern}`;
|
|
388
|
+
case 'fail':
|
|
389
|
+
return `Expected failure: ${assertion.reason}`;
|
|
390
|
+
case 'skip':
|
|
391
|
+
return `Skipped: ${assertion.reason || 'no reason given'}`;
|
|
392
|
+
case 'todo':
|
|
393
|
+
return `TODO: ${assertion.reason || 'not implemented'}`;
|
|
394
|
+
case 'event-count':
|
|
395
|
+
return `Event count: ${assertion.eventCount}`;
|
|
396
|
+
case 'event-assert': {
|
|
397
|
+
const prefix = assertion.assertTrue ? 'assertTrue' : 'assertFalse';
|
|
398
|
+
const posStr = assertion.eventPosition ? ` Event ${assertion.eventPosition}:` : '';
|
|
399
|
+
const dataStr = assertion.eventData ? ` ${JSON.stringify(assertion.eventData)}` : '';
|
|
400
|
+
return `${prefix}:${posStr} ${assertion.eventType}${dataStr}`;
|
|
401
|
+
}
|
|
402
|
+
case 'state-assert': {
|
|
403
|
+
const prefix = assertion.assertTrue ? 'assertTrue' : 'assertFalse';
|
|
404
|
+
return `${prefix}: ${assertion.stateExpression}`;
|
|
405
|
+
}
|
|
406
|
+
default:
|
|
407
|
+
return 'Unknown assertion';
|
|
408
|
+
}
|
|
409
|
+
}
|