|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 "use strict"; |
|
5 |
|
6 module.metadata = { |
|
7 "stability": "unstable" |
|
8 }; |
|
9 |
|
10 const { isFunction, isNull, isObject, isString, |
|
11 isRegExp, isArray, isDate, isPrimitive, |
|
12 isUndefined, instanceOf, source } = require("../lang/type"); |
|
13 |
|
14 /** |
|
15 * The `AssertionError` is defined in assert. |
|
16 * @extends Error |
|
17 * @example |
|
18 * new assert.AssertionError({ |
|
19 * message: message, |
|
20 * actual: actual, |
|
21 * expected: expected |
|
22 * }) |
|
23 */ |
|
24 function AssertionError(options) { |
|
25 let assertionError = Object.create(AssertionError.prototype); |
|
26 |
|
27 if (isString(options)) |
|
28 options = { message: options }; |
|
29 if ("actual" in options) |
|
30 assertionError.actual = options.actual; |
|
31 if ("expected" in options) |
|
32 assertionError.expected = options.expected; |
|
33 if ("operator" in options) |
|
34 assertionError.operator = options.operator; |
|
35 |
|
36 assertionError.message = options.message; |
|
37 assertionError.stack = new Error().stack; |
|
38 return assertionError; |
|
39 } |
|
40 AssertionError.prototype = Object.create(Error.prototype, { |
|
41 constructor: { value: AssertionError }, |
|
42 name: { value: "AssertionError", enumerable: true }, |
|
43 toString: { value: function toString() { |
|
44 let value; |
|
45 if (this.message) { |
|
46 value = this.name + " : " + this.message; |
|
47 } |
|
48 else { |
|
49 value = [ |
|
50 this.name + " : ", |
|
51 source(this.expected), |
|
52 this.operator, |
|
53 source(this.actual) |
|
54 ].join(" "); |
|
55 } |
|
56 return value; |
|
57 }} |
|
58 }); |
|
59 exports.AssertionError = AssertionError; |
|
60 |
|
61 function Assert(logger) { |
|
62 let assert = Object.create(Assert.prototype, { _log: { value: logger }}); |
|
63 |
|
64 assert.fail = assert.fail.bind(assert); |
|
65 assert.pass = assert.pass.bind(assert); |
|
66 |
|
67 return assert; |
|
68 } |
|
69 |
|
70 Assert.prototype = { |
|
71 fail: function fail(e) { |
|
72 if (!e || typeof(e) !== 'object') { |
|
73 this._log.fail(e); |
|
74 return; |
|
75 } |
|
76 let message = e.message; |
|
77 try { |
|
78 if ('operator' in e) { |
|
79 message += [ |
|
80 " -", |
|
81 source(e.expected), |
|
82 e.operator, |
|
83 source(e.actual) |
|
84 ].join(" "); |
|
85 } |
|
86 } |
|
87 catch(e) {} |
|
88 this._log.fail(message); |
|
89 }, |
|
90 pass: function pass(message) { |
|
91 this._log.pass(message); |
|
92 }, |
|
93 error: function error(e) { |
|
94 this._log.exception(e); |
|
95 }, |
|
96 ok: function ok(value, message) { |
|
97 if (!!!value) { |
|
98 this.fail({ |
|
99 actual: value, |
|
100 expected: true, |
|
101 message: message, |
|
102 operator: "==" |
|
103 }); |
|
104 } |
|
105 else { |
|
106 this.pass(message); |
|
107 } |
|
108 }, |
|
109 |
|
110 /** |
|
111 * The equality assertion tests shallow, coercive equality with `==`. |
|
112 * @example |
|
113 * assert.equal(1, 1, "one is one"); |
|
114 */ |
|
115 equal: function equal(actual, expected, message) { |
|
116 if (actual == expected) { |
|
117 this.pass(message); |
|
118 } |
|
119 else { |
|
120 this.fail({ |
|
121 actual: actual, |
|
122 expected: expected, |
|
123 message: message, |
|
124 operator: "==" |
|
125 }); |
|
126 } |
|
127 }, |
|
128 |
|
129 /** |
|
130 * The non-equality assertion tests for whether two objects are not equal |
|
131 * with `!=`. |
|
132 * @example |
|
133 * assert.notEqual(1, 2, "one is not two"); |
|
134 */ |
|
135 notEqual: function notEqual(actual, expected, message) { |
|
136 if (actual != expected) { |
|
137 this.pass(message); |
|
138 } |
|
139 else { |
|
140 this.fail({ |
|
141 actual: actual, |
|
142 expected: expected, |
|
143 message: message, |
|
144 operator: "!=", |
|
145 }); |
|
146 } |
|
147 }, |
|
148 |
|
149 /** |
|
150 * The equivalence assertion tests a deep (with `===`) equality relation. |
|
151 * @example |
|
152 * assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects") |
|
153 */ |
|
154 deepEqual: function deepEqual(actual, expected, message) { |
|
155 if (isDeepEqual(actual, expected)) { |
|
156 this.pass(message); |
|
157 } |
|
158 else { |
|
159 this.fail({ |
|
160 actual: actual, |
|
161 expected: expected, |
|
162 message: message, |
|
163 operator: "deepEqual" |
|
164 }); |
|
165 } |
|
166 }, |
|
167 |
|
168 /** |
|
169 * The non-equivalence assertion tests for any deep (with `===`) inequality. |
|
170 * @example |
|
171 * assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }), |
|
172 * "object's inherit from different prototypes"); |
|
173 */ |
|
174 notDeepEqual: function notDeepEqual(actual, expected, message) { |
|
175 if (!isDeepEqual(actual, expected)) { |
|
176 this.pass(message); |
|
177 } |
|
178 else { |
|
179 this.fail({ |
|
180 actual: actual, |
|
181 expected: expected, |
|
182 message: message, |
|
183 operator: "notDeepEqual" |
|
184 }); |
|
185 } |
|
186 }, |
|
187 |
|
188 /** |
|
189 * The strict equality assertion tests strict equality, as determined by |
|
190 * `===`. |
|
191 * @example |
|
192 * assert.strictEqual(null, null, "`null` is `null`") |
|
193 */ |
|
194 strictEqual: function strictEqual(actual, expected, message) { |
|
195 if (actual === expected) { |
|
196 this.pass(message); |
|
197 } |
|
198 else { |
|
199 this.fail({ |
|
200 actual: actual, |
|
201 expected: expected, |
|
202 message: message, |
|
203 operator: "===" |
|
204 }); |
|
205 } |
|
206 }, |
|
207 |
|
208 /** |
|
209 * The strict non-equality assertion tests for strict inequality, as |
|
210 * determined by `!==`. |
|
211 * @example |
|
212 * assert.notStrictEqual(null, undefined, "`null` is not `undefined`"); |
|
213 */ |
|
214 notStrictEqual: function notStrictEqual(actual, expected, message) { |
|
215 if (actual !== expected) { |
|
216 this.pass(message); |
|
217 } |
|
218 else { |
|
219 this.fail({ |
|
220 actual: actual, |
|
221 expected: expected, |
|
222 message: message, |
|
223 operator: "!==" |
|
224 }) |
|
225 } |
|
226 }, |
|
227 |
|
228 /** |
|
229 * The assertion whether or not given `block` throws an exception. If optional |
|
230 * `Error` argument is provided and it's type of function thrown error is |
|
231 * asserted to be an instance of it, if type of `Error` is string then message |
|
232 * of throw exception is asserted to contain it. |
|
233 * @param {Function} block |
|
234 * Function that is expected to throw. |
|
235 * @param {Error|RegExp} [Error] |
|
236 * Error constructor that is expected to be thrown or a string that |
|
237 * must be contained by a message of the thrown exception, or a RegExp |
|
238 * matching a message of the thrown exception. |
|
239 * @param {String} message |
|
240 * Description message |
|
241 * |
|
242 * @examples |
|
243 * |
|
244 * assert.throws(function block() { |
|
245 * doSomething(4) |
|
246 * }, "Object is expected", "Incorrect argument is passed"); |
|
247 * |
|
248 * assert.throws(function block() { |
|
249 * Object.create(5) |
|
250 * }, TypeError, "TypeError is thrown"); |
|
251 */ |
|
252 throws: function throws(block, Error, message) { |
|
253 let threw = false; |
|
254 let exception = null; |
|
255 |
|
256 // If third argument is not provided and second argument is a string it |
|
257 // means that optional `Error` argument was not passed, so we shift |
|
258 // arguments. |
|
259 if (isString(Error) && isUndefined(message)) { |
|
260 message = Error; |
|
261 Error = undefined; |
|
262 } |
|
263 |
|
264 // Executing given `block`. |
|
265 try { |
|
266 block(); |
|
267 } |
|
268 catch (e) { |
|
269 threw = true; |
|
270 exception = e; |
|
271 } |
|
272 |
|
273 // If exception was thrown and `Error` argument was not passed assert is |
|
274 // passed. |
|
275 if (threw && (isUndefined(Error) || |
|
276 // If passed `Error` is RegExp using it's test method to |
|
277 // assert thrown exception message. |
|
278 (isRegExp(Error) && Error.test(exception.message)) || |
|
279 // If passed `Error` is a constructor function testing if |
|
280 // thrown exception is an instance of it. |
|
281 (isFunction(Error) && instanceOf(exception, Error)))) |
|
282 { |
|
283 this.pass(message); |
|
284 } |
|
285 |
|
286 // Otherwise we report assertion failure. |
|
287 else { |
|
288 let failure = { |
|
289 message: message, |
|
290 operator: "throws" |
|
291 }; |
|
292 |
|
293 if (exception) |
|
294 failure.actual = exception; |
|
295 |
|
296 if (Error) |
|
297 failure.expected = Error; |
|
298 |
|
299 this.fail(failure); |
|
300 } |
|
301 } |
|
302 }; |
|
303 exports.Assert = Assert; |
|
304 |
|
305 function isDeepEqual(actual, expected) { |
|
306 |
|
307 // 7.1. All identical values are equivalent, as determined by ===. |
|
308 if (actual === expected) { |
|
309 return true; |
|
310 } |
|
311 |
|
312 // 7.2. If the expected value is a Date object, the actual value is |
|
313 // equivalent if it is also a Date object that refers to the same time. |
|
314 else if (isDate(actual) && isDate(expected)) { |
|
315 return actual.getTime() === expected.getTime(); |
|
316 } |
|
317 |
|
318 // XXX specification bug: this should be specified |
|
319 else if (isPrimitive(actual) || isPrimitive(expected)) { |
|
320 return expected === actual; |
|
321 } |
|
322 |
|
323 // 7.3. Other pairs that do not both pass typeof value == "object", |
|
324 // equivalence is determined by ==. |
|
325 else if (!isObject(actual) && !isObject(expected)) { |
|
326 return actual == expected; |
|
327 } |
|
328 |
|
329 // 7.4. For all other Object pairs, including Array objects, equivalence is |
|
330 // determined by having the same number of owned properties (as verified |
|
331 // with Object.prototype.hasOwnProperty.call), the same set of keys |
|
332 // (although not necessarily the same order), equivalent values for every |
|
333 // corresponding key, and an identical "prototype" property. Note: this |
|
334 // accounts for both named and indexed properties on Arrays. |
|
335 else { |
|
336 return actual.prototype === expected.prototype && |
|
337 isEquivalent(actual, expected); |
|
338 } |
|
339 } |
|
340 |
|
341 function isEquivalent(a, b, stack) { |
|
342 let aKeys = Object.keys(a); |
|
343 let bKeys = Object.keys(b); |
|
344 |
|
345 return aKeys.length === bKeys.length && |
|
346 isArrayEquivalent(aKeys.sort(), bKeys.sort()) && |
|
347 aKeys.every(function(key) { |
|
348 return isDeepEqual(a[key], b[key], stack) |
|
349 }); |
|
350 } |
|
351 |
|
352 function isArrayEquivalent(a, b, stack) { |
|
353 return isArray(a) && isArray(b) && |
|
354 a.every(function(value, index) { |
|
355 return isDeepEqual(value, b[index]); |
|
356 }); |
|
357 } |