Skip to content

Commit 78477eb

Browse files
SimenBcpojer
authored andcommitted
feat(jest-message-util): render codeframe on failure (#5087)
1 parent 6f7c85a commit 78477eb

8 files changed

Lines changed: 178 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969

7070
### Features
7171

72+
* `[jest-message-util]` Add codeframe to test assertion failures
73+
([#5087](https://github.com/facebook/jest/pull/5087))
7274
* `[jest-config]` Add Global Setup/Teardown options
7375
([#4716](https://github.com/facebook/jest/pull/4716))
7476
* `[jest-config]` Add `testEnvironmentOptions` to apply to jsdom options or node

docs/Configuration.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,14 +262,18 @@ here, and some code mutates that value in the midst of running a test, that
262262
mutation will _not_ be persisted across test runs for other test files.
263263

264264
### `globalSetup` [string]
265+
265266
Default: `undefined`
266267

267-
This option allows the use of a custom global setup module which exports an async function that is triggered once before all test suites.
268+
This option allows the use of a custom global setup module which exports an
269+
async function that is triggered once before all test suites.
268270

269271
### `globalTeardown` [string]
272+
270273
Default: `undefined`
271274

272-
This option allows the use of a custom global teardown module which exports an async function that is triggered once after all test suites.
275+
This option allows the use of a custom global teardown module which exports an
276+
async function that is triggered once after all test suites.
273277

274278
### `mapCoverage` [boolean]
275279

docs/TimerMocks.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ test('waits 1 second before ending the game', () => {
3434
const timerGame = require('../timerGame');
3535
timerGame();
3636

37-
expect(setTimeout).toHaveBeenCalledTimes(1)
37+
expect(setTimeout).toHaveBeenCalledTimes(1);
3838
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
3939
});
4040
```
@@ -63,7 +63,7 @@ test('calls the callback after 1 second', () => {
6363

6464
// Now our callback should have been called!
6565
expect(callback).toBeCalled();
66-
expect(callback).toHaveBeenCalledTimes(1)
66+
expect(callback).toHaveBeenCalledTimes(1);
6767
});
6868
```
6969

@@ -110,7 +110,7 @@ describe('infiniteTimerGame', () => {
110110

111111
// At this point in time, there should have been a single call to
112112
// setTimeout to schedule the end of the game in 1 second.
113-
expect(setTimeout).toHaveBeenCalledTimes(1)
113+
expect(setTimeout).toHaveBeenCalledTimes(1);
114114
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
115115

116116
// Fast forward and exhaust only currently pending timers
@@ -122,7 +122,7 @@ describe('infiniteTimerGame', () => {
122122

123123
// And it should have created a new timer to start the game over in
124124
// 10 seconds
125-
expect(setTimeout).toHaveBeenCalledTimes(2)
125+
expect(setTimeout).toHaveBeenCalledTimes(2);
126126
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
127127
});
128128
});
@@ -170,7 +170,7 @@ it('calls the callback after 1 second via advanceTimersByTime', () => {
170170

171171
// Now our callback should have been called!
172172
expect(callback).toBeCalled();
173-
expect(callback).toHaveBeenCalledTimes(1)
173+
expect(callback).toHaveBeenCalledTimes(1);
174174
});
175175
```
176176

integration_tests/__tests__/__snapshots__/failures.test.js.snap

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ exports[`not throwing Error objects 4`] = `
4040
expect(received).toBeTruthy()
4141
Expected value to be truthy, instead received
4242
false
43+
11 | const throws = () => {
44+
12 | expect.assertions(2);
45+
> 13 | expect(false).toBeTruthy();
46+
14 | };
47+
15 | const redeclare = () => {
48+
16 | expect.assertions(1);
4349
at __tests__/assertion_count.test.js:13:17
4450
● .assertions() › throws
4551
expect.assertions(2)
@@ -48,6 +54,12 @@ exports[`not throwing Error objects 4`] = `
4854
expect(received).toBeTruthy()
4955
Expected value to be truthy, instead received
5056
false
57+
15 | const redeclare = () => {
58+
16 | expect.assertions(1);
59+
> 17 | expect(false).toBeTruthy();
60+
18 | expect.assertions(2);
61+
19 | };
62+
20 |
5163
at __tests__/assertion_count.test.js:17:17
5264
● .assertions() › throws on assertion
5365
expect.assertions(0)
@@ -85,6 +97,13 @@ exports[`works with node assert 1`] = `
8597
true
8698
Received:
8799
false
100+
101+
13 |
102+
14 | test('assert', () => {
103+
> 15 | assert(false);
104+
16 | });
105+
17 |
106+
18 | test('assert with a message', () => {
88107
89108
at __tests__/node_assertion_error.test.js:15:3
90109
@@ -99,6 +118,13 @@ exports[`works with node assert 1`] = `
99118
100119
Message:
101120
this is a message
121+
122+
17 |
123+
18 | test('assert with a message', () => {
124+
> 19 | assert(false, 'this is a message');
125+
20 | });
126+
21 |
127+
22 | test('assert.ok', () => {
102128
103129
at __tests__/node_assertion_error.test.js:19:3
104130
@@ -110,6 +136,13 @@ exports[`works with node assert 1`] = `
110136
true
111137
Received:
112138
false
139+
140+
21 |
141+
22 | test('assert.ok', () => {
142+
> 23 | assert.ok(false);
143+
24 | });
144+
25 |
145+
26 | test('assert.ok with a message', () => {
113146
114147
at __tests__/node_assertion_error.test.js:23:10
115148
@@ -124,6 +157,13 @@ exports[`works with node assert 1`] = `
124157
125158
Message:
126159
this is a message
160+
161+
25 |
162+
26 | test('assert.ok with a message', () => {
163+
> 27 | assert.ok(false, 'this is a message');
164+
28 | });
165+
29 |
166+
30 | test('assert.equal', () => {
127167
128168
at __tests__/node_assertion_error.test.js:27:10
129169
@@ -135,6 +175,13 @@ exports[`works with node assert 1`] = `
135175
2
136176
Received:
137177
1
178+
179+
29 |
180+
30 | test('assert.equal', () => {
181+
> 31 | assert.equal(1, 2);
182+
32 | });
183+
33 |
184+
34 | test('assert.notEqual', () => {
138185
139186
at __tests__/node_assertion_error.test.js:31:10
140187
@@ -150,6 +197,13 @@ exports[`works with node assert 1`] = `
150197
Difference:
151198
152199
Compared values have no visual difference.
200+
201+
33 |
202+
34 | test('assert.notEqual', () => {
203+
> 35 | assert.notEqual(1, 1);
204+
36 | });
205+
37 |
206+
38 | test('assert.deepEqual', () => {
153207
154208
at __tests__/node_assertion_error.test.js:35:10
155209
@@ -175,6 +229,13 @@ exports[`works with node assert 1`] = `
175229
},
176230
},
177231
}
232+
233+
37 |
234+
38 | test('assert.deepEqual', () => {
235+
> 39 | assert.deepEqual({a: {b: {c: 5}}}, {a: {b: {c: 6}}});
236+
40 | });
237+
41 |
238+
42 | test('assert.deepEqual with a message', () => {
178239
179240
at __tests__/node_assertion_error.test.js:39:10
180241
@@ -203,6 +264,13 @@ exports[`works with node assert 1`] = `
203264
},
204265
},
205266
}
267+
268+
41 |
269+
42 | test('assert.deepEqual with a message', () => {
270+
> 43 | assert.deepEqual({a: {b: {c: 5}}}, {a: {b: {c: 7}}}, 'this is a message');
271+
44 | });
272+
45 |
273+
46 | test('assert.notDeepEqual', () => {
206274
207275
at __tests__/node_assertion_error.test.js:43:10
208276
@@ -218,6 +286,13 @@ exports[`works with node assert 1`] = `
218286
Difference:
219287
220288
Compared values have no visual difference.
289+
290+
45 |
291+
46 | test('assert.notDeepEqual', () => {
292+
> 47 | assert.notDeepEqual({a: 1}, {a: 1});
293+
48 | });
294+
49 |
295+
50 | test('assert.strictEqual', () => {
221296
222297
at __tests__/node_assertion_error.test.js:47:10
223298
@@ -229,6 +304,13 @@ exports[`works with node assert 1`] = `
229304
NaN
230305
Received:
231306
1
307+
308+
49 |
309+
50 | test('assert.strictEqual', () => {
310+
> 51 | assert.strictEqual(1, NaN);
311+
52 | });
312+
53 |
313+
54 | test('assert.notStrictEqual', () => {
232314
233315
at __tests__/node_assertion_error.test.js:51:10
234316
@@ -247,6 +329,13 @@ exports[`works with node assert 1`] = `
247329
Difference:
248330
249331
Compared values have no visual difference.
332+
333+
53 |
334+
54 | test('assert.notStrictEqual', () => {
335+
> 55 | assert.notStrictEqual(1, 1, 'My custom error message');
336+
56 | });
337+
57 |
338+
58 | test('assert.deepStrictEqual', () => {
250339
251340
at __tests__/node_assertion_error.test.js:55:10
252341
@@ -268,6 +357,13 @@ exports[`works with node assert 1`] = `
268357
- \\"a\\": 2,
269358
+ \\"a\\": 1,
270359
}
360+
361+
57 |
362+
58 | test('assert.deepStrictEqual', () => {
363+
> 59 | assert.deepStrictEqual({a: 1}, {a: 2});
364+
60 | });
365+
61 |
366+
62 | test('assert.notDeepStrictEqual', () => {
271367
272368
at __tests__/node_assertion_error.test.js:59:10
273369
@@ -283,6 +379,13 @@ exports[`works with node assert 1`] = `
283379
Difference:
284380
285381
Compared values have no visual difference.
382+
383+
61 |
384+
62 | test('assert.notDeepStrictEqual', () => {
385+
> 63 | assert.notDeepStrictEqual({a: 1}, {a: 1});
386+
64 | });
387+
65 |
388+
66 | test('assert.ifError', () => {
286389
287390
at __tests__/node_assertion_error.test.js:63:10
288391
@@ -301,6 +404,13 @@ exports[`works with node assert 1`] = `
301404
302405
Message:
303406
Got unwanted exception.
407+
408+
69 |
409+
70 | test('assert.doesNotThrow', () => {
410+
> 71 | assert.doesNotThrow(() => {
411+
72 | throw Error('err!');
412+
73 | });
413+
74 | });
304414
305415
at __tests__/node_assertion_error.test.js:71:10
306416
@@ -313,6 +423,12 @@ exports[`works with node assert 1`] = `
313423
314424
Message:
315425
Missing expected exception.
426+
427+
75 |
428+
76 | test('assert.throws', () => {
429+
> 77 | assert.throws(() => {});
430+
78 | });
431+
79 |
316432
317433
at __tests__/node_assertion_error.test.js:77:10
318434

integration_tests/__tests__/failures.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ test('works with node assert', () => {
7272
Got unwanted exception.
7373
err!
7474
err!
75+
76+
69 |
77+
70 | test('assert.doesNotThrow', () => {
78+
> 71 | assert.doesNotThrow(() => {
79+
72 | throw Error('err!');
80+
73 | });
81+
74 | });
7582
7683
at __tests__/node_assertion_error.test.js:71:10
7784
`);

packages/jest-message-util/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"license": "MIT",
99
"main": "build/index.js",
1010
"dependencies": {
11+
"@babel/code-frame": "^7.0.0-beta.35",
1112
"chalk": "^2.0.1",
1213
"micromatch": "^2.3.11",
1314
"slash": "^1.0.0",

packages/jest-message-util/src/index.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import type {Glob, Path} from 'types/Config';
1111
import type {AssertionResult, TestResult} from 'types/TestResult';
1212

13+
import fs from 'fs';
1314
import path from 'path';
1415
import chalk from 'chalk';
1516
import micromatch from 'micromatch';
1617
import slash from 'slash';
18+
import {codeFrameColumns} from '@babel/code-frame';
1719
import StackUtils from 'stack-utils';
1820

1921
let nodeInternals = [];
@@ -194,15 +196,45 @@ export const formatStackTrace = (
194196
testPath: ?Path,
195197
) => {
196198
let lines = stack.split(/\n/);
199+
let renderedCallsite = '';
197200
const relativeTestPath = testPath
198201
? slash(path.relative(config.rootDir, testPath))
199202
: null;
200203
lines = removeInternalStackEntries(lines, options);
201-
return lines
204+
205+
if (testPath) {
206+
const topFrame = lines
207+
.join('\n')
208+
.trim()
209+
.split('\n')[0];
210+
211+
const parsedFrame = StackUtils.parseLine(topFrame);
212+
213+
if (parsedFrame) {
214+
renderedCallsite = codeFrameColumns(
215+
fs.readFileSync(testPath, 'utf8'),
216+
{
217+
start: {line: parsedFrame.line},
218+
},
219+
{highlightCode: true},
220+
);
221+
222+
renderedCallsite = renderedCallsite
223+
.split('\n')
224+
.map(line => MESSAGE_INDENT + line)
225+
.join('\n');
226+
227+
renderedCallsite = `\n${renderedCallsite}\n`;
228+
}
229+
}
230+
231+
const stacktrace = lines
202232
.map(trimPaths)
203233
.map(formatPaths.bind(null, config, options, relativeTestPath))
204234
.map(line => STACK_INDENT + line)
205235
.join('\n');
236+
237+
return renderedCallsite + stacktrace;
206238
};
207239

208240
export const formatResultsErrors = (

0 commit comments

Comments
 (0)