Skip to content

Commit 20f4cea

Browse files
committed
Bind assertions, move skip()
Assertions are now bound. Fixes #1592. Assertions are now skipped by calling `.skip()` at the end, e.g. `t.fail.skip()`. This is consistent with the test interface, where `skip()` can only be used at the end of the chain. The private `t._test` value has been removed. Instead a WeakMap is used internally to look up the corresponding Test instance given an ExecutionContext.
1 parent 5377199 commit 20f4cea

File tree

5 files changed

+212
-92
lines changed

5 files changed

+212
-92
lines changed

lib/assert.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ function wrapAssertions(callbacks) {
334334

335335
let result;
336336
try {
337-
result = this._test.compareWithSnapshot(options);
337+
result = this.compareWithSnapshot(options);
338338
} catch (err) {
339339
if (!(err instanceof snapshotManager.SnapshotError)) {
340340
throw err;

lib/test.js

Lines changed: 52 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ function formatErrorValue(label, error) {
1515
return {label, formatted};
1616
}
1717

18-
class SkipApi {
19-
constructor(test) {
20-
this._test = test;
21-
}
22-
}
23-
2418
const captureStack = start => {
2519
const limitBefore = Error.stackTraceLimit;
2620
Error.stackTraceLimit = 1;
@@ -30,81 +24,85 @@ const captureStack = start => {
3024
return obj.stack;
3125
};
3226

33-
class ExecutionContext {
34-
constructor(test) {
35-
Object.defineProperties(this, {
36-
_test: {value: test},
37-
skip: {value: new SkipApi(test)}
38-
});
39-
}
27+
const assertions = assert.wrapAssertions({
28+
pass(test) {
29+
test.countPassedAssertion();
30+
},
4031

41-
log() {
42-
const args = Array.from(arguments, value => {
43-
return typeof value === 'string' ?
44-
value :
45-
concordance.format(value, concordanceOptions);
46-
});
32+
pending(test, promise) {
33+
test.addPendingAssertion(promise);
34+
},
4735

48-
if (args.length > 0) {
49-
this._test.addLog(args.join(' '));
50-
}
36+
fail(test, error) {
37+
test.addFailedAssertion(error);
5138
}
39+
});
40+
const assertionNames = Object.keys(assertions);
41+
42+
function log() {
43+
const args = Array.from(arguments, value => {
44+
return typeof value === 'string' ?
45+
value :
46+
concordance.format(value, concordanceOptions);
47+
});
48+
49+
if (args.length > 0) {
50+
this.addLog(args.join(' '));
51+
}
52+
}
53+
54+
function plan(count) {
55+
this.plan(count, captureStack(this.plan));
56+
}
57+
58+
const testMap = new WeakMap();
59+
class ExecutionContext {
60+
constructor(test) {
61+
testMap.set(this, test);
5262

53-
plan(ct) {
54-
this._test.plan(ct, captureStack(this.plan));
63+
const skip = () => {
64+
test.countPassedAssertion();
65+
};
66+
const boundPlan = plan.bind(test);
67+
boundPlan.skip = () => {};
68+
69+
Object.defineProperties(this, assertionNames.reduce((props, name) => {
70+
props[name] = {value: assertions[name].bind(test)};
71+
props[name].value.skip = skip;
72+
return props;
73+
}, {
74+
log: {value: log.bind(test)},
75+
plan: {value: boundPlan}
76+
}));
5577
}
5678

5779
get end() {
58-
const end = this._test.bindEndCallback();
80+
const end = testMap.get(this).bindEndCallback();
5981
const endFn = err => end(err, captureStack(endFn));
6082
return endFn;
6183
}
6284

6385
get title() {
64-
return this._test.title;
86+
return testMap.get(this).title;
6587
}
6688

6789
get context() {
68-
return this._test.contextRef.get();
90+
return testMap.get(this).contextRef.get();
6991
}
7092

7193
set context(context) {
72-
this._test.contextRef.set(context);
94+
testMap.get(this).contextRef.set(context);
7395
}
7496

7597
_throwsArgStart(assertion, file, line) {
76-
this._test.trackThrows({assertion, file, line});
98+
testMap.get(this).trackThrows({assertion, file, line});
7799
}
78100

79101
_throwsArgEnd() {
80-
this._test.trackThrows(null);
102+
testMap.get(this).trackThrows(null);
81103
}
82104
}
83105

84-
{
85-
const assertions = assert.wrapAssertions({
86-
pass(executionContext) {
87-
executionContext._test.countPassedAssertion();
88-
},
89-
90-
pending(executionContext, promise) {
91-
executionContext._test.addPendingAssertion(promise);
92-
},
93-
94-
fail(executionContext, error) {
95-
executionContext._test.addFailedAssertion(error);
96-
}
97-
});
98-
Object.assign(ExecutionContext.prototype, assertions);
99-
100-
function skipFn() {
101-
this._test.countPassedAssertion();
102-
}
103-
Object.keys(assertions).forEach(el => {
104-
SkipApi.prototype[el] = skipFn;
105-
});
106-
}
107-
108106
class Test {
109107
constructor(options) {
110108
this.contextRef = options.contextRef;

readme.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,23 @@ test('unicorns are truthy', t => {
851851
});
852852
```
853853

854+
Assertions are bound to their test so you can assign them to a variable or pass them around:
855+
856+
```js
857+
test('unicorns are truthy', t => {
858+
const truthy = t.thruthy;
859+
truthy('unicorn');
860+
});
861+
```
862+
863+
Assertions can be skipped by adding `.skip()`:
864+
865+
```js
866+
test('unicorns are truthy', t => {
867+
t.truthy.skip('unicorn');
868+
});
869+
```
870+
854871
If multiple assertion failures are encountered within a single test, AVA will only display the *first* one.
855872

856873
### `.pass([message])`
@@ -1040,7 +1057,7 @@ Any assertion can be skipped using the `skip` modifier. Skipped assertions are s
10401057
```js
10411058
test('skip assertion', t => {
10421059
t.plan(2);
1043-
t.skip.is(foo(), 5); // No need to change your plan count when skipping
1060+
t.is.skip(foo(), 5); // No need to change your plan count when skipping
10441061
t.is(1, 1);
10451062
});
10461063
```

test/assert.js

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,28 @@ const HelloMessage = require('./fixture/hello-message');
1515
let lastFailure = null;
1616
let lastPassed = false;
1717
const assertions = assert.wrapAssertions({
18-
pass() {
18+
pass(testObj) {
19+
if (testObj !== assertions && !(testObj instanceof Test)) {
20+
throw new Error('Expected testObj');
21+
}
1922
lastPassed = true;
2023
},
2124

22-
pending(_, promise) {
25+
pending(testObj, promise) {
26+
if (testObj !== assertions && !(testObj instanceof Test)) {
27+
throw new Error('Expected testObj');
28+
}
29+
2330
promise.catch(err => {
2431
lastFailure = err;
2532
});
2633
},
2734

28-
fail(_, error) {
35+
fail(testObj, error) {
36+
if (testObj !== assertions && !(testObj instanceof Test)) {
37+
throw new Error('Expected testObj');
38+
}
39+
2940
lastFailure = error;
3041
}
3142
});
@@ -813,31 +824,27 @@ test('.snapshot()', t => {
813824
updating
814825
});
815826
const setup = title => {
816-
const fauxTest = new Test({
827+
return new Test({
817828
title,
818829
compareTestSnapshot: options => manager.compare(options)
819830
});
820-
const executionContext = {
821-
_test: fauxTest
822-
};
823-
return executionContext;
824831
};
825832

826833
passes(t, () => {
827-
const executionContext = setup('passes');
828-
assertions.snapshot.call(executionContext, {foo: 'bar'});
829-
assertions.snapshot.call(executionContext, {foo: 'bar'}, {id: 'fixed id'}, 'message not included in snapshot report');
830-
assertions.snapshot.call(executionContext, React.createElement(HelloMessage, {name: 'Sindre'}));
831-
assertions.snapshot.call(executionContext, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
834+
const testInstance = setup('passes');
835+
assertions.snapshot.call(testInstance, {foo: 'bar'});
836+
assertions.snapshot.call(testInstance, {foo: 'bar'}, {id: 'fixed id'}, 'message not included in snapshot report');
837+
assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Sindre'}));
838+
assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
832839
});
833840

834841
{
835-
const executionContext = setup('fails');
842+
const testInstance = setup('fails');
836843
if (updating) {
837-
assertions.snapshot.call(executionContext, {foo: 'bar'});
844+
assertions.snapshot.call(testInstance, {foo: 'bar'});
838845
} else {
839846
failsWith(t, () => {
840-
assertions.snapshot.call(executionContext, {foo: 'not bar'});
847+
assertions.snapshot.call(testInstance, {foo: 'not bar'});
841848
}, {
842849
assertion: 'snapshot',
843850
message: 'Did not match snapshot',
@@ -847,21 +854,21 @@ test('.snapshot()', t => {
847854
}
848855

849856
failsWith(t, () => {
850-
const executionContext = setup('fails (fixed id)');
851-
assertions.snapshot.call(executionContext, {foo: 'not bar'}, {id: 'fixed id'}, 'different message, also not included in snapshot report');
857+
const testInstance = setup('fails (fixed id)');
858+
assertions.snapshot.call(testInstance, {foo: 'not bar'}, {id: 'fixed id'}, 'different message, also not included in snapshot report');
852859
}, {
853860
assertion: 'snapshot',
854861
message: 'different message, also not included in snapshot report',
855862
values: [{label: 'Difference:', formatted: ' {\n- foo: \'not bar\',\n+ foo: \'bar\',\n }'}]
856863
});
857864

858865
{
859-
const executionContext = setup('fails');
866+
const testInstance = setup('fails');
860867
if (updating) {
861-
assertions.snapshot.call(executionContext, {foo: 'bar'}, 'my message');
868+
assertions.snapshot.call(testInstance, {foo: 'bar'}, 'my message');
862869
} else {
863870
failsWith(t, () => {
864-
assertions.snapshot.call(executionContext, {foo: 'not bar'}, 'my message');
871+
assertions.snapshot.call(testInstance, {foo: 'not bar'}, 'my message');
865872
}, {
866873
assertion: 'snapshot',
867874
message: 'my message',
@@ -871,23 +878,23 @@ test('.snapshot()', t => {
871878
}
872879

873880
{
874-
const executionContext = setup('rendered comparison');
881+
const testInstance = setup('rendered comparison');
875882
if (updating) {
876-
assertions.snapshot.call(executionContext, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
883+
assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
877884
} else {
878885
passes(t, () => {
879-
assertions.snapshot.call(executionContext, React.createElement('div', null, 'Hello ', React.createElement('mark', null, 'Sindre')));
886+
assertions.snapshot.call(testInstance, React.createElement('div', null, 'Hello ', React.createElement('mark', null, 'Sindre')));
880887
});
881888
}
882889
}
883890

884891
{
885-
const executionContext = setup('rendered comparison');
892+
const testInstance = setup('rendered comparison');
886893
if (updating) {
887-
assertions.snapshot.call(executionContext, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
894+
assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Sindre'})).toJSON());
888895
} else {
889896
failsWith(t, () => {
890-
assertions.snapshot.call(executionContext, renderer.create(React.createElement(HelloMessage, {name: 'Vadim'})).toJSON());
897+
assertions.snapshot.call(testInstance, renderer.create(React.createElement(HelloMessage, {name: 'Vadim'})).toJSON());
891898
}, {
892899
assertion: 'snapshot',
893900
message: 'Did not match snapshot',
@@ -897,12 +904,12 @@ test('.snapshot()', t => {
897904
}
898905

899906
{
900-
const executionContext = setup('element comparison');
907+
const testInstance = setup('element comparison');
901908
if (updating) {
902-
assertions.snapshot.call(executionContext, React.createElement(HelloMessage, {name: 'Sindre'}));
909+
assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Sindre'}));
903910
} else {
904911
failsWith(t, () => {
905-
assertions.snapshot.call(executionContext, React.createElement(HelloMessage, {name: 'Vadim'}));
912+
assertions.snapshot.call(testInstance, React.createElement(HelloMessage, {name: 'Vadim'}));
906913
}, {
907914
assertion: 'snapshot',
908915
message: 'Did not match snapshot',

0 commit comments

Comments
 (0)