UNPKG

19.5 kBJavaScriptView Raw
1'use strict';
2
3var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
4
5function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
6
7/*
8 * Copyright (c) 2013, Groupon, Inc.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions are
13 * met:
14 *
15 * Redistributions of source code must retain the above copyright notice,
16 * this list of conditions and the following disclaimer.
17 *
18 * Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 *
22 * Neither the name of GROUPON nor the names of its contributors may be
23 * used to endorse or promote products derived from this software without
24 * specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
27 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
29 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
32 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38/*
39Copyright (c) 2013, Groupon, Inc.
40All rights reserved.
41
42Redistribution and use in source and binary forms, with or without
43modification, are permitted provided that the following conditions are
44met:
45
46Redistributions of source code must retain the above copyright notice,
47this list of conditions and the following disclaimer.
48
49Redistributions in binary form must reproduce the above copyright
50notice, this list of conditions and the following disclaimer in the
51documentation and/or other materials provided with the distribution.
52
53Neither the name of GROUPON nor the names of its contributors may be
54used to endorse or promote products derived from this software without
55specific prior written permission.
56
57THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
58IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
59TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
60PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
61HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
62SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
63TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
64PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
65LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
66NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
67SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
68*/
69
70// eat _ off the global scope, or require it ourselves if missing
71// eslint-disable-next-line no-new-func
72var global = Function('return this')();
73var assert = void 0;
74
75// eslint-disable-next-line global-require
76var _ = global._ || require('lodash');
77
78// we can make this optional for browser use
79var jsDiff = void 0;
80// eslint-disable-next-line global-require
81try {
82 jsDiff = require('diff');
83} catch (err) {/* */}
84
85var toString = Object.prototype.toString;
86
87var green = function green(x) {
88 return '\x1B[32m' + x + '\x1B[39m';
89};
90var red = function red(x) {
91 return '\x1B[31m' + x + '\x1B[39m';
92};
93var clear = '\x1b[39;49;00m';
94
95if (global.process && process.isTTY && process.isTTY()) {
96 red = function red(x) {
97 return '' + x;
98 };
99 green = red;
100 clear = '';
101}
102
103function error(message, explanation) {
104 if (explanation != null) {
105 message = 'Assertion failed: ' + explanation + '\n' + clear + message;
106 }
107 return new Error(message);
108}
109
110function nameNegative(name) {
111 if (name === 'truthy') {
112 return 'falsey';
113 }
114 if (name === 'resolves') {
115 return 'rejects';
116 }
117 return 'not' + name.charAt().toUpperCase() + name.slice(1);
118}
119
120function asRegExp(re) {
121 var flags = '';
122 if (re.global) flags += 'g';
123 if (re.multiline) flags += 'm';
124 if (re.ignoreCase) flags += 'i';
125 return '/' + re.source + '/' + flags;
126}
127
128function stringifyReplacer(key, val) {
129 if (typeof val === 'function') return toString(val);
130 if (_.isRegExp(val)) return asRegExp(val);
131 if (_.isObject(val) && !_.isArray(val)) {
132 return _(val).toPairs().sortBy(0).fromPairs().value();
133 }
134 return val;
135}
136
137function stringify(x) {
138 if (x == null) return '' + x;
139 if (_.isNaN(x)) return 'NaN';
140 if (_.isRegExp(x)) return asRegExp(x);
141 if ((typeof x === 'undefined' ? 'undefined' : _typeof(x)) === 'symbol') return x.toString();
142 var json = JSON.stringify(x, stringifyReplacer, 2);
143 var className = x && x.constructor && x.constructor.name;
144 if ((typeof x === 'undefined' ? 'undefined' : _typeof(x)) !== 'object' || className === 'Object' || className === 'Array') {
145 return json;
146 }
147
148 if (x instanceof Error || /Error/.test(className)) {
149 if (json === '{}') {
150 return x.stack;
151 }
152 return x.stack + '\nwith error metadata:\n' + json;
153 }
154 if (x.toString === toString) {
155 return className;
156 }
157 try {
158 return className + '[' + x + ']';
159 } catch (e) {
160 return className;
161 }
162}
163
164// assert that the function got `count` args (if an integer), one of the number
165// of args (if an array of legal counts), and if it was an array and the count
166// was equal to the last option (fully populated), that the first arg is a String
167// (that test's semantic explanation)
168function handleArgs(self, count, args, name, help) {
169 var negated = false;
170 if (_.isString(self)) {
171 negated = true;
172 name = nameNegative(name);
173 }
174
175 var argc = args.length;
176 if (argc === count) return [name, negated];
177
178 var max = '';
179 if (_.isArray(count) && count.indexOf(argc) !== -1) {
180 var n = count[count.length - 1];
181 if (argc !== n || _.isString(args[0])) return [name, negated];
182 max = ',\nand when called with ' + n + ' args, the first arg must be a docstring';
183 }
184
185 var wantedArgCount = void 0;
186 if (_.isNumber(count)) {
187 wantedArgCount = count + ' argument';
188 } else {
189 wantedArgCount = count.slice(0, -1).join(', ');
190 count = count.pop();
191 wantedArgCount = wantedArgCount + ' or ' + count + ' argument';
192 }
193 if (count !== 1) wantedArgCount += 's';
194
195 var actualArgs = stringify([].slice.call(args)).slice(1, -1);
196
197 var functionSource = Function.prototype.toString.call(assert[name]);
198 var wantedArgNames = functionSource.match(/^function\s*[^(]*\s*\(([^)]*)/)[1];
199 if (max) {
200 wantedArgNames = 'explanation, ' + wantedArgNames;
201 }
202
203 var wanted = name + '(' + wantedArgNames + ')';
204 var actual = name + '(' + actualArgs + ')';
205 var message = green(wanted) + ' needs ' + (wantedArgCount + max) + '\nyour usage: ' + red(actual);
206
207 if (typeof help === 'function') {
208 help = help();
209 }
210 throw error(message, help);
211}
212
213function type(x) {
214 if (_.isString(x)) return 'String';
215 if (_.isNumber(x)) return 'Number';
216 if (_.isRegExp(x)) return 'RegExp';
217 if (_.isArray(x)) return 'Array';
218 throw new TypeError('unsupported type: ' + x);
219}
220
221function abbreviate(name, value, threshold) {
222 var str = stringify(value);
223 if (str.length <= (threshold || 1024)) return str;
224 var desc = 'length: ' + value.length;
225 if (_.isArray(value)) desc += '; ' + str.length + ' JSON encoded';
226 if (name) name += ' ';
227 return '' + name + type(value) + '[' + desc + ']';
228}
229
230// translates any argument we were meant to interpret as a type, into its name
231function getNameOfType(x) {
232 switch (false) {
233 case !(x == null):
234 return '' + x; // null / undefined
235 case !_.isString(x):
236 return x;
237 case !_.isFunction(x):
238 return x.name;
239 case !_.isNaN(x):
240 return 'NaN';
241 default:
242 return x;
243 }
244}
245
246// listing the most specific types first lets us iterate in order and verify that
247// the expected type was the first match
248var types = ['null', 'Date', 'Array', 'String', 'RegExp', 'Boolean', 'Function', 'Object', 'NaN', 'Number', 'undefined'];
249
250function implodeNicely(list, conjunction) {
251 var first = list.slice(0, -1).join(', ');
252 var last = list[list.length - 1];
253 return first + ' ' + (conjunction || 'and') + ' ' + last;
254}
255
256function isType(value, typeName) {
257 if (typeName === 'Date') return _.isDate(value) && !_.isNaN(+value);
258 return _['is' + typeName[0].toUpperCase() + typeName.slice(1)](value);
259}
260
261// gets the name of the type that value is an incarnation of
262function getTypeName(value) {
263 return _.find(types, _.partial(isType, value));
264}
265
266/* eslint-disable prefer-rest-params */
267var assertSync = {
268 truthy: function truthy(bool) {
269 var args = handleArgs(this, [1, 2], arguments, 'truthy');
270 var name = args[0];
271 var negated = args[1];
272 var explanation = void 0;
273 if (arguments.length === 2) {
274 explanation = arguments[0];
275 bool = arguments[1];
276 }
277 if (!bool && !negated || bool && negated) {
278 throw error('Expected ' + red(stringify(bool)) + ' to be ' + name, explanation);
279 }
280 },
281 expect: function expect(bool) {
282 var explanation = void 0;
283 if (arguments.length === 2) {
284 explanation = arguments[0];
285 bool = arguments[1];
286 }
287 if (explanation) return assertSync.equal(explanation, true, bool);
288 return assertSync.equal(true, bool);
289 },
290 equal: function equal(expected, actual) {
291 var explanation = void 0;
292 var negated = handleArgs(this, [2, 3], arguments, 'equal')[1];
293 if (arguments.length === 3) {
294 explanation = arguments[0];
295 expected = arguments[1];
296 actual = arguments[2];
297 }
298 if (negated) {
299 if (expected === actual) {
300 throw error('notEqual assertion expected ' + red(stringify(actual)) + ' to be exactly anything else', explanation);
301 }
302 } else if (expected !== actual) {
303 throw error('Expected: ' + green(stringify(expected)) + '\nActually: ' + ('' + red(stringify(actual))), explanation);
304 }
305 },
306 deepEqual: function deepEqual(expected, actual) {
307 var explanation = void 0;
308 var negated = handleArgs(this, [2, 3], arguments, 'deepEqual')[1];
309 if (arguments.length === 3) {
310 explanation = arguments[0];
311 expected = arguments[1];
312 actual = arguments[2];
313 }
314 var isEqual = _.isEqual(expected, actual);
315 if (isEqual && !negated || !isEqual && negated) return;
316
317 var wrongLooks = stringify(actual);
318 if (negated) {
319 throw error('notDeepEqual assertion expected exactly anything else but\n' + red(wrongLooks), explanation);
320 }
321
322 var rightLooks = stringify(expected);
323 var message = void 0;
324 if (wrongLooks === rightLooks) {
325 message = 'deepEqual ' + green(rightLooks) + ' failed on something that\n' + 'serializes to the same result (likely some function)';
326 } else if (jsDiff) {
327 var values = jsDiff.diffJson(actual, expected).map(function (entry) {
328 var value = entry.value;
329 var prefix = ' ';
330 if (entry.added) prefix = '+ ';else if (entry.removed) prefix = '- ';
331 value = value.replace(/^/gm, prefix).replace(/\n..$/, '');
332 if (entry.added) value = green(value);else if (entry.removed) value = red(value);
333 return value;
334 });
335 message = 'Actual: ' + red('-') + ' Expected: ' + green('+') + '\n' + values.join('\n');
336 } else {
337 message = 'Expected: ' + green(rightLooks) + '\n Actually: ' + ('' + red(wrongLooks));
338 }
339
340 throw error(message, explanation);
341 },
342 include: function include(needle, haystack) {
343 var args = handleArgs(this, [2, 3], arguments, 'include');
344 var name = args[0];
345 var negated = args[1];
346 var explanation = void 0;
347 if (arguments.length === 3) {
348 explanation = arguments[0];
349 needle = arguments[1];
350 haystack = arguments[2];
351 }
352 if (_.isString(haystack)) {
353 if (needle === '') {
354 var what = negated ? 'always-failing test' : 'no-op test';
355 throw error(what + ' detected: all strings contain the empty string!');
356 }
357 if (!_.isString(needle) && !_.isNumber(needle) && !_.isRegExp(needle)) {
358 var problem = 'needs a RegExp/String/Number needle for a String haystack';
359 throw new TypeError(name + ' ' + problem + '; you used:\n' + (name + ' ' + green(stringify(haystack)) + ', ' + red(stringify(needle))));
360 }
361 } else if (!_.isArray(haystack)) {
362 needle = stringify(needle);
363 throw new TypeError(name + ' takes a String or Array haystack; you used:\n' + name + ' ' + red(stringify(haystack)) + ', ' + needle);
364 }
365
366 var contained = void 0;
367 if (_.isString(haystack)) {
368 if (_.isRegExp(needle)) {
369 contained = haystack.match(needle);
370 } else {
371 contained = haystack.indexOf(needle) !== -1;
372 }
373 } else {
374 contained = _.includes(haystack, needle);
375 }
376
377 if (negated) {
378 if (contained) {
379 // eslint-disable-next-line prefer-template
380 var message = 'notInclude expected needle not to be found in ' + ('haystack\n- needle: ' + stringify(needle) + '\n haystack: ') + abbreviate('', haystack);
381 if (_.isString(haystack) && _.isRegExp(needle)) {
382 message += ', but found:\n';
383 if (needle.global) {
384 message += contained.map(function (s) {
385 return '* ' + red(stringify(s));
386 }).join('\n');
387 } else {
388 message += '* ' + red(stringify(contained[0]));
389 }
390 }
391 throw error(message, explanation);
392 }
393 } else if (!contained) {
394 throw error(name + ' expected needle to be found in haystack\n' + ('- needle: ' + stringify(needle) + '\n') + ('haystack: ' + abbreviate('', haystack)), explanation);
395 }
396 },
397 match: function match(regexp, string) {
398 var args = handleArgs(this, [2, 3], arguments, 'match');
399 var name = args[0];
400 var negated = args[1];
401 var explanation = void 0;
402 if (arguments.length === 3) {
403 explanation = arguments[0];
404 regexp = arguments[1];
405 string = arguments[2];
406 }
407
408 var re = _.isRegExp(regexp);
409 if (!re || !_.isString(string)) {
410 string = abbreviate('string', string);
411 var oops = re ? 'string arg is not a String' : 'regexp arg is not a RegExp';
412 var called = name + ' ' + stringify(regexp) + ', ' + red(string);
413 throw new TypeError(name + ': ' + oops + '; you used:\n' + called);
414 }
415
416 var matched = regexp.test(string);
417 if (negated) {
418 if (!matched) return;
419 var message = 'Expected: ' + stringify(regexp) + '\nnot to match: ' + ('' + red(abbreviate('string', string)));
420 if (regexp.global) {
421 var count = string.match(regexp).length;
422 message += '\nMatches: ' + red(count);
423 }
424 throw error(message, explanation);
425 }
426 if (!matched) {
427 throw error('Expected: ' + stringify(regexp) + '\nto match: ' + ('' + red(abbreviate('string', string))), explanation);
428 }
429 },
430 throws: function throws(fn) {
431 var args = handleArgs(this, [1, 2], arguments, 'throws');
432 var name = args[0];
433 var negated = args[1];
434 var explanation = void 0;
435 if (arguments.length === 2) {
436 explanation = arguments[0];
437 fn = arguments[1];
438 }
439 if (typeof explanation === 'function') {
440 fn = explanation;
441 explanation = undefined;
442 }
443 if (typeof fn !== 'function') {
444 throw error(name + ' expects ' + green('a function') + ' but got ' + red(fn));
445 }
446
447 try {
448 fn();
449 } catch (err) {
450 if (negated) {
451 throw error('Threw an exception despite ' + name + ' assertion:\n' + ('' + err.message), explanation);
452 }
453 return err;
454 }
455
456 if (negated) return undefined;
457 throw error("Didn't throw an exception as expected to", explanation);
458 },
459 hasType: function hasType(expectedType, value) {
460 var args = handleArgs(this, [2, 3], arguments, 'hasType');
461 var name = args[0];
462 var negated = args[1];
463 var explanation = void 0;
464 if (arguments.length === 3) {
465 explanation = arguments[0];
466 expectedType = arguments[1];
467 value = arguments[2];
468 }
469
470 var stringType = getNameOfType(expectedType);
471 if (types.indexOf(stringType) === -1) {
472 var badArg = stringify(expectedType);
473 var suggestions = implodeNicely(types, 'or');
474 throw new TypeError(name + ': unknown expectedType ' + badArg + '; you used:\n' + name + ' ' + (red(badArg) + ', ' + stringify(value) + '\nDid you mean ' + suggestions + '?'));
475 }
476
477 var typeMatches = stringType === getTypeName(value);
478 if (!typeMatches && !negated || typeMatches && negated) {
479 value = red(stringify(value));
480 var toBeOrNotToBe = (negated ? 'not ' : '') + 'to be';
481 var message = 'Expected value ' + value + ' ' + toBeOrNotToBe + ' of type ' + stringType;
482 throw error(message, explanation);
483 }
484 }
485};
486
487// produce negatived versions of all the common assertion functions
488var positiveAssertions = ['truthy', 'equal', 'deepEqual', 'include', 'match', 'throws', 'hasType'];
489positiveAssertions.forEach(function (name) {
490 assertSync[nameNegative(name)] = function _oneTest() {
491 return assertSync[name].apply('!', arguments);
492 };
493});
494
495// borrowed from Q
496function isPromiseAlike(p) {
497 return p === Object(p) && typeof p.then === 'function';
498}
499
500// promise-specific tests
501assert = {
502 resolves: function resolves(testee) {
503 var name = handleArgs(this, [1, 2], arguments, 'resolves')[0];
504 var explanation = void 0;
505 if (arguments.length === 2) {
506 explanation = arguments[0];
507 testee = arguments[1];
508 }
509
510 if (!isPromiseAlike(testee)) {
511 throw error(name + ' expects ' + green('a promise') + ' but got ' + red(stringify(testee)));
512 }
513
514 if (name === 'rejects') {
515 return testee.then(function () {
516 throw error("Promise wasn't rejected as expected to", explanation);
517 }, _.identity);
518 }
519 return testee.catch(function (err) {
520 throw error('Promise was rejected despite resolves assertion:\n' + ('' + (err && err.message || err)), explanation);
521 });
522 },
523 rejects: function rejects() {
524 return assert.resolves.apply('!', arguments);
525 }
526};
527
528// union of promise-specific and promise-aware wrapped synchronous tests
529_.forEach(assertSync || {}, function (fn, name) {
530 assert[name] = function _oneTest() {
531 if (arguments.length === 0) return fn();
532 var args = [].slice.call(arguments);
533 var testee = args.pop();
534 if (isPromiseAlike(testee)) {
535 return testee.then(function (val) {
536 return fn.apply(undefined, _toConsumableArray(args).concat([val]));
537 });
538 }
539 return fn.apply(undefined, _toConsumableArray(args).concat([testee]));
540 };
541});
542
543// export as a module to node - or to the global scope, if not
544if (typeof module !== 'undefined' && module && module.exports) {
545 module.exports = assert;
546} else {
547 global.assert = assert;
548}
549//# sourceMappingURL=assertive.js.map
\No newline at end of file