Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Magic assert #1154

Merged
merged 39 commits into from
Feb 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e60f1cd
magic assert
Nov 15, 2016
e9bcbbd
fix stack paths with beginning spaces
Jan 16, 2017
27f40e5
rename t.jsxNotEqual to t.notJsxEqual
Jan 16, 2017
6d89985
make snapshots compatible with magic assert output
Jan 16, 2017
29f3865
fix t.snapshot stackStartFunction
Jan 16, 2017
b4e9254
fix stack parsing when serializing error
Jan 16, 2017
a9c1d9e
add minor comments
Jan 17, 2017
406daef
fix stackStartFunction for t.notJsxEqual
Jan 17, 2017
2187982
remove auto-generated diff from t.snapshot error message
Jan 17, 2017
1a6e33e
add extractStack() to correctly extract actual stack from err.stack
Jan 17, 2017
7b2dd8b
display error message
Jan 17, 2017
fecc9de
fix full-width line test in verbose reporter
Jan 17, 2017
0214d66
make error stack grey in verbose reporter
Jan 17, 2017
018802e
unify output
Jan 17, 2017
dbdf8b5
remove moot test
Jan 17, 2017
7804a8d
check if snapshot exists before trying to parse it
Jan 17, 2017
13279e9
update jsx support
Jan 19, 2017
8b7b3d2
show error stack if present
Jan 19, 2017
26d5db2
add "threw non-error" note to uncaught errors
Jan 19, 2017
7062599
remove empty space if error message is empty
Jan 19, 2017
ddeaf8c
fix path parsing for code excerpts
Jan 19, 2017
6320ddc
dont check for explicit stack path in serialize-error tests
Jan 19, 2017
1b8fcfe
cleanup
Jan 19, 2017
4379e3a
change splice to slice
Jan 20, 2017
7a53696
remove notes
Jan 29, 2017
e06d8f3
remove t.jsxEqual
Jan 29, 2017
96caf61
fix power-assert patterns
Jan 30, 2017
be3c42d
use babel-preset-transform-test-files fork
Jan 30, 2017
9a27d34
handle jsx differently in t.snapshot
Jan 30, 2017
2fa8da9
explain jest-snapshot double json encoding
Jan 30, 2017
9e9642f
adjust code excerpt line trimming
Jan 30, 2017
2508272
remove react element rendering
Jan 30, 2017
efe21ac
use babel-preset-transform-test-files@2
Jan 30, 2017
62a25a7
use @ava/pretty-format
Jan 30, 2017
286d620
cleanup
Jan 30, 2017
cf6ee8e
remove previous pretty-format
Jan 31, 2017
b139bc6
change custom jsx key in snapshots
Jan 31, 2017
9ea5af4
disable assertion output for t.throws
Jan 31, 2017
443888f
skip code excerpt when its broken
Feb 1, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 26 additions & 12 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,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', 'throws', 'notThrows'].indexOf(err.operator) === -1;
throw err;
}
}

Expand Down Expand Up @@ -109,7 +111,7 @@ x.throws = (fn, err, msg) => {

return result;
} catch (err) {
test(false, create(err.actual, err.expected, err.operator, err.message, x.throws));
test(false, create(err.actual, err.expected, 'throws', err.message, x.throws));
}
};

Expand All @@ -134,7 +136,7 @@ x.notThrows = (fn, msg) => {
try {
assert.doesNotThrow(fn, msg);
} catch (err) {
test(false, create(err.actual, err.expected, err.operator, err.message, x.notThrows));
test(false, create(err.actual, err.expected, 'notThrows', err.message, x.notThrows));
}
};

Expand Down Expand Up @@ -163,21 +165,33 @@ x._snapshot = function (tree, optionalMessage, match, snapshotStateGetter) {
snapshotState: state
};

const result = toMatchSnapshot.call(context, tree);
// symbols can't be serialized and saved in a snapshot,
// that's why tree is saved in `jsx` prop, so that JSX can be detected later
const serializedTree = tree.$$typeof === Symbol.for('react.test.json') ? {__ava_react_jsx: tree} : tree; // eslint-disable-line camelcase
const result = toMatchSnapshot.call(context, JSON.stringify(serializedTree));

let message = 'Please check your code or --update-snapshots\n\n';
let message = 'Please check your code or --update-snapshots';

if (optionalMessage) {
message += indentString(optionalMessage, 2);
}

if (typeof result.message === 'function') {
message += indentString(result.message(), 2);
message += '\n\n' + indentString(optionalMessage, 2);
}

state.save();

test(result.pass, create(result, false, 'snapshot', message, x.snap));
let expected;

if (result.expected) {
// JSON in a snapshot is surrounded with `"`, because jest-snapshot
// serializes snapshot values too, so it ends up double JSON encoded
expected = JSON.parse(result.expected.slice(1).slice(0, -1));
// Define a `$$typeof` symbol, so that pretty-format detects it as React tree
if (expected.__ava_react_jsx) { // eslint-disable-line camelcase
expected = expected.__ava_react_jsx; // eslint-disable-line camelcase
Object.defineProperty(expected, '$$typeof', {value: Symbol.for('react.test.json')});
}
}

test(result.pass, create(tree, expected, 'snapshot', message, x.snapshot));
};

x.snapshot = function (tree, optionalMessage) {
Expand Down
4 changes: 2 additions & 2 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ exports.run = () => {
if (cli.flags.tap && !cli.flags.watch) {
reporter = tapReporter();
} else if (cli.flags.verbose || isCi) {
reporter = verboseReporter();
reporter = verboseReporter({basePath: pkgDir});
} else {
reporter = miniReporter({watching: cli.flags.watch});
reporter = miniReporter({watching: cli.flags.watch, basePath: pkgDir});
}

reporter.api = api;
Expand Down
45 changes: 45 additions & 0 deletions lib/code-excerpt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'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');

const formatLineNumber = (lineNumber, maxLineNumber) => {
return ' '.repeat(String(maxLineNumber).length - String(lineNumber).length) + lineNumber;
};

module.exports = (file, line, options) => {
options = options || {};

const maxWidth = options.maxWidth || 80;
const source = fs.readFileSync(file, 'utf8');
const excerpt = codeExcerpt(source, line, {around: 1});
if (!excerpt) {
return null;
}

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

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;
})
.join('\n');
};
3 changes: 2 additions & 1 deletion lib/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
const chalk = require('chalk');

module.exports = {
title: chalk.white,
title: chalk.bold.white,
error: chalk.red,
skip: chalk.yellow,
todo: chalk.blue,
pass: chalk.green,
duration: chalk.gray.dim,
errorSource: chalk.gray,
errorStack: chalk.gray,
stack: chalk.red,
information: chalk.magenta
Expand Down
92 changes: 52 additions & 40 deletions lib/enhance-assert.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,79 @@
'use strict';

module.exports = enhanceAssert;
module.exports.formatter = formatter;
const dotProp = require('dot-prop');

// When adding patterns, don't forget to add to
// https://github.com/avajs/babel-preset-transform-test-files/blob/master/espower-patterns.json
// Then release a new version of that preset and bump the SemVer range here.
module.exports.PATTERNS = [
const PATTERNS = [
't.truthy(value, [message])',
'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])'
];

module.exports.NON_ENHANCED_PATTERNS = [
const NON_ENHANCED_PATTERNS = [
't.pass([message])',
't.fail([message])',
'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])'
];

function enhanceAssert(opts) {
const enhanceAssert = opts => {
const empower = require('empower-core');

const enhanced = empower(
opts.assert,
{
destructive: false,
onError: opts.onError,
onSuccess: opts.onSuccess,
patterns: module.exports.PATTERNS,
wrapOnlyPatterns: module.exports.NON_ENHANCED_PATTERNS,
bindReceiver: false
}
);
const enhanced = empower(opts.assert, {
destructive: false,
onError: opts.onError,
onSuccess: opts.onSuccess,
patterns: PATTERNS,
wrapOnlyPatterns: NON_ENHANCED_PATTERNS,
bindReceiver: false
});

return enhanced;
}
};

function formatter() {
const createFormatter = require('power-assert-context-formatter');
const SuccinctRenderer = require('power-assert-renderer-succinct');
const AssertionRenderer = require('power-assert-renderer-assertion');
const 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]);
};

return createFormatter({
renderers: [
{
ctor: AssertionRenderer
},
{
ctor: SuccinctRenderer,
options: {
maxDepth: 3
}
}
]
});
}
const computeStatement = (tokens, range) => {
return tokens
.filter(token => isRangeMatch(token.range, range))
.map(token => token.value === undefined ? token.type.label : token.value)
.join('');
};

const getNode = (ast, path) => dotProp.get(ast, path.replace(/\//g, '.'));

const formatter = () => {
return context => {
const ast = JSON.parse(context.source.ast);
const tokens = JSON.parse(context.source.tokens);
const args = context.args[0].events;

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

return [computeStatement(tokens, range), arg.value];
})
.reverse();
};
};

module.exports = enhanceAssert;
module.exports.PATTERNS = PATTERNS;
module.exports.NON_ENHANCED_PATTERNS = NON_ENHANCED_PATTERNS;
module.exports.formatter = formatter;
10 changes: 10 additions & 0 deletions lib/extract-stack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';
const stackLineRegex = /^.+ \(.+:[0-9]+:[0-9]+\)$/;

module.exports = stack => {
return stack
.split('\n')
.filter(line => stackLineRegex.test(line))
.map(line => line.trim())
.join('\n');
};
72 changes: 72 additions & 0 deletions lib/format-assert-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';
const indentString = require('indent-string');
const chalk = require('chalk');
const diff = require('diff');

const 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')
.slice(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.bgGreen.black(part.value);
}

if (part.removed) {
return chalk.bgRed.black(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');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use a single template string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried that, but didn't work out, because the resulting string contains code's indentation:

    Actual:
    ...
    Expected:
    ...

Am I doing smth wrong here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's a common issue when using template strings like that. You either need to just remove the indent, which looks weird, or use something like https://github.com/sindresorhus/redent In this case, I don't think it's worth it.

};
Loading