1 | 'use strict';
|
2 |
|
3 | var assert = require('assert');
|
4 | var EventEmitter = require('events').EventEmitter;
|
5 | var Promise = require('promise');
|
6 | var chalk = require('chalk');
|
7 | var ms = require('ms');
|
8 | var result = require('test-result');
|
9 | var timeout = require('./timeout');
|
10 |
|
11 | module.exports = TestSuite;
|
12 | function TestSuite(name) {
|
13 | this.name = name;
|
14 | this.colors = true;
|
15 | this._queue = [];
|
16 | this._stack = [];
|
17 | EventEmitter.call(this);
|
18 | }
|
19 | TestSuite.prototype = Object.create(EventEmitter.prototype);
|
20 | TestSuite.constructor = TestSuite;
|
21 | TestSuite.now = function () {
|
22 | return (new Date()).getTime();
|
23 | };
|
24 |
|
25 | TestSuite.prototype.addTest = function (name, fn, options) {
|
26 | assert(typeof name === 'string', 'The description must be a string')
|
27 | assert(typeof fn === 'function', 'The test must be a function')
|
28 | if (fn.length === 1) {
|
29 | fn = Promise.denodeify(fn);
|
30 | }
|
31 | this._queue.push(new TestCase(false, name, fn, options || {}));
|
32 | };
|
33 | TestSuite.prototype.addCode = function (fn, options) {
|
34 | this._queue.push(new TestCase(true, '', fn, options || {}));
|
35 | };
|
36 | TestSuite.prototype.run = function () {
|
37 | var index = 0;
|
38 | var self = this;
|
39 | return new Promise(function (resolve, reject) {
|
40 | function next() {
|
41 | while (index >= self._queue.length && self._stack.length) {
|
42 | var frame = self._stack.pop();
|
43 | index = frame.index;
|
44 | self._queue = frame.queue;
|
45 | self.emit('end-section', frame.name);
|
46 | }
|
47 | if (index >= self._queue.length) {
|
48 | self.emit('suite-pass');
|
49 | return resolve();
|
50 | }
|
51 | var test = self._queue[index];
|
52 | if (test.justRun) {
|
53 | self.emit('run-start');
|
54 | Promise.resolve(null).then(function () {
|
55 | return timeout(test.fn(), test.timeout);
|
56 | }).done(function () {
|
57 | self.emit('run-pass');
|
58 | self.emit('run-end');
|
59 | index++;
|
60 | next();
|
61 | }, function (err) {
|
62 | self.emit('run-fail', err);
|
63 | self.emit('run-end');
|
64 | self.emit('suite-fail');
|
65 | reject(err);
|
66 | });
|
67 | return;
|
68 | }
|
69 | self._stack.push(new StackFrame(index + 1, self._queue, test.name));
|
70 | index = 0;
|
71 | self._queue = [];
|
72 | self.emit('start', test.name);
|
73 | Promise.resolve(null).then(function () {
|
74 | return timeout(test.fn(), test.timeout)
|
75 | }).done(function () {
|
76 | if (self._queue.length) {
|
77 | self.emit('end', test.name);
|
78 | self.emit('start-section', test.name);
|
79 | } else {
|
80 | self.emit('pass', test.name);
|
81 | self.emit('end', test.name);
|
82 | var frame = self._stack.pop();
|
83 | index = frame.index;
|
84 | self._queue = frame.queue;
|
85 | }
|
86 | next();
|
87 | }, function (err) {
|
88 | self.emit('fail', test.name, err);
|
89 | self.emit('end', test.name);
|
90 | self.emit('suite-fail');
|
91 | reject(err);
|
92 | });
|
93 | }
|
94 | self.emit('suite-start');
|
95 | next();
|
96 | });
|
97 | };
|
98 |
|
99 | TestSuite.prototype.disableColors = function () {
|
100 | this.colors = false;
|
101 | };
|
102 | TestSuite.prototype.addLogging = function () {
|
103 | var self = this;
|
104 | function color(color, str) {
|
105 | return self.colors ? chalk[color](str) : str;
|
106 | }
|
107 | var indent = [];
|
108 | var start = Date.now();
|
109 | this.on('start-section', function (name) {
|
110 | console.log(indent.join('') + color('magenta', ' \u2022 ') + name);
|
111 | indent.push(' ');
|
112 | });
|
113 | this.on('end-section', function (name) {
|
114 | indent.pop();
|
115 | });
|
116 | this.on('start', function () {
|
117 | start = TestSuite.now();
|
118 | });
|
119 | this.on('run-start', function () {
|
120 | start = TestSuite.now();
|
121 | });
|
122 | this.on('pass', function (name) {
|
123 | var end = TestSuite.now();
|
124 | var duration = end - start;
|
125 | console.log(indent.join('') +
|
126 | color('green', ' \u2713 ') +
|
127 | name +
|
128 | color('cyan', ' (' + ms(duration) + ')'));
|
129 | });
|
130 | this.on('fail', function (name, err) {
|
131 | var end = TestSuite.now();
|
132 | var duration = end - start;
|
133 | console.log(indent.join('') +
|
134 | color('red', ' \u2717 ') +
|
135 | name +
|
136 | color('cyan', ' (' + ms(duration) + ')'));
|
137 | console.log('');
|
138 | var errString = errorToString(err);
|
139 | console.log(errString.replace(/^/gm, indent.join('') + ' '));
|
140 | });
|
141 | this.on('run-fail', function (err) {
|
142 | var end = TestSuite.now();
|
143 | var duration = end - start;
|
144 | console.log(indent.join('') +
|
145 | color('red', ' \u2717 ') +
|
146 | 'run' +
|
147 | color('cyan', ' (' + ms(duration) + ')'));
|
148 | console.log('');
|
149 | var errString = errorToString(err);
|
150 | console.log(errString.replace(/^/gm, indent.join('') + ' '));
|
151 | });
|
152 | };
|
153 | TestSuite.prototype.addExit = function () {
|
154 | var name = this.name || 'tests';
|
155 | var start = TestSuite.now();
|
156 | this.on('suite-start', function () {
|
157 | start = TestSuite.now();
|
158 | });
|
159 | this.on('suite-pass', function () {
|
160 | console.log('');
|
161 | console.log('Total duration ' + ms(TestSuite.now() - start));
|
162 | result.pass(name);
|
163 | });
|
164 | this.on('suite-fail', function () {
|
165 | console.log('');
|
166 | console.log('Total duration ' + ms(TestSuite.now() - start));
|
167 | result.fail(name);
|
168 | });
|
169 | };
|
170 |
|
171 | function TestCase(justRun, name, fn, options) {
|
172 | this.justRun = justRun;
|
173 | this.name = name;
|
174 | this.fn = fn;
|
175 | this.timeout = options.timeout || '20 seconds';
|
176 | }
|
177 | function StackFrame(index, queue, name) {
|
178 | this.index = index;
|
179 | this.queue = queue;
|
180 | this.name = name;
|
181 | }
|
182 |
|
183 | function errorToString(e) {
|
184 | return '' + (e.stack || (e.message || e));
|
185 | }
|