Skip to content

Commit ff62d97

Browse files
author
vdemedes
committed
add magic assert
1 parent 2aa85b6 commit ff62d97

11 files changed

+259
-81
lines changed

lib/assert.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const indentString = require('indent-string');
77
const isObservable = require('is-observable');
88
const isPromise = require('is-promise');
99
const jestSnapshot = require('jest-snapshot');
10+
const reactElementToJSXString = require('react-element-to-jsx-string').default;
1011
const snapshotState = require('./snapshot-state');
1112

1213
const x = module.exports;
@@ -18,7 +19,7 @@ function create(val, expected, operator, msg, fn) {
1819
return {
1920
actual: val,
2021
expected,
21-
message: msg,
22+
message: msg || ' ',
2223
operator,
2324
stackStartFunction: fn
2425
};
@@ -151,6 +152,16 @@ x.ifError = (err, msg) => {
151152
test(!err, create(err, 'Error', '!==', msg, x.ifError));
152153
};
153154

155+
x.jsxEqual = (val, expected, msg) => {
156+
const treeEquals = reactElementToJSXString(val) === reactElementToJSXString(expected);
157+
test(treeEquals, create(val, expected, '===', msg, x.jsxEqual));
158+
};
159+
160+
x.notJsxEqual = (val, expected, msg) => {
161+
const treeEquals = reactElementToJSXString(val) === reactElementToJSXString(expected);
162+
test(!treeEquals, create(val, expected, '!==', msg, x.jsxEqual));
163+
};
164+
154165
x._snapshot = function (tree, optionalMessage, match, snapshotStateGetter) {
155166
// Set defaults - this allows tests to mock deps easily
156167
const toMatchSnapshot = match || jestSnapshot.toMatchSnapshot;

lib/cli.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ exports.run = function () {
103103
throw new Error(colors.error(figures.cross) + ' The --require and -r flags are deprecated. Requirements should be configured in package.json - see documentation.');
104104
}
105105

106+
var resolveTestsFrom = cli.input.length === 0 ? pkgDir : process.cwd();
107+
106108
var api = new Api({
107109
failFast: cli.flags.failFast,
108110
serial: cli.flags.serial,
@@ -112,7 +114,7 @@ exports.run = function () {
112114
explicitTitles: cli.flags.watch,
113115
match: arrify(cli.flags.match),
114116
babelConfig: babelConfig.validate(conf.babel),
115-
resolveTestsFrom: cli.input.length === 0 ? pkgDir : process.cwd(),
117+
resolveTestsFrom: resolveTestsFrom,
116118
pkgDir: pkgDir,
117119
timeout: cli.flags.timeout,
118120
concurrency: cli.flags.concurrency ? parseInt(cli.flags.concurrency, 10) : 0,
@@ -124,9 +126,9 @@ exports.run = function () {
124126
if (cli.flags.tap && !cli.flags.watch) {
125127
reporter = tapReporter();
126128
} else if (cli.flags.verbose || isCi) {
127-
reporter = verboseReporter();
129+
reporter = verboseReporter({basePath: resolveTestsFrom});
128130
} else {
129-
reporter = miniReporter({watching: cli.flags.watch});
131+
reporter = miniReporter({watching: cli.flags.watch, basePath: resolveTestsFrom});
130132
}
131133

132134
reporter.api = api;

lib/code-excerpt.js

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
'use strict';
22

3-
var fs = require('fs');
4-
var codeExcerpt = require('code-excerpt');
5-
var repeating = require('repeating');
6-
var chalk = require('chalk');
3+
const fs = require('fs');
4+
const codeExcerpt = require('code-excerpt');
5+
const repeating = require('repeating');
6+
const truncate = require('cli-truncate');
7+
const chalk = require('chalk');
78

89
function formatLineNumber(line, maxLines) {
9-
return repeating('0', String(maxLines).length - String(line).length) + line;
10+
return repeating(' ', String(maxLines).length - String(line).length) + line;
1011
}
1112

12-
module.exports = function (file, line) {
13-
var source = fs.readFileSync(file, 'utf8');
14-
var excerpt = codeExcerpt(source, line, {around: 1});
13+
module.exports = (file, line, options) => {
14+
const maxWidth = (options || {}).maxWidth || 80;
15+
const source = fs.readFileSync(file, 'utf8');
16+
const excerpt = codeExcerpt(source, line, {around: 1});
1517

16-
return excerpt.map(function (item) {
17-
var lineNumber = formatLineNumber(item.line, line) + ': ';
18-
var coloredLineNumber = item.line === line ? lineNumber : chalk.grey(lineNumber);
18+
return excerpt
19+
.map(item => {
20+
const lineNumber = formatLineNumber(item.line, line) + ': ';
21+
const coloredLineNumber = item.line === line ? lineNumber : chalk.grey(lineNumber);
1922

20-
var result = ' ' + coloredLineNumber + item.value;
21-
return item.line === line ? chalk.bgRed(result) : result;
22-
})
23-
.map(function (line) {
24-
return ' ' + line;
25-
})
26-
.join('\n');
23+
const result = truncate(' ' + coloredLineNumber + item.value, maxWidth - 2);
24+
return item.line === line ? chalk.bgRed(result) : result;
25+
})
26+
.map(line => ` ${line}`)
27+
.join('\n');
2728
};

lib/colors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const chalk = require('chalk');
33

44
module.exports = {
5-
title: chalk.white,
5+
title: chalk.bold.white,
66
error: chalk.red,
77
skip: chalk.yellow,
88
todo: chalk.blue,

lib/enhance-assert.js

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const dotProp = require('dot-prop');
4+
35
module.exports = enhanceAssert;
46
module.exports.formatter = formatter;
57

@@ -8,17 +10,9 @@ module.exports.PATTERNS = [
810
't.falsy(value, [message])',
911
't.true(value, [message])',
1012
't.false(value, [message])',
11-
't.is(value, expected, [message])',
12-
't.not(value, expected, [message])',
13-
't.deepEqual(value, expected, [message])',
14-
't.notDeepEqual(value, expected, [message])',
15-
't.regex(contents, regex, [message])',
16-
't.notRegex(contents, regex, [message])',
1713
// Deprecated apis
1814
't.ok(value, [message])',
19-
't.notOk(value, [message])',
20-
't.same(value, expected, [message])',
21-
't.notSame(value, expected, [message])'
15+
't.notOk(value, [message])'
2216
];
2317

2418
module.exports.NON_ENHANCED_PATTERNS = [
@@ -27,7 +21,15 @@ module.exports.NON_ENHANCED_PATTERNS = [
2721
't.throws(fn, [message])',
2822
't.notThrows(fn, [message])',
2923
't.ifError(error, [message])',
30-
't.snapshot(contents, [message])'
24+
't.snapshot(contents, [message])',
25+
't.is(value, expected, [message])',
26+
't.not(value, expected, [message])',
27+
't.deepEqual(value, expected, [message])',
28+
't.notDeepEqual(value, expected, [message])',
29+
't.regex(contents, regex, [message])',
30+
't.notRegex(contents, regex, [message])',
31+
't.same(value, expected, [message])',
32+
't.notSame(value, expected, [message])'
3133
];
3234

3335
function enhanceAssert(opts) {
@@ -48,22 +50,38 @@ function enhanceAssert(opts) {
4850
return enhanced;
4951
}
5052

53+
function isRangeMatch(a, b) {
54+
return (a[0] === b[0] && a[1] === b[1]) ||
55+
(a[0] > b[0] && a[0] < b[1]) ||
56+
(a[1] > b[0] && a[1] < b[1]);
57+
}
58+
59+
function computeStatement(tokens, range) {
60+
return tokens
61+
.filter(token => isRangeMatch(token.range, range))
62+
.map(token => token.value === undefined ? token.type.label : token.value)
63+
.join('');
64+
}
65+
66+
function getNode(ast, path) {
67+
return dotProp.get(ast, path.replace(/\//g, '.'));
68+
}
69+
5170
function formatter() {
52-
const createFormatter = require('power-assert-context-formatter');
53-
const SuccinctRenderer = require('power-assert-renderer-succinct');
54-
const AssertionRenderer = require('power-assert-renderer-assertion');
71+
return context => {
72+
var ast = JSON.parse(context.source.ast);
73+
var tokens = JSON.parse(context.source.tokens);
74+
var args = context.args[0].events;
75+
76+
return args
77+
.map(arg => {
78+
var range = getNode(ast, arg.espath).range;
5579

56-
return createFormatter({
57-
renderers: [
58-
{
59-
ctor: AssertionRenderer
60-
},
61-
{
62-
ctor: SuccinctRenderer,
63-
options: {
64-
maxDepth: 3
65-
}
66-
}
67-
]
68-
});
80+
return [
81+
computeStatement(tokens, range),
82+
arg.value
83+
];
84+
})
85+
.reverse();
86+
};
6987
}

lib/format-assert-error.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict';
2+
3+
const indentString = require('indent-string');
4+
const chalk = require('chalk');
5+
const diff = require('diff');
6+
7+
function cleanUp(line) {
8+
if (line[0] === '+') {
9+
return `${chalk.green('+')} ${line.slice(1)}`;
10+
}
11+
12+
if (line[0] === '-') {
13+
return `${chalk.red('-')} ${line.slice(1)}`;
14+
}
15+
16+
if (line.match(/@@/)) {
17+
return null;
18+
}
19+
20+
if (line.match(/\\ No newline/)) {
21+
return null;
22+
}
23+
24+
return ` ${line}`;
25+
}
26+
27+
module.exports = err => {
28+
if (err.statements) {
29+
const statements = JSON.parse(err.statements);
30+
31+
return statements
32+
.map(statement => `${statement[0]}\n${chalk.grey('=>')} ${statement[1]}`)
33+
.join('\n\n') + '\n';
34+
}
35+
36+
if ((err.actualType === 'object' || err.actualType === 'array') && err.actualType === err.expectedType) {
37+
const patch = diff.createPatch('string', err.actual, err.expected);
38+
const msg = patch
39+
.split('\n')
40+
.splice(4)
41+
.map(cleanUp)
42+
.filter(Boolean)
43+
.join('\n');
44+
45+
return `Difference:\n\n${msg}`;
46+
}
47+
48+
if (err.actualType === 'string' && err.expectedType === 'string') {
49+
const patch = diff.diffChars(err.actual, err.expected);
50+
const msg = patch
51+
.map(part => {
52+
if (part.added) {
53+
return chalk.black.bgGreen(part.value);
54+
}
55+
56+
if (part.removed) {
57+
return chalk.black.bgRed(part.value);
58+
}
59+
60+
return part.value;
61+
})
62+
.join('');
63+
64+
return `Difference:\n\n${msg}\n`;
65+
}
66+
67+
return [
68+
'Actual:\n',
69+
`${indentString(err.actual, 2)}\n`,
70+
'Expected:\n',
71+
`${indentString(err.expected, 2)}\n`
72+
].join('\n');
73+
};

lib/reporters/mini.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22
var StringDecoder = require('string_decoder').StringDecoder;
3+
var path = require('path');
34
var cliCursor = require('cli-cursor');
45
var lastLineTracker = require('last-line-stream/tracker');
56
var plur = require('plur');
@@ -8,7 +9,9 @@ var chalk = require('chalk');
89
var cliTruncate = require('cli-truncate');
910
var cross = require('figures').cross;
1011
var repeating = require('repeating');
12+
var indentString = require('indent-string');
1113
var objectAssign = require('object-assign');
14+
var formatAssertError = require('../format-assert-error');
1215
var codeExcerpt = require('../code-excerpt');
1316
var colors = require('../colors');
1417

@@ -171,27 +174,32 @@ MiniReporter.prototype.finish = function (runStatus) {
171174
}
172175

173176
if (this.failCount > 0) {
174-
runStatus.errors.forEach(function (test) {
175-
if (!test.error || !test.error.message) {
177+
runStatus.errors.forEach(function (test, index) {
178+
if (!test.error) {
176179
return;
177180
}
178181

179182
var title = test.error ? test.title : 'Unhandled Error';
180183
var description;
181-
var errorTitle = ' ' + test.error.message + '\n';
182-
var isPowerAssert = test.error.message.split('\n').length > 1;
183184

184185
if (test.error) {
185-
description = stripFirstLine(test.error.stack).trimRight();
186+
description = stripFirstLine(test.error.stack
187+
.split('\n')
188+
.map(str => str.trim())
189+
.join('\n'));
186190
} else {
187191
description = JSON.stringify(test);
188192
}
189193

190-
status += '\n\n ' + colors.title(title) + '\n';
191-
status += colors.stack(errorTitle) + '\n';
192-
status += codeExcerpt(test.error.source.file, test.error.source.line) + '\n\n';
193-
status += colors.errorStack(description);
194-
});
194+
var beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n';
195+
var errorPath = path.relative(this.options.basePath, test.error.source.file) + ':' + test.error.source.line;
196+
197+
status += beforeSpacing + ' ' + colors.title(title) + '\n';
198+
status += ' ' + colors.errorStack(errorPath) + '\n\n';
199+
status += codeExcerpt(test.error.source.file, test.error.source.line, {maxWidth: process.stdout.columns}) + '\n\n';
200+
status += indentString(formatAssertError(test.error), 2) + '\n';
201+
status += indentString(colors.errorStack(description), 2);
202+
}, this);
195203
}
196204

197205
if (this.rejectionCount > 0 || this.exceptionCount > 0) {

0 commit comments

Comments
 (0)