UNPKG

4.38 kBJavaScriptView Raw
1'use strict';
2var util = require('util');
3var EventEmitter = require('events').EventEmitter;
4var Promise = require('bluebird');
5var hasFlag = require('has-flag');
6var Test = require('./test');
7
8function noop() {}
9
10function each(items, fn) {
11 return Promise.all(items.map(fn));
12}
13
14function eachSeries(items, fn) {
15 return Promise.resolve(items).each(fn);
16}
17
18function Runner(opts) {
19 if (!(this instanceof Runner)) {
20 return new Runner(opts);
21 }
22
23 EventEmitter.call(this);
24
25 this.results = [];
26
27 this.stats = {
28 failCount: 0,
29 testCount: 0
30 };
31
32 this.tests = {
33 concurrent: [],
34 serial: [],
35 before: [],
36 after: [],
37 beforeEach: [],
38 afterEach: []
39 };
40}
41
42util.inherits(Runner, EventEmitter);
43module.exports = Runner;
44
45Runner.prototype.addTest = function (title, cb) {
46 this.stats.testCount++;
47 this.tests.concurrent.push(new Test(title, cb));
48};
49
50Runner.prototype.addSerialTest = function (title, cb) {
51 this.stats.testCount++;
52 this.tests.serial.push(new Test(title, cb));
53};
54
55Runner.prototype.addBeforeHook = function (title, cb) {
56 var test = new Test(title, cb);
57 test.type = 'hook';
58
59 this.tests.before.push(test);
60};
61
62Runner.prototype.addAfterHook = function (title, cb) {
63 var test = new Test(title, cb);
64 test.type = 'hook';
65
66 this.tests.after.push(test);
67};
68
69Runner.prototype.addBeforeEachHook = function (title, cb) {
70 if (!cb) {
71 cb = title;
72 title = undefined;
73 }
74
75 this.tests.beforeEach.push({
76 title: title,
77 fn: cb
78 });
79};
80
81Runner.prototype.addAfterEachHook = function (title, cb) {
82 if (!cb) {
83 cb = title;
84 title = undefined;
85 }
86
87 this.tests.afterEach.push({
88 title: title,
89 fn: cb
90 });
91};
92
93Runner.prototype.addSkippedTest = function (title, cb) {
94 var test = new Test(title, cb);
95 test.skip = true;
96
97 this.tests.concurrent.push(test);
98};
99
100Runner.prototype._runTestWithHooks = function (test) {
101 if (test.skip) {
102 this._addTestResult(test);
103 return Promise.resolve();
104 }
105
106 var beforeHooks = this.tests.beforeEach.map(function (hook) {
107 var title = hook.title || 'beforeEach for "' + test.title + '"';
108 hook = new Test(title, hook.fn);
109 hook.type = 'eachHook';
110
111 return hook;
112 });
113
114 var afterHooks = this.tests.afterEach.map(function (hook) {
115 var title = hook.title || 'afterEach for "' + test.title + '"';
116 hook = new Test(title, hook.fn);
117 hook.type = 'eachHook';
118
119 return hook;
120 });
121
122 var tests = [];
123
124 tests.push.apply(tests, beforeHooks);
125 tests.push(test);
126 tests.push.apply(tests, afterHooks);
127
128 // wrapper to allow context to be a primitive value
129 var contextWrapper = {
130 context: {}
131 };
132
133 return eachSeries(tests, function (test) {
134 Object.defineProperty(test, 'context', {
135 get: function () {
136 return contextWrapper.context;
137 },
138 set: function (val) {
139 contextWrapper.context = val;
140 }
141 });
142
143 return this._runTest(test);
144 }.bind(this)).catch(noop);
145};
146
147Runner.prototype._runTest = function (test) {
148 // add test result regardless of state
149 // but on error, don't execute next tests
150 return test.run()
151 .finally(function () {
152 this._addTestResult(test);
153 }.bind(this));
154};
155
156Runner.prototype.concurrent = function (tests) {
157 if (hasFlag('serial')) {
158 return this.serial(tests);
159 }
160
161 return each(tests, this._runTestWithHooks.bind(this));
162};
163
164Runner.prototype.serial = function (tests) {
165 return eachSeries(tests, this._runTestWithHooks.bind(this));
166};
167
168Runner.prototype._addTestResult = function (test) {
169 if (test.assertError) {
170 this.stats.failCount++;
171 }
172
173 var props = {
174 duration: test.duration,
175 title: test.title,
176 error: test.assertError,
177 type: test.type,
178 skip: test.skip
179 };
180
181 this.results.push(props);
182 this.emit('test', props);
183};
184
185Runner.prototype.run = function () {
186 var self = this;
187 var tests = this.tests;
188 var stats = this.stats;
189
190 // Runner is executed directly in tests, in that case process.send() == undefined
191 if (process.send) {
192 process.send({
193 name: 'stats',
194 data: stats
195 });
196 }
197
198 return eachSeries(tests.before, this._runTest.bind(this))
199 .catch(noop)
200 .then(function () {
201 if (stats.failCount > 0) {
202 return Promise.reject();
203 }
204 })
205 .then(function () {
206 return self.serial(tests.serial);
207 })
208 .then(function () {
209 return self.concurrent(tests.concurrent);
210 })
211 .then(function () {
212 return eachSeries(tests.after, self._runTest.bind(self));
213 })
214 .catch(noop)
215 .then(function () {
216 stats.passCount = stats.testCount - stats.failCount;
217 });
218};