Skip to content

Commit

Permalink
magic assert
Browse files Browse the repository at this point in the history
  • Loading branch information
vdemedes committed Jan 5, 2017
1 parent 314f7a0 commit 8a4c3ca
Show file tree
Hide file tree
Showing 13 changed files with 410 additions and 66 deletions.
17 changes: 15 additions & 2 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const indentString = require('indent-string');
const isObservable = require('is-observable');
const isPromise = require('is-promise');
const jestSnapshot = require('jest-snapshot');
const reactElementToJSXString = require('react-element-to-jsx-string').default;
const snapshotState = require('./snapshot-state');

const x = module.exports;
Expand All @@ -18,15 +19,17 @@ function create(val, expected, operator, msg, fn) {
return {
actual: val,
expected,
message: msg,
message: msg || ' ',
operator,
stackStartFunction: fn
};
}

function test(ok, opts) {
if (!ok) {
throw new assert.AssertionError(opts);
const err = new assert.AssertionError(opts);
err.showOutput = ['fail'].indexOf(err.operator) === -1;
throw err;
}
}

Expand Down Expand Up @@ -151,6 +154,16 @@ x.ifError = (err, msg) => {
test(!err, create(err, 'Error', '!==', msg, x.ifError));
};

x.jsxEqual = (val, expected, msg) => {
const treeEquals = reactElementToJSXString(val) === reactElementToJSXString(expected);
test(treeEquals, create(val, expected, '===', msg, x.jsxEqual));
};

x.jsxNotEqual = (val, expected, msg) => {
const treeEquals = reactElementToJSXString(val) === reactElementToJSXString(expected);
test(!treeEquals, create(val, expected, '!==', msg, x.jsxEqual));
};

x._snapshot = function (tree, optionalMessage, match, snapshotStateGetter) {
// Set defaults - this allows tests to mock deps easily
const toMatchSnapshot = match || jestSnapshot.toMatchSnapshot;
Expand Down
8 changes: 5 additions & 3 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ exports.run = function () {
throw new Error(colors.error(figures.cross) + ' The --require and -r flags are deprecated. Requirements should be configured in package.json - see documentation.');
}

var resolveTestsFrom = cli.input.length === 0 ? pkgDir : process.cwd();

var api = new Api({
failFast: cli.flags.failFast,
serial: cli.flags.serial,
Expand All @@ -112,7 +114,7 @@ exports.run = function () {
explicitTitles: cli.flags.watch,
match: arrify(cli.flags.match),
babelConfig: babelConfig.validate(conf.babel),
resolveTestsFrom: cli.input.length === 0 ? pkgDir : process.cwd(),
resolveTestsFrom,
pkgDir: pkgDir,
timeout: cli.flags.timeout,
concurrency: cli.flags.concurrency ? parseInt(cli.flags.concurrency, 10) : 0,
Expand All @@ -124,9 +126,9 @@ exports.run = function () {
if (cli.flags.tap && !cli.flags.watch) {
reporter = tapReporter();
} else if (cli.flags.verbose || isCi) {
reporter = verboseReporter();
reporter = verboseReporter({basePath: resolveTestsFrom});
} else {
reporter = miniReporter({watching: cli.flags.watch});
reporter = miniReporter({watching: cli.flags.watch, basePath: resolveTestsFrom});
}

reporter.api = api;
Expand Down
43 changes: 43 additions & 0 deletions lib/code-excerpt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

const fs = require('fs');
const equalLength = require('equal-length');
const codeExcerpt = require('code-excerpt');
const truncate = require('cli-truncate');
const chalk = require('chalk');

function formatLineNumber(line, maxLines) {
return ' '.repeat(String(maxLines).length - String(line).length) + line;
}

module.exports = (file, line, options) => {
const maxWidth = (options || {}).maxWidth || 80;
const indent = (options || {}).indent || 0;
const source = fs.readFileSync(file, 'utf8');
const excerpt = codeExcerpt(source, line, {around: 1});

const lines = excerpt.map(item => ({
line: item.line,
value: truncate(item.value, maxWidth - String(line).length - 1)
}));

const joinedLines = lines.map(line => line.value).join('\n');
const extendedLines = equalLength(joinedLines).split('\n');

return lines
.map((item, index) => ({
line: item.line,
value: extendedLines[index]
}))
.map(item => {
const isErrorSource = item.line === line;

const lineNumber = formatLineNumber(item.line, line) + ':';
const coloredLineNumber = isErrorSource ? lineNumber : chalk.grey(lineNumber);
const result = ` ${coloredLineNumber} ${item.value}`;

return isErrorSource ? chalk.bgRed(result) : result;
})
.map(line => ' '.repeat(indent) + line)
.join('\n');
};
2 changes: 1 addition & 1 deletion lib/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const chalk = require('chalk');

module.exports = {
title: chalk.white,
title: chalk.bold.white,
error: chalk.red,
skip: chalk.yellow,
todo: chalk.blue,
Expand Down
70 changes: 44 additions & 26 deletions lib/enhance-assert.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const dotProp = require('dot-prop');

module.exports = enhanceAssert;
module.exports.formatter = formatter;

Expand All @@ -8,17 +10,9 @@ module.exports.PATTERNS = [
't.falsy(value, [message])',
't.true(value, [message])',
't.false(value, [message])',
't.is(value, expected, [message])',
't.not(value, expected, [message])',
't.deepEqual(value, expected, [message])',
't.notDeepEqual(value, expected, [message])',
't.regex(contents, regex, [message])',
't.notRegex(contents, regex, [message])',
// Deprecated apis
't.ok(value, [message])',
't.notOk(value, [message])',
't.same(value, expected, [message])',
't.notSame(value, expected, [message])'
't.notOk(value, [message])'
];

module.exports.NON_ENHANCED_PATTERNS = [
Expand All @@ -27,7 +21,15 @@ module.exports.NON_ENHANCED_PATTERNS = [
't.throws(fn, [message])',
't.notThrows(fn, [message])',
't.ifError(error, [message])',
't.snapshot(contents, [message])'
't.snapshot(contents, [message])',
't.is(value, expected, [message])',
't.not(value, expected, [message])',
't.deepEqual(value, expected, [message])',
't.notDeepEqual(value, expected, [message])',
't.regex(contents, regex, [message])',
't.notRegex(contents, regex, [message])',
't.same(value, expected, [message])',
't.notSame(value, expected, [message])'
];

function enhanceAssert(opts) {
Expand All @@ -48,22 +50,38 @@ function enhanceAssert(opts) {
return enhanced;
}

function isRangeMatch(a, b) {
return (a[0] === b[0] && a[1] === b[1]) ||
(a[0] > b[0] && a[0] < b[1]) ||
(a[1] > b[0] && a[1] < b[1]);
}

function computeStatement(tokens, range) {
return tokens
.filter(token => isRangeMatch(token.range, range))
.map(token => token.value === undefined ? token.type.label : token.value)
.join('');
}

function getNode(ast, path) {
return dotProp.get(ast, path.replace(/\//g, '.'));
}

function formatter() {
const createFormatter = require('power-assert-context-formatter');
const SuccinctRenderer = require('power-assert-renderer-succinct');
const AssertionRenderer = require('power-assert-renderer-assertion');
return context => {
var ast = JSON.parse(context.source.ast);
var tokens = JSON.parse(context.source.tokens);
var args = context.args[0].events;

return args
.map(arg => {
var range = getNode(ast, arg.espath).range;

return createFormatter({
renderers: [
{
ctor: AssertionRenderer
},
{
ctor: SuccinctRenderer,
options: {
maxDepth: 3
}
}
]
});
return [
computeStatement(tokens, range),
arg.value
];
})
.reverse();
};
}
73 changes: 73 additions & 0 deletions lib/format-assert-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict';

const indentString = require('indent-string');
const chalk = require('chalk');
const diff = require('diff');

function cleanUp(line) {
if (line[0] === '+') {
return `${chalk.green('+')} ${line.slice(1)}`;
}

if (line[0] === '-') {
return `${chalk.red('-')} ${line.slice(1)}`;
}

if (line.match(/@@/)) {
return null;
}

if (line.match(/\\ No newline/)) {
return null;
}

return ` ${line}`;
}

module.exports = err => {
if (err.statements) {
const statements = JSON.parse(err.statements);

return statements
.map(statement => `${statement[0]}\n${chalk.grey('=>')} ${statement[1]}`)
.join('\n\n') + '\n';
}

if ((err.actualType === 'object' || err.actualType === 'array') && err.actualType === err.expectedType) {
const patch = diff.createPatch('string', err.actual, err.expected);
const msg = patch
.split('\n')
.splice(4)
.map(cleanUp)
.filter(Boolean)
.join('\n');

return `Difference:\n\n${msg}`;
}

if (err.actualType === 'string' && err.expectedType === 'string') {
const patch = diff.diffChars(err.actual, err.expected);
const msg = patch
.map(part => {
if (part.added) {
return chalk.black.bgGreen(part.value);
}

if (part.removed) {
return chalk.black.bgRed(part.value);
}

return part.value;
})
.join('');

return `Difference:\n\n${msg}\n`;
}

return [
'Actual:\n',
`${indentString(err.actual, 2)}\n`,
'Expected:\n',
`${indentString(err.expected, 2)}\n`
].join('\n');
};
37 changes: 21 additions & 16 deletions lib/reporters/mini.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
var StringDecoder = require('string_decoder').StringDecoder;
var path = require('path');
var cliCursor = require('cli-cursor');
var lastLineTracker = require('last-line-stream/tracker');
var plur = require('plur');
Expand All @@ -8,7 +9,10 @@ var chalk = require('chalk');
var cliTruncate = require('cli-truncate');
var cross = require('figures').cross;
var repeating = require('repeating');
var indentString = require('indent-string');
var objectAssign = require('object-assign');
var formatAssertError = require('../format-assert-error');
var codeExcerpt = require('../code-excerpt');
var colors = require('../colors');

chalk.enabled = true;
Expand Down Expand Up @@ -170,28 +174,29 @@ MiniReporter.prototype.finish = function (runStatus) {
}

if (this.failCount > 0) {
runStatus.errors.forEach(function (test) {
if (!test.error || !test.error.message) {
runStatus.errors.forEach(function (test, index) {
if (!test.error) {
return;
}

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

description = stripFirstLine(test.error.stack).trimRight();

if (isPowerAssert) {
description = stripFirstLine(description).replace(/ {3}/g, ' ');
} else {
description.replace(/ {3}/g, ' ');
var description = stripFirstLine(test.error.stack
.split('\n')
.map(str => str.trim())
.join('\n'));

var beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n';
var errorPath = path.relative(this.options.basePath, test.error.source.file) + ':' + test.error.source.line;

status += beforeSpacing + ' ' + colors.title(title) + '\n';
status += ' ' + colors.errorStack(errorPath) + '\n\n';
status += codeExcerpt(test.error.source.file, test.error.source.line, {maxWidth: process.stdout.columns, indent: 2}) + '\n\n';
if (test.error.showOutput) {
status += indentString(formatAssertError(test.error), 2) + '\n';
}

status += '\n\n ' + colors.title(title) + '\n';
status += colors.stack(errorTitle);
status += colors.errorStack(description);
});
status += indentString(colors.errorStack(description), 2);
}, this);
}

if (this.rejectionCount > 0 || this.exceptionCount > 0) {
Expand Down
Loading

0 comments on commit 8a4c3ca

Please sign in to comment.