Skip to content

Bound execution context and changed skip #1696

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

Merged
merged 3 commits into from
Feb 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 1 addition & 14 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ function wrapAssertions(callbacks) {
const pass = callbacks.pass;
const pending = callbacks.pending;
const fail = callbacks.fail;
const log = callbacks.log;

const noop = () => {};
const makeRethrow = reason => () => {
Expand Down Expand Up @@ -130,18 +129,6 @@ function wrapAssertions(callbacks) {
}
},

log() {
const args = Array.from(arguments, value => {
return typeof value === 'string' ?
value :
concordance.format(value, concordanceOptions);
});

if (args.length > 0) {
log(this, args.join(' '));
}
},

deepEqual(actual, expected, message) {
const result = concordance.compare(actual, expected, concordanceOptions);
if (result.pass) {
Expand Down Expand Up @@ -347,7 +334,7 @@ function wrapAssertions(callbacks) {

let result;
try {
result = this._test.compareWithSnapshot(options);
result = this.compareWithSnapshot(options);
} catch (err) {
if (!(err instanceof snapshotManager.SnapshotError)) {
throw err;
Expand Down
2 changes: 2 additions & 0 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ class Runner extends EventEmitter {
task.implementation :
t => task.implementation.apply(null, [t].concat(task.args)),
compareTestSnapshot: this.boundCompareTestSnapshot,
updateSnapshots: this.updateSnapshots,
metadata: task.metadata,
title: `${task.title}${titleSuffix || ''}`
}));
Expand Down Expand Up @@ -300,6 +301,7 @@ class Runner extends EventEmitter {
task.implementation :
t => task.implementation.apply(null, [t].concat(task.args)),
compareTestSnapshot: this.boundCompareTestSnapshot,
updateSnapshots: this.updateSnapshots,
metadata: task.metadata,
title: task.title
});
Expand Down
112 changes: 65 additions & 47 deletions lib/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ function formatErrorValue(label, error) {
return {label, formatted};
}

class SkipApi {
constructor(test) {
this._test = test;
}
}

const captureStack = start => {
const limitBefore = Error.stackTraceLimit;
Error.stackTraceLimit = 1;
Expand All @@ -30,71 +24,87 @@ const captureStack = start => {
return obj.stack;
};

const assertions = assert.wrapAssertions({
pass(test) {
test.countPassedAssertion();
},

pending(test, promise) {
test.addPendingAssertion(promise);
},

fail(test, error) {
test.addFailedAssertion(error);
}
});
const assertionNames = Object.keys(assertions);

function log() {
const args = Array.from(arguments, value => {
return typeof value === 'string' ?
value :
concordance.format(value, concordanceOptions);
});

if (args.length > 0) {
this.addLog(args.join(' '));
}
}

function plan(count) {
this.plan(count, captureStack(this.plan));
}

const testMap = new WeakMap();
class ExecutionContext {
constructor(test) {
Object.defineProperties(this, {
_test: {value: test},
skip: {value: new SkipApi(test)}
});
}
testMap.set(this, test);

plan(ct) {
this._test.plan(ct, captureStack(this.plan));
const skip = () => {
test.countPassedAssertion();
};
const boundPlan = plan.bind(test);
boundPlan.skip = () => {};

Object.defineProperties(this, assertionNames.reduce((props, name) => {
props[name] = {value: assertions[name].bind(test)};
props[name].value.skip = skip;
return props;
}, {
log: {value: log.bind(test)},
plan: {value: boundPlan}
}));

this.snapshot.skip = () => {
test.skipSnapshot();
};
}

get end() {
const end = this._test.bindEndCallback();
const end = testMap.get(this).bindEndCallback();
const endFn = err => end(err, captureStack(endFn));
return endFn;
}

get title() {
return this._test.title;
return testMap.get(this).title;
}

get context() {
return this._test.contextRef.get();
return testMap.get(this).contextRef.get();
}

set context(context) {
this._test.contextRef.set(context);
testMap.get(this).contextRef.set(context);
}

_throwsArgStart(assertion, file, line) {
this._test.trackThrows({assertion, file, line});
testMap.get(this).trackThrows({assertion, file, line});
}

_throwsArgEnd() {
this._test.trackThrows(null);
}
}

{
const assertions = assert.wrapAssertions({
log(executionContext, text) {
executionContext._test.addLog(text);
},

pass(executionContext) {
executionContext._test.countPassedAssertion();
},

pending(executionContext, promise) {
executionContext._test.addPendingAssertion(promise);
},

fail(executionContext, error) {
executionContext._test.addFailedAssertion(error);
}
});
Object.assign(ExecutionContext.prototype, assertions);

function skipFn() {
this._test.countPassedAssertion();
testMap.get(this).trackThrows(null);
}
Object.keys(assertions).forEach(el => {
SkipApi.prototype[el] = skipFn;
});
}

class Test {
Expand All @@ -114,6 +124,14 @@ class Test {
const label = assertionOptions.id ? '' : assertionOptions.message || `Snapshot ${this.snapshotInvocationCount}`;
return options.compareTestSnapshot({belongsTo, expected, index, label});
};
this.skipSnapshot = () => {
if (options.updateSnapshots) {
this.addFailedAssertion(new Error('Snapshot assertions cannot be skipped when updating snapshots'));
} else {
this.snapshotInvocationCount++;
this.countPassedAssertion();
}
};

this.assertCount = 0;
this.assertError = undefined;
Expand Down
21 changes: 20 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,23 @@ test('unicorns are truthy', t => {
});
```

Assertions are bound to their test so you can assign them to a variable or pass them around:

```js
test('unicorns are truthy', t => {
const truthy = t.thruthy;
truthy('unicorn');
});
```

Assertions can be skipped by adding `.skip()`:

```js
test('unicorns are truthy', t => {
t.truthy.skip('unicorn');
});
```

If multiple assertion failures are encountered within a single test, AVA will only display the *first* one.

### `.pass([message])`
Expand Down Expand Up @@ -975,6 +992,8 @@ Assert that `error` is falsy.

Compares the `expected` value with a previously recorded snapshot. Snapshots are stored for each test, so ensure you give your tests unique titles. Alternatively pass an `options` object to select a specific snapshot, for instance `{id: 'my snapshot'}`.

Snapshot assertions cannot be skipped when snapshots are being updated.

## Snapshot testing

AVA supports snapshot testing, [as introduced by Jest](https://facebook.github.io/jest/docs/snapshot-testing.html), through its [Assertions](#assertions) interface. You can snapshot any value as well as React elements:
Expand Down Expand Up @@ -1040,7 +1059,7 @@ Any assertion can be skipped using the `skip` modifier. Skipped assertions are s
```js
test('skip assertion', t => {
t.plan(2);
t.skip.is(foo(), 5); // No need to change your plan count when skipping
t.is.skip(foo(), 5); // No need to change your plan count when skipping
t.is(1, 1);
});
```
Expand Down
67 changes: 37 additions & 30 deletions test/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,28 @@ const HelloMessage = require('./fixture/hello-message');
let lastFailure = null;
let lastPassed = false;
const assertions = assert.wrapAssertions({
pass() {
pass(testObj) {
if (testObj !== assertions && !(testObj instanceof Test)) {
throw new Error('Expected testObj');
}
lastPassed = true;
},

pending(_, promise) {
pending(testObj, promise) {
if (testObj !== assertions && !(testObj instanceof Test)) {
throw new Error('Expected testObj');
}

promise.catch(err => {
lastFailure = err;
});
},

fail(_, error) {
fail(testObj, error) {
if (testObj !== assertions && !(testObj instanceof Test)) {
throw new Error('Expected testObj');
}

lastFailure = error;
}
});
Expand Down Expand Up @@ -813,31 +824,27 @@ test('.snapshot()', t => {
updating
});
const setup = title => {
const fauxTest = new Test({
return new Test({
title,
compareTestSnapshot: options => manager.compare(options)
});
const executionContext = {
_test: fauxTest
};
return executionContext;
};

passes(t, () => {
const executionContext = setup('passes');
assertions.snapshot.call(executionContext, {foo: 'bar'});
assertions.snapshot.call(executionContext, {foo: 'bar'}, {id: 'fixed id'}, 'message not included in snapshot report');
assertions.snapshot.call(executionContext, React.createElement(HelloMessage, {name: 'Sindre'}));
assertions.snapshot.call(executionContext, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
const testInstance = setup('passes');
assertions.snapshot.call(testInstance, {foo: 'bar'});
assertions.snapshot.call(testInstance, {foo: 'bar'}, {id: 'fixed id'}, 'message not included in snapshot report');
assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Sindre'}));
assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
});

{
const executionContext = setup('fails');
const testInstance = setup('fails');
if (updating) {
assertions.snapshot.call(executionContext, {foo: 'bar'});
assertions.snapshot.call(testInstance, {foo: 'bar'});
} else {
failsWith(t, () => {
assertions.snapshot.call(executionContext, {foo: 'not bar'});
assertions.snapshot.call(testInstance, {foo: 'not bar'});
}, {
assertion: 'snapshot',
message: 'Did not match snapshot',
Expand All @@ -847,21 +854,21 @@ test('.snapshot()', t => {
}

failsWith(t, () => {
const executionContext = setup('fails (fixed id)');
assertions.snapshot.call(executionContext, {foo: 'not bar'}, {id: 'fixed id'}, 'different message, also not included in snapshot report');
const testInstance = setup('fails (fixed id)');
assertions.snapshot.call(testInstance, {foo: 'not bar'}, {id: 'fixed id'}, 'different message, also not included in snapshot report');
}, {
assertion: 'snapshot',
message: 'different message, also not included in snapshot report',
values: [{label: 'Difference:', formatted: ' {\n- foo: \'not bar\',\n+ foo: \'bar\',\n }'}]
});

{
const executionContext = setup('fails');
const testInstance = setup('fails');
if (updating) {
assertions.snapshot.call(executionContext, {foo: 'bar'}, 'my message');
assertions.snapshot.call(testInstance, {foo: 'bar'}, 'my message');
} else {
failsWith(t, () => {
assertions.snapshot.call(executionContext, {foo: 'not bar'}, 'my message');
assertions.snapshot.call(testInstance, {foo: 'not bar'}, 'my message');
}, {
assertion: 'snapshot',
message: 'my message',
Expand All @@ -871,23 +878,23 @@ test('.snapshot()', t => {
}

{
const executionContext = setup('rendered comparison');
const testInstance = setup('rendered comparison');
if (updating) {
assertions.snapshot.call(executionContext, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
} else {
passes(t, () => {
assertions.snapshot.call(executionContext, React.createElement('div', null, 'Hello ', React.createElement('mark', null, 'Sindre')));
assertions.snapshot.call(testInstance, React.createElement('div', null, 'Hello ', React.createElement('mark', null, 'Sindre')));
});
}
}

{
const executionContext = setup('rendered comparison');
const testInstance = setup('rendered comparison');
if (updating) {
assertions.snapshot.call(executionContext, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
} else {
failsWith(t, () => {
assertions.snapshot.call(executionContext, renderer.create(React.createElement(HelloMessage, {name: 'Vadim'})).toJSON());
assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Vadim'})).toJSON());
}, {
assertion: 'snapshot',
message: 'Did not match snapshot',
Expand All @@ -897,12 +904,12 @@ test('.snapshot()', t => {
}

{
const executionContext = setup('element comparison');
const testInstance = setup('element comparison');
if (updating) {
assertions.snapshot.call(executionContext, React.createElement(HelloMessage, {name: 'Sindre'}));
assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Sindre'}));
} else {
failsWith(t, () => {
assertions.snapshot.call(executionContext, React.createElement(HelloMessage, {name: 'Vadim'}));
assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Vadim'}));
}, {
assertion: 'snapshot',
message: 'Did not match snapshot',
Expand Down
Loading