Skip to content

Commit 14f7095

Browse files
nowellsnovemberborn
authored andcommitted
Implement t.log() (#1452)
There are times when you wish to include comments in your tests that will be shown in the context of the test result, and not streamed to `stdout` like `console.log` statements are. Fixes #420.
1 parent e28be05 commit 14f7095

File tree

12 files changed

+309
-26
lines changed

12 files changed

+309
-26
lines changed

lib/assert.js

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function wrapAssertions(callbacks) {
6464
const pass = callbacks.pass;
6565
const pending = callbacks.pending;
6666
const fail = callbacks.fail;
67+
const log = callbacks.log;
6768

6869
const noop = () => {};
6970
const makeRethrow = reason => () => {
@@ -110,6 +111,10 @@ function wrapAssertions(callbacks) {
110111
}
111112
},
112113

114+
log(text) {
115+
log(this, text);
116+
},
117+
113118
deepEqual(actual, expected, message) {
114119
const result = concordance.compare(actual, expected, concordanceOptions);
115120
if (result.pass) {

lib/colors.js

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const chalk = require('chalk');
33

44
module.exports = {
5+
log: chalk.gray,
56
title: chalk.bold.white,
67
error: chalk.red,
78
skip: chalk.yellow,

lib/reporters/mini.js

+16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const lastLineTracker = require('last-line-stream/tracker');
55
const plur = require('plur');
66
const spinners = require('cli-spinners');
77
const chalk = require('chalk');
8+
const figures = require('figures');
89
const cliTruncate = require('cli-truncate');
910
const cross = require('figures').cross;
1011
const indentString = require('indent-string');
@@ -163,6 +164,21 @@ class MiniReporter {
163164
}
164165

165166
status += ' ' + colors.title(test.title) + '\n';
167+
168+
if (test.logs) {
169+
test.logs.forEach(log => {
170+
const logLines = indentString(colors.log(log), 6);
171+
const logLinesWithLeadingFigure = logLines.replace(
172+
/^ {6}/,
173+
` ${colors.information(figures.info)} `
174+
);
175+
176+
status += logLinesWithLeadingFigure + '\n';
177+
});
178+
179+
status += '\n';
180+
}
181+
166182
if (test.error.source) {
167183
status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n';
168184

lib/reporters/tap.js

+22-10
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class TapReporter {
4949
return 'TAP version 13';
5050
}
5151
test(test) {
52-
let output;
52+
const output = [];
5353

5454
let directive = '';
5555
const passed = test.todo ? 'not ok' : 'ok';
@@ -62,17 +62,29 @@ class TapReporter {
6262

6363
const title = stripAnsi(test.title);
6464

65+
const appendLogs = () => {
66+
if (test.logs) {
67+
test.logs.forEach(log => {
68+
const logLines = indentString(log, 4);
69+
const logLinesWithLeadingFigure = logLines.replace(
70+
/^ {4}/,
71+
' * '
72+
);
73+
74+
output.push(logLinesWithLeadingFigure);
75+
});
76+
}
77+
};
78+
79+
output.push(`# ${title}`);
80+
6581
if (test.error) {
66-
output = [
67-
'# ' + title,
68-
format('not ok %d - %s', ++this.i, title),
69-
dumpError(test.error, true)
70-
];
82+
output.push(format('not ok %d - %s', ++this.i, title));
83+
appendLogs();
84+
output.push(dumpError(test.error, true));
7185
} else {
72-
output = [
73-
`# ${title}`,
74-
format('%s %d - %s %s', passed, ++this.i, title, directive).trim()
75-
];
86+
output.push(format('%s %d - %s %s', passed, ++this.i, title, directive).trim());
87+
appendLogs();
7688
}
7789

7890
return output.join('\n');

lib/reporters/verbose.js

+40-16
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,38 @@ class VerboseReporter {
2424
return '';
2525
}
2626
test(test, runStatus) {
27+
const lines = [];
2728
if (test.error) {
28-
return ' ' + colors.error(figures.cross) + ' ' + test.title + ' ' + colors.error(test.error.message);
29-
}
30-
31-
if (test.todo) {
32-
return ' ' + colors.todo('- ' + test.title);
29+
lines.push(' ' + colors.error(figures.cross) + ' ' + test.title + ' ' + colors.error(test.error.message));
30+
} else if (test.todo) {
31+
lines.push(' ' + colors.todo('- ' + test.title));
3332
} else if (test.skip) {
34-
return ' ' + colors.skip('- ' + test.title);
35-
}
33+
lines.push(' ' + colors.skip('- ' + test.title));
34+
} else if (test.failing) {
35+
lines.push(' ' + colors.error(figures.tick) + ' ' + colors.error(test.title));
36+
} else if (runStatus.fileCount === 1 && runStatus.testCount === 1 && test.title === '[anonymous]') {
37+
// No output
38+
} else {
39+
// Display duration only over a threshold
40+
const threshold = 100;
41+
const duration = test.duration > threshold ? colors.duration(' (' + prettyMs(test.duration) + ')') : '';
3642

37-
if (test.failing) {
38-
return ' ' + colors.error(figures.tick) + ' ' + colors.error(test.title);
43+
lines.push(' ' + colors.pass(figures.tick) + ' ' + test.title + duration);
3944
}
4045

41-
if (runStatus.fileCount === 1 && runStatus.testCount === 1 && test.title === '[anonymous]') {
42-
return undefined;
43-
}
46+
if (test.logs) {
47+
test.logs.forEach(log => {
48+
const logLines = indentString(colors.log(log), 6);
49+
const logLinesWithLeadingFigure = logLines.replace(
50+
/^ {6}/,
51+
` ${colors.information(figures.info)} `
52+
);
4453

45-
// Display duration only over a threshold
46-
const threshold = 100;
47-
const duration = test.duration > threshold ? colors.duration(' (' + prettyMs(test.duration) + ')') : '';
54+
lines.push(logLinesWithLeadingFigure);
55+
});
56+
}
4857

49-
return ' ' + colors.pass(figures.tick) + ' ' + test.title + duration;
58+
return lines.length > 0 ? lines.join('\n') : undefined;
5059
}
5160
unhandledError(err) {
5261
if (err.type === 'exception' && err.name === 'AvaError') {
@@ -104,6 +113,21 @@ class VerboseReporter {
104113
}
105114

106115
output += ' ' + colors.title(test.title) + '\n';
116+
117+
if (test.logs) {
118+
test.logs.forEach(log => {
119+
const logLines = indentString(colors.log(log), 6);
120+
const logLinesWithLeadingFigure = logLines.replace(
121+
/^ {6}/,
122+
` ${colors.information(figures.info)} `
123+
);
124+
125+
output += logLinesWithLeadingFigure + '\n';
126+
});
127+
128+
output += '\n';
129+
}
130+
107131
if (test.error.source) {
108132
output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n';
109133

lib/runner.js

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class Runner extends EventEmitter {
130130
addTestResult(result) {
131131
const test = result.result;
132132
const props = {
133+
logs: test.logs,
133134
duration: test.duration,
134135
title: test.title,
135136
error: result.reason,

lib/test.js

+9
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ class ExecutionContext {
7878

7979
{
8080
const assertions = assert.wrapAssertions({
81+
log(executionContext, text) {
82+
executionContext._test.addLog(text);
83+
},
84+
8185
pass(executionContext) {
8286
executionContext._test.countPassedAssertion();
8387
},
@@ -108,6 +112,7 @@ class Test {
108112
this.metadata = options.metadata;
109113
this.onResult = options.onResult;
110114
this.title = options.title;
115+
this.logs = [];
111116

112117
this.snapshotInvocationCount = 0;
113118
this.compareWithSnapshot = assertionOptions => {
@@ -175,6 +180,10 @@ class Test {
175180
this.assertCount++;
176181
}
177182

183+
addLog(text) {
184+
this.logs.push(text);
185+
}
186+
178187
addPendingAssertion(promise) {
179188
if (this.finishing) {
180189
this.saveFirstError(new Error('Assertion passed, but test has already finished'));

readme.md

+4
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,10 @@ Plan how many assertion there are in the test. The test will fail if the actual
875875

876876
End the test. Only works with `test.cb()`.
877877

878+
###### `t.log(message)`
879+
880+
Print a log message contextually alongside the test result instead of immediately printing it to `stdout` like `console.log`.
881+
878882
## Assertions
879883

880884
Assertions are mixed into the [execution object](#t) provided to each test implementation:

test/reporters/mini.js

+54
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const indentString = require('indent-string');
33
const tempWrite = require('temp-write');
44
const flatten = require('arr-flatten');
5+
const figures = require('figures');
56
const chalk = require('chalk');
67
const sinon = require('sinon');
78
const test = require('tap').test;
@@ -934,3 +935,56 @@ test('result when no-color flag is set', t => {
934935
t.is(output, expectedOutput);
935936
t.end();
936937
});
938+
939+
test('results with errors and logs', t => {
940+
const err1 = new Error('failure one');
941+
err1.stack = beautifyStack(err1.stack);
942+
const err1Path = tempWrite.sync('a();');
943+
err1.source = source(err1Path);
944+
err1.avaAssertionError = true;
945+
err1.statements = [];
946+
err1.values = [
947+
{label: 'actual:', formatted: JSON.stringify('abc') + '\n'},
948+
{label: 'expected:', formatted: JSON.stringify('abd') + '\n'}
949+
];
950+
951+
const reporter = miniReporter();
952+
reporter.failCount = 1;
953+
954+
const runStatus = {
955+
errors: [{
956+
title: 'failed one',
957+
logs: ['log from a failed test\nwith a newline', 'another log from failed test'],
958+
error: err1
959+
}]
960+
};
961+
962+
const output = reporter.finish(runStatus);
963+
compareLineOutput(t, output, flatten([
964+
'',
965+
' ' + chalk.red('1 failed'),
966+
'',
967+
' ' + chalk.bold.white('failed one'),
968+
' ' + chalk.magenta(figures.info) + ' ' + chalk.gray('log from a failed test'),
969+
' ' + chalk.gray('with a newline'),
970+
' ' + chalk.magenta(figures.info) + ' ' + chalk.gray('another log from failed test'),
971+
'',
972+
' ' + chalk.grey(`${err1.source.file}:${err1.source.line}`),
973+
'',
974+
indentString(codeExcerpt(err1.source), 2).split('\n'),
975+
'',
976+
/failure one/,
977+
'',
978+
' actual:',
979+
'',
980+
' "abc"',
981+
'',
982+
' expected:',
983+
'',
984+
' "abd"',
985+
'',
986+
stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE,
987+
''
988+
]));
989+
t.end();
990+
});

test/reporters/tap.js

+48
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,51 @@ test('stdout and stderr should call process.stderr.write', t => {
266266
t.is(stub.callCount, 2);
267267
t.end();
268268
});
269+
270+
test('successful test with logs', t => {
271+
const reporter = new TapReporter();
272+
273+
const actualOutput = reporter.test({
274+
title: 'passing',
275+
logs: ['log message 1\nwith a newline', 'log message 2']
276+
});
277+
278+
const expectedOutput = [
279+
'# passing',
280+
'ok 1 - passing',
281+
' * log message 1',
282+
' with a newline',
283+
' * log message 2'
284+
].join('\n');
285+
286+
t.is(actualOutput, expectedOutput);
287+
t.end();
288+
});
289+
290+
test('failing test with logs', t => {
291+
const reporter = new TapReporter();
292+
293+
const actualOutput = reporter.test({
294+
title: 'failing',
295+
error: {
296+
name: 'AssertionError',
297+
message: 'false == true'
298+
},
299+
logs: ['log message 1\nwith a newline', 'log message 2']
300+
});
301+
302+
const expectedOutput = [
303+
'# failing',
304+
'not ok 1 - failing',
305+
' * log message 1',
306+
' with a newline',
307+
' * log message 2',
308+
' ---',
309+
' name: AssertionError',
310+
' message: false == true',
311+
' ...'
312+
].join('\n');
313+
314+
t.is(actualOutput, expectedOutput);
315+
t.end();
316+
});

0 commit comments

Comments
 (0)