UNPKG

6.3 kBJavaScriptView Raw
1'use strict'
2
3var Assert = require('./assert').Assert
4 , Log = require('./log').Log
5 // constancts
6 , ERR_COMPLETED_ASSERT = 'Assert in completed test'
7 , ERR_COMPLETED_COMPLETE = 'Attemt to complete test more then one times'
8 , ERR_EXPECT = 'AssertionError'
9
10/**
11 * Test constructor. Wrapper of the CommonJS test function.
12 */
13function Test(options) {
14 var self = Object.create(Test.prototype,
15 { name: { value: options.name }
16 , mute: { value: options.mute }
17 , unit: { value: options.unit }
18 , log: { value: options.log }
19 , passes: { value: [] }
20 , fails: { value: [] }
21 , errors: { value: [] }
22 })
23 self.assert = options.Assert(self)
24 return self
25}
26Test.prototype =
27{ constructor: Test
28/**
29 * Name of the test unit.
30 * @param {String}
31 */
32, name: null
33/**
34 * Instance of Logger used to log results of the tests. All the nested tests
35 * and suites will get sub-loggers of this one.
36 * @type {Logger}
37 */
38, log: null
39/**
40 * CommonJS test function that is being wrapped by this object.
41 * @type {Function}
42 */
43, unit: null
44/**
45 * Array of all the `AssertError`s for the this unit.
46 * @type {AssertError[]}
47 */
48, fails: null
49/**
50 * Array of the exceptions that occured during execurtion of this unit.
51 * @type {Error[]}
52 */
53, errors: null
54/**
55 * Array of the passed assertion messages.
56 * @type {String[]}
57 */
58, passes: null
59/**
60 * Wheather or not test execution is finished. Used for logging errors for all
61 * the asserts that are executed after test is finished.
62 */
63, completed: false
64, pass: function pass(message) {
65 message = message || ''
66 if (this.completed) return this.error(new Error(ERR_COMPLETED_ASSERT))
67 this.passes.push(message)
68 if (!this.mute) this.log.pass(message)
69 }
70, fail: function fail(e) {
71 if (this.completed) return this.error(new Error(ERR_COMPLETED_ASSERT))
72 this.fails.push(e)
73 if (!this.mute) this.log.fail(e)
74 }
75, error: function error(e) {
76 this.errors.push(e)
77 if (!this.mute) this.log.error(e)
78 }
79, complete: function complete(callback) {
80 if (this.completed) return this.error(new Error(ERR_COMPLETED_COMPLETE))
81 callback(this, this.completed = true)
82 }
83, run: function run(callback) {
84 var unit = this.unit
85 , sync = unit.length <= 1
86 , failFast = unit.length == 0
87 , assert = this.assert
88 , complete = this.complete = this.complete.bind(this, callback)
89 try {
90 if (!this.mute) this.log.print(this.name)
91 unit(assert, complete)
92 if (failFast) this.pass()
93 if (sync) this.complete()
94 } catch(e) {
95 if (ERR_EXPECT == e.name) assert.fail(e)
96 else assert.error(e)
97 this.complete()
98 }
99 }
100}
101
102/**
103 * Test suite / group constructor. All the tests in the suite can be executed
104 * by calling `run` method on returned instance.
105 * @param {Object} options
106 * Options with keys:
107 * @param {Object} tests
108 * List of test functions / sublists of test functions.
109 * @param {Log} log
110 * Logger for this Suite. If this is sub-suite logger provided will be
111 * smart enough to indent results for this suite.
112 * @param {Assert} Assert
113 * Assertions constructor. Constructor is used to construct individual
114 * assert objects per test.
115 */
116function Suite(options) {
117 var log = options.log
118 , units = []
119 , unitMap = options.units
120
121 for (var name in unitMap) {
122 if (0 !== name.indexOf('test')) continue
123 var unit = unitMap[name]
124 units.push(('function' == typeof unit ? Test : Suite)(
125 { name: name
126 , mute: options.mute
127 , units: unit
128 , unit: unit
129 , Assert: unitMap.Assert || Assert
130 , log: log.section()
131 }))
132 }
133
134 return Object.create(Suite.prototype,
135 { name: { value: options.name }
136 , mute: { value: options.mute }
137 , log: { value: log }
138 , units: { value: units }
139 })
140}
141Suite.prototype = Object.create(
142 { constructor: Suite
143 /**
144 * Name of the test unit.
145 * @param {String}
146 */
147 , name: null
148 /**
149 * Instance of Logger used to log results of the tests. All the nested tests
150 * and suites will get sub-loggers of this one.
151 * @type {Logger}
152 */
153 , log: null
154 /**
155 * Array of all the `AssertError`s for the this unit.
156 * @type {AssertError[]}
157 */
158 , fails: null
159 /**
160 * Array of the exceptions that occured during execurtion of this unit.
161 * @type {Error[]}
162 */
163 , errors: null
164 /**
165 * Array of the passed assertion messages.
166 * @type {String[]}
167 */
168 , passes: null
169 /**
170 * List of tests / suites to run on execution.
171 * @type {Suite|Test[]}
172 */
173 , units: null
174 /**
175 * Index of the test that will be executed on calling `next`.
176 * @type {Number}
177 */
178 , index: 0
179 /**
180 * Callback that is called when all the tests in the suite are executed.
181 * @type {Function}
182 */
183 , complete: null
184 /**
185 * Calling this function executes all the tests in this and all the subsuites.
186 * Passed callback is called after all tests are finished.
187 * @param {Function} callback
188 * Function that will be called once whole suite is executed
189 */
190 , run: function run(callback) {
191 if (!this.mute) this.log.print(this.name)
192 this.complete = callback
193 this.next = this.next.bind(this)
194 this.next()
195 }
196 /**
197 * Runs next test / suite of tests. If no tests are left in the suite
198 * callback passed to the run method is called instead.
199 */
200 , next: function next() {
201 var units = this.units
202 if (this.index < units.length) {
203 units[this.index ++].run(this.next)
204 }
205 else this.complete(this)
206 }
207 }
208, { passes: { get: UnitedProprerty('passes') }
209 , fails: { get: UnitedProprerty('fails') }
210 , errors: { get: UnitedProprerty('errors') }
211 }
212)
213
214function UnitedProprerty(name) {
215 return function Property() {
216 return this.units.reduce(function(value, unit) {
217 return value.concat(unit[name])
218 }, [])
219 }
220}
221
222/**
223 * Runs passed tests.
224 */
225function run(units, callback) {
226 var log = Log()
227 Suite(
228 { name: 'Running all tests:'
229 , units: units
230 , mute: units.mute === true
231 , log: log
232 }
233 ).run(function(suite) {
234 if (callback) return callback(suite)
235 if (suite.mute) return
236 log.print
237 ( 'Passed:' + suite.passes.length
238 + ' Failed:' + suite.fails.length
239 + ' Errors:' + suite.errors.length
240 )
241 })
242}
243exports.run = run