Skip to content

Commit 51a0ff0

Browse files
kugtong33novemberborn
authored andcommitted
Support t.context in before and after hooks
1 parent 73baf70 commit 51a0ff0

File tree

8 files changed

+108
-66
lines changed

8 files changed

+108
-66
lines changed

index.d.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ export interface TestInterface<Context = {}> {
6969
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
7070
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
7171

72-
after: AfterInterface<null>;
72+
after: AfterInterface<Context>;
7373
afterEach: AfterInterface<Context>;
74-
before: BeforeInterface<null>;
74+
before: BeforeInterface<Context>;
7575
beforeEach: BeforeInterface<Context>;
7676
cb: CbInterface<Context>;
7777
failing: FailingInterface<Context>;
@@ -187,9 +187,9 @@ declare const test: TestInterface;
187187
export default test;
188188

189189
export {test};
190-
export const after: AfterInterface<null>;
190+
export const after: AfterInterface;
191191
export const afterEach: AfterInterface;
192-
export const before: BeforeInterface<null>;
192+
export const before: BeforeInterface;
193193
export const beforeEach: BeforeInterface;
194194
export const cb: CbInterface;
195195
export const failing: FailingInterface;

index.js.flow

+4-4
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ export interface TestInterface<Context = {}> {
7272
(title: string, macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
7373
(macro: Macro<Context> | Macro<Context>[], ...args: Array<any>): void;
7474

75-
after: AfterInterface<null>;
75+
after: AfterInterface<Context>;
7676
afterEach: AfterInterface<Context>;
77-
before: BeforeInterface<null>;
77+
before: BeforeInterface<Context>;
7878
beforeEach: BeforeInterface<Context>;
7979
cb: CbInterface<Context>;
8080
failing: FailingInterface<Context>;
@@ -189,9 +189,9 @@ export type TodoDeclaration = {(title: string): void};
189189
declare export default TestInterface<>;
190190

191191
declare export var test: TestInterface<>;
192-
declare export var after: AfterInterface<null>;
192+
declare export var after: AfterInterface<>;
193193
declare export var afterEach: AfterInterface<>;
194-
declare export var before: BeforeInterface<null>;
194+
declare export var before: BeforeInterface<>;
195195
declare export var beforeEach: BeforeInterface<>;
196196
declare export var cb: CbInterface<>;
197197
declare export var failing: FailingInterface<>;

lib/test-collection.js

+56-23
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
'use strict';
22
const EventEmitter = require('events');
3+
const clone = require('lodash.clone');
34
const Concurrent = require('./concurrent');
45
const Sequence = require('./sequence');
56
const Test = require('./test');
67

8+
class ContextRef {
9+
constructor() {
10+
this.value = {};
11+
}
12+
13+
get() {
14+
return this.value;
15+
}
16+
17+
set(newValue) {
18+
this.value = newValue;
19+
}
20+
21+
copy() {
22+
return new LateBinding(this); // eslint-disable-line no-use-before-define
23+
}
24+
}
25+
26+
class LateBinding extends ContextRef {
27+
constructor(ref) {
28+
super();
29+
this.ref = ref;
30+
this.bound = false;
31+
}
32+
33+
get() {
34+
if (!this.bound) {
35+
this.set(clone(this.ref.get()));
36+
}
37+
return super.get();
38+
}
39+
40+
set(newValue) {
41+
this.bound = true;
42+
super.set(newValue);
43+
}
44+
}
45+
746
class TestCollection extends EventEmitter {
847
constructor(options) {
948
super();
@@ -98,9 +137,9 @@ class TestCollection extends EventEmitter {
98137
this.emit('test', result);
99138
}
100139

101-
_buildHooks(hooks, testTitle, context) {
140+
_buildHooks(hooks, testTitle, contextRef) {
102141
return hooks.map(hook => {
103-
const test = this._buildHook(hook, testTitle, context);
142+
const test = this._buildHook(hook, testTitle, contextRef);
104143

105144
if (hook.metadata.skipped || hook.metadata.todo) {
106145
return this._skippedTest(test);
@@ -117,10 +156,6 @@ class TestCollection extends EventEmitter {
117156
title += ` for ${testTitle}`;
118157
}
119158

120-
if (!contextRef) {
121-
contextRef = null;
122-
}
123-
124159
const test = new Test({
125160
contextRef,
126161
failWithoutAssertions: false,
@@ -135,10 +170,6 @@ class TestCollection extends EventEmitter {
135170
}
136171

137172
_buildTest(test, contextRef) {
138-
if (!contextRef) {
139-
contextRef = null;
140-
}
141-
142173
test = new Test({
143174
contextRef,
144175
failWithoutAssertions: this.failWithoutAssertions,
@@ -152,26 +183,26 @@ class TestCollection extends EventEmitter {
152183
return test;
153184
}
154185

155-
_buildTestWithHooks(test) {
186+
_buildTestWithHooks(test, contextRef) {
156187
if (test.metadata.skipped || test.metadata.todo) {
157188
return new Sequence([this._skippedTest(this._buildTest(test))], true);
158189
}
159190

160-
const context = {context: {}};
191+
const copiedRef = contextRef.copy();
161192

162-
const beforeHooks = this._buildHooks(this.hooks.beforeEach, test.title, context);
163-
const afterHooks = this._buildHooks(this.hooks.afterEach, test.title, context);
193+
const beforeHooks = this._buildHooks(this.hooks.beforeEach, test.title, copiedRef);
194+
const afterHooks = this._buildHooks(this.hooks.afterEach, test.title, copiedRef);
164195

165-
let sequence = new Sequence([].concat(beforeHooks, this._buildTest(test, context), afterHooks), true);
196+
let sequence = new Sequence([].concat(beforeHooks, this._buildTest(test, copiedRef), afterHooks), true);
166197
if (this.hooks.afterEachAlways.length > 0) {
167-
const afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterEachAlways, test.title, context));
198+
const afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterEachAlways, test.title, copiedRef));
168199
sequence = new Sequence([sequence, afterAlwaysHooks], false);
169200
}
170201
return sequence;
171202
}
172203

173-
_buildTests(tests) {
174-
return tests.map(test => this._buildTestWithHooks(test));
204+
_buildTests(tests, contextRef) {
205+
return tests.map(test => this._buildTestWithHooks(test, contextRef));
175206
}
176207

177208
_hasUnskippedTests() {
@@ -182,22 +213,24 @@ class TestCollection extends EventEmitter {
182213
}
183214

184215
build() {
185-
const serialTests = new Sequence(this._buildTests(this.tests.serial), this.bail);
186-
const concurrentTests = new Concurrent(this._buildTests(this.tests.concurrent), this.bail);
216+
const contextRef = new ContextRef();
217+
218+
const serialTests = new Sequence(this._buildTests(this.tests.serial, contextRef), this.bail);
219+
const concurrentTests = new Concurrent(this._buildTests(this.tests.concurrent, contextRef), this.bail);
187220
const allTests = new Sequence([serialTests, concurrentTests]);
188221

189222
let finalTests;
190223
// Only run before and after hooks when there are unskipped tests
191224
if (this._hasUnskippedTests()) {
192-
const beforeHooks = new Sequence(this._buildHooks(this.hooks.before));
193-
const afterHooks = new Sequence(this._buildHooks(this.hooks.after));
225+
const beforeHooks = new Sequence(this._buildHooks(this.hooks.before, null, contextRef));
226+
const afterHooks = new Sequence(this._buildHooks(this.hooks.after, null, contextRef));
194227
finalTests = new Sequence([beforeHooks, allTests, afterHooks], true);
195228
} else {
196229
finalTests = new Sequence([allTests], true);
197230
}
198231

199232
if (this.hooks.afterAlways.length > 0) {
200-
const afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterAlways));
233+
const afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterAlways, null, contextRef));
201234
finalTests = new Sequence([finalTests, afterAlwaysHooks], false);
202235
}
203236

lib/test.js

+2-10
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,11 @@ class ExecutionContext {
5353
}
5454

5555
get context() {
56-
const contextRef = this._test.contextRef;
57-
return contextRef && contextRef.context;
56+
return this._test.contextRef.get();
5857
}
5958

6059
set context(context) {
61-
const contextRef = this._test.contextRef;
62-
63-
if (!contextRef) {
64-
this._test.saveFirstError(new Error(`\`t.context\` is not available in ${this._test.metadata.type} tests`));
65-
return;
66-
}
67-
68-
contextRef.context = context;
60+
this._test.contextRef.set(context);
6961
}
7062

7163
_throwsArgStart(assertion, file, line) {

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
"is-observable": "^1.1.0",
111111
"is-promise": "^2.1.0",
112112
"last-line-stream": "^1.0.0",
113+
"lodash.clone": "^4.5.0",
113114
"lodash.clonedeepwith": "^4.5.0",
114115
"lodash.debounce": "^4.0.3",
115116
"lodash.difference": "^4.3.0",

test/hooks.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -454,26 +454,36 @@ test('shared context', t => {
454454
const runner = new Runner();
455455

456456
runner.chain.before(a => {
457-
a.is(a.context, null);
457+
a.deepEqual(a.context, {});
458+
a.context.arr = ['a'];
459+
a.context.prop = 'before';
458460
});
459461

460462
runner.chain.after(a => {
461-
a.is(a.context, null);
463+
a.deepEqual(a.context.arr, ['a', 'b', 'c', 'd']);
464+
a.is(a.context.prop, 'before');
462465
});
463466

464467
runner.chain.beforeEach(a => {
465-
a.context.arr = ['a'];
468+
a.deepEqual(a.context.arr, ['a']);
469+
a.context.arr.push('b');
470+
a.is(a.context.prop, 'before');
471+
a.context.prop = 'beforeEach';
466472
});
467473

468474
runner.chain('test', a => {
469475
a.pass();
470-
a.context.arr.push('b');
471476
a.deepEqual(a.context.arr, ['a', 'b']);
477+
a.context.arr.push('c');
478+
a.is(a.context.prop, 'beforeEach');
479+
a.context.prop = 'test';
472480
});
473481

474482
runner.chain.afterEach(a => {
475-
a.context.arr.push('c');
476483
a.deepEqual(a.context.arr, ['a', 'b', 'c']);
484+
a.context.arr.push('d');
485+
a.is(a.context.prop, 'test');
486+
a.context.prop = 'afterEach';
477487
});
478488

479489
return runner.run({}).then(() => {

test/test.js

+21-20
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,21 @@ const Test = require('../lib/test');
88
const failingTestHint = 'Test was expected to fail, but succeeded, you should stop marking the test as failing';
99
const noop = () => {};
1010

11+
class ContextRef {
12+
constructor() {
13+
this.value = {};
14+
}
15+
get() {
16+
return this.value;
17+
}
18+
set(newValue) {
19+
this.value = newValue;
20+
}
21+
}
22+
1123
function ava(fn, contextRef, onResult) {
1224
return new Test({
13-
contextRef,
25+
contextRef: contextRef || new ContextRef(),
1426
failWithoutAssertions: true,
1527
fn,
1628
metadata: {type: 'test', callback: false},
@@ -21,7 +33,7 @@ function ava(fn, contextRef, onResult) {
2133

2234
ava.failing = (fn, contextRef, onResult) => {
2335
return new Test({
24-
contextRef,
36+
contextRef: contextRef || new ContextRef(),
2537
failWithoutAssertions: true,
2638
fn,
2739
metadata: {type: 'test', callback: false, failing: true},
@@ -32,7 +44,7 @@ ava.failing = (fn, contextRef, onResult) => {
3244

3345
ava.cb = (fn, contextRef, onResult) => {
3446
return new Test({
35-
contextRef,
47+
contextRef: contextRef || new ContextRef(),
3648
failWithoutAssertions: true,
3749
fn,
3850
metadata: {type: 'test', callback: true},
@@ -43,7 +55,7 @@ ava.cb = (fn, contextRef, onResult) => {
4355

4456
ava.cb.failing = (fn, contextRef, onResult) => {
4557
return new Test({
46-
contextRef,
58+
contextRef: contextRef || new ContextRef(),
4759
failWithoutAssertions: true,
4860
fn,
4961
metadata: {type: 'test', callback: true, failing: true},
@@ -615,7 +627,11 @@ test('no crash when adding assertions after the test has ended', t => {
615627

616628
test('contextRef', t => {
617629
new Test({
618-
contextRef: {context: {foo: 'bar'}},
630+
contextRef: {
631+
get() {
632+
return {foo: 'bar'};
633+
}
634+
},
619635
failWithoutAssertions: true,
620636
fn(a) {
621637
a.pass();
@@ -628,21 +644,6 @@ test('contextRef', t => {
628644
}).run();
629645
});
630646

631-
test('it is an error to set context in a hook', t => {
632-
let result;
633-
const avaTest = ava(a => {
634-
a.context = 'foo';
635-
}, null, r => {
636-
result = r;
637-
});
638-
avaTest.metadata.type = 'foo';
639-
640-
const passed = avaTest.run();
641-
t.is(passed, false);
642-
t.match(result.reason.message, /`t\.context` is not available in foo tests/);
643-
t.end();
644-
});
645-
646647
test('failing tests should fail', t => {
647648
const passed = ava.failing('foo', a => {
648649
a.fail();

0 commit comments

Comments
 (0)