UNPKG

12.2 kBPlain TextView Raw
1'use strict'
2
3// Polyfill for older node versions
4if (Array.prototype.includes == null) {
5 // eslint-disable-next-line no-extend-native
6 Array.prototype.includes = function (searchElement) {
7 return this.indexOf(searchElement) !== -1
8 }
9}
10
11// Import
12import {
13 ok as _ok,
14 strictEqual as _strictEqual,
15 deepStrictEqual as _deepStrictEqual,
16} from 'assert'
17import { inspect as _inspect } from 'util'
18import * as ansi from '@bevry/ansi'
19import Errlop from 'errlop'
20import { argv, env, stdout, stderr, versions } from 'process'
21
22/** Type for a callback that receives an optional error as the first argument */
23type Errback = (error?: Error) => void
24
25/** Alias for setTimeout with paramaters reversed. */
26export function wait(delay: number, fn: (...args: any[]) => void) {
27 // @ts-ignore
28 return setTimeout(fn, delay)
29}
30
31/** Whether or not we are running in the node environment */
32export function isNode(): boolean {
33 return Boolean(versions && versions.node)
34}
35
36/** Whether or not stdout and stderr are interactive. */
37export function isTTY(): boolean {
38 return (
39 isNode() &&
40 stdout &&
41 stdout.isTTY === true &&
42 stderr &&
43 stderr.isTTY === true
44 )
45}
46
47/** Convert a value to its boolean equivalent */
48export function bool(value: any): boolean | null {
49 if (value === 'no' || value === 'false' || value === 'n' || value === 'N') {
50 return false
51 }
52 if (value == null || value === '' || value === 'null' || value === 'NULL')
53 return null
54 // node.js 15 compatibility
55 // which sets COLOR to 0 (is TTY) 1 (not TTY)
56 // which is the opposite of what one would reasonable expect
57 // so ignore such pollution, such that decision is determined by another factor
58 if (value === '0') return null
59 if (value === '1') return null
60 // return boolean version
61 return Boolean(value)
62}
63
64/** Whether or not colors are desired on this environment */
65export function useColors(): boolean {
66 // if unsupported, return false
67 if (!isNode()) return false
68 // if disabled, return false
69 if (argv.includes('--no-colors') || argv.includes('--no-color')) return false
70 // if unspecified, use default (tty)
71 return bool(env.COLOR) ?? bool(env.COLORS) ?? isTTY()
72}
73
74/** Applies the color to the value if desired */
75export function color(value: any, color: Function): string {
76 return useColors() ? color(value) : value
77}
78
79/**
80 * Checks to see if a value is an object
81 * https://github.com/bevry/typechecker/blob/69008d42927749d7e21cfe9816e478dd8d15ab88/source/index.js#L22-L30
82 */
83function isObject(value: any): boolean {
84 // null is object, hence the extra check
85 return value !== null && typeof value === 'object'
86}
87
88/**
89 * Return a stringified version of the value with indentation and colors where applicable.
90 * Colors will be applied if the environment supports it (--no-colors not present and TTY).
91 * For the available options, refer to https://nodejs.org/dist/latest-v14.x/docs/api/util.html#util_util_inspect_object_options for Node.js
92 */
93export function inspect(value: any, opts: any = {}): string {
94 // If the terminal supports colours, and the user hasn't specified, then default to a sensible default
95 const colors = useColors()
96 const depth = 50
97
98 // Inspect and return using our defaults
99 return _inspect(value, { colors, depth, ...opts })
100}
101
102/** Log the inspected values of each of the arguments to stdout */
103export function log(...args: any): void {
104 if (isNode() && env.ASSERT_SILENCE) return
105 for (let i = 0; i < args.length; ++i) {
106 console.log(inspect(args[i]))
107 }
108}
109
110/** Output a comparison of the failed result to stderr */
111export function logComparison(
112 actual: any,
113 expected: any,
114 error: Error | string | any
115): void {
116 if (isNode() && env.ASSERT_SILENCE) return
117
118 const lines = [
119 '------------------------------------',
120 'Comparison Error:',
121 color(error.stack || error.message || error, ansi.green),
122 '',
123 ]
124
125 lines.push(
126 'Comparison Actual:',
127 inspect(actual),
128 '',
129 'Comparison Expected:',
130 inspect(expected),
131 '------------------------------------'
132 )
133
134 // Work for node
135 if (isNode() && stderr) {
136 stderr.write(lines.join('\n') + '\n')
137 }
138 // Work for browsers
139 else {
140 console.log(lines.join('\n'))
141 }
142}
143
144/** Same as assert.strictEqual in that it performs a strict equals check, but if a failure occurs it will output detailed information */
145export function equal(
146 actual: any,
147 expected: any,
148 testName = 'equal assertion',
149 next?: Errback
150): void | never {
151 try {
152 _strictEqual(actual, expected, testName)
153 } catch (checkError) {
154 logComparison(actual, expected, checkError)
155 if (next) {
156 next(checkError)
157 return
158 } else {
159 throw checkError
160 }
161 }
162 if (next) next()
163}
164
165/** Is greater than or equal to */
166export function gte(
167 actual: any,
168 expected: any,
169 testName = 'is greater than or equal to assertion',
170 next?: Errback
171): void | never {
172 try {
173 _strictEqual(actual >= expected, true, testName)
174 } catch (checkError) {
175 logComparison(actual, expected, checkError)
176 if (next) {
177 next(checkError)
178 return
179 } else {
180 throw checkError
181 }
182 }
183 if (next) next()
184}
185
186/** Is less than or equal to */
187export function lte(
188 actual: any,
189 expected: any,
190 testName = 'is less than or equal to assertion',
191 next?: Errback
192): void | never {
193 try {
194 _strictEqual(actual <= expected, true, testName)
195 } catch (checkError) {
196 logComparison(actual, expected, checkError)
197 if (next) {
198 next(checkError)
199 return
200 } else {
201 throw checkError
202 }
203 }
204 if (next) next()
205}
206
207/** Is greater than */
208export function gt(
209 actual: any,
210 expected: any,
211 testName = 'is greater than assertion',
212 next?: Errback
213): void | never {
214 try {
215 _strictEqual(actual > expected, true, testName)
216 } catch (checkError) {
217 logComparison(actual, expected, checkError)
218 if (next) {
219 next(checkError)
220 return
221 } else {
222 throw checkError
223 }
224 }
225 if (next) next()
226}
227
228/** Is less than */
229export function lt(
230 actual: any,
231 expected: any,
232 testName = 'is less than assertion',
233 next?: Errback
234): void | never {
235 try {
236 _strictEqual(actual < expected, true, testName)
237 } catch (checkError) {
238 logComparison(actual, expected, checkError)
239 if (next) {
240 next(checkError)
241 return
242 } else {
243 throw checkError
244 }
245 }
246 if (next) next()
247}
248
249/** Ensure what is passed is undefined, otherwise fail and output what it is */
250export function undef(
251 actual: any,
252 testName = 'undef assertion',
253 next?: Errback
254) {
255 try {
256 _strictEqual(typeof actual, 'undefined', testName)
257 } catch (checkError) {
258 logComparison(actual, 'undefined', checkError)
259 if (next) {
260 next(checkError)
261 return
262 } else {
263 throw checkError
264 }
265 }
266 if (next) next()
267}
268
269/** Ensure what is passed is undefined or null, otherwise fail and output what it is */
270export function nullish(
271 actual: any,
272 testName = 'nullish assertion',
273 next?: Errback
274) {
275 try {
276 _strictEqual(typeof actual, 'undefined', testName)
277 } catch (e1) {
278 try {
279 _strictEqual(actual, null, testName)
280 } catch (e2) {
281 const error = new Errlop(e2, e1)
282 logComparison(actual, 'nullish', error)
283 if (next) {
284 next(error)
285 return
286 } else {
287 throw error
288 }
289 }
290 }
291 if (next) next()
292}
293
294/** Same as assert.deepStrictEqual in that it performs a deep strict equals check, but if a failure occurs it will output detailed information */
295export function deepEqual(
296 actual: any,
297 expected: any,
298 testName = 'deep equal assertion',
299 next?: Errback
300): void | never {
301 try {
302 _deepStrictEqual(actual, expected, testName)
303 } catch (checkError) {
304 logComparison(actual, expected, checkError)
305 if (next) {
306 next(checkError)
307 return
308 } else {
309 throw checkError
310 }
311 }
312 if (next) next()
313}
314
315/** Checks to see if the actual result contains the expected result .*/
316export function contains(
317 actual: any,
318 expected: any,
319 testName = 'contains assertion',
320 next?: Errback
321): void | never {
322 if (testName == null)
323 testName = `Expected [${actual}] to contain [${expected}]`
324 try {
325 _ok(actual.indexOf(expected) !== -1, testName)
326 } catch (checkError) {
327 if (next) {
328 next(checkError)
329 return
330 } else {
331 throw checkError
332 }
333 }
334 if (next) next()
335}
336
337/** Checks to see if an error was as expected, if a failure occurs it will output detailed information */
338export function errorEqual(
339 actualError: any,
340 expectedError: any,
341 testName = 'error equal assertion',
342 next?: Errback
343): void | never {
344 let expectedErrorMessage, actualErrorMessage
345
346 if (expectedError) {
347 if (expectedError instanceof Error) {
348 expectedErrorMessage = expectedError.message
349 } else {
350 expectedErrorMessage = expectedError
351 expectedError = new Error(expectedErrorMessage)
352 }
353 }
354
355 if (actualError) {
356 if (actualError instanceof Error) {
357 actualErrorMessage = actualError.message
358 } else {
359 actualErrorMessage = actualError
360 actualError = new Error(actualErrorMessage)
361 }
362 }
363
364 try {
365 if (actualErrorMessage && expectedErrorMessage) {
366 contains(actualErrorMessage, expectedErrorMessage, testName)
367 } else {
368 equal(actualError, expectedError || null, testName)
369 }
370 } catch (checkError) {
371 logComparison(
372 actualError && (actualError.stack || actualError.message || actualError),
373 expectedErrorMessage,
374 checkError
375 )
376 if (next) {
377 next(checkError)
378 return
379 } else {
380 throw checkError
381 }
382 }
383 if (next) next()
384}
385
386/** Generate a callback that will return the specified value. */
387export function returnViaCallback(value: any): () => typeof value {
388 return function () {
389 return value
390 }
391}
392
393/** Generate a callback that will receive a completion callback whcih it will call with the specified result after the specified delay. */
394/* eslint no-magic-numbers:0 */
395export function completeViaCallback(value: any, delay = 100) {
396 return function (
397 complete: (error: null, result: typeof value) => void
398 ): void {
399 wait(delay, function () {
400 complete(null, value)
401 })
402 }
403}
404
405/** Generate a callback that will receive a completion callback which it will call with the passed error after the specified delay. */
406/* eslint no-magic-numbers:0 */
407export function errorViaCallback(error: Error | string, delay = 100) {
408 return function (complete: (error: Error | string) => void): void {
409 wait(delay, function () {
410 complete(error)
411 })
412 }
413}
414/** Generate a callback that return an error instance with the specified message/error. */
415export function returnErrorViaCallback(
416 error: Error | string = 'an error occured'
417) {
418 return function (): Error {
419 if (error instanceof Error) {
420 return error
421 } else {
422 return new Error(error)
423 }
424 }
425}
426
427/** Generate a callback that throw an error instance with the specified message/error. */
428export function throwErrorViaCallback(
429 error: Error | string = 'an error occured'
430) {
431 return function (): never {
432 if (error instanceof Error) {
433 throw error
434 } else {
435 throw new Error(error)
436 }
437 }
438}
439
440/** Generate a callback that will check the arguments it received with the arguments specified, if a failure occurs it will output detailed information. */
441export function expectViaCallback(...expected: any) {
442 return (...actual: any) => deepEqual(actual, expected)
443}
444
445/**
446 * Generate a callback that will check its error (the actual error) against the passed error (the expected error).
447 * If a failure occurs it will output detailed information. */
448export function expectErrorViaCallback(
449 expected: Error | string,
450 testName = 'expect error via callback assertion',
451 next?: Errback
452) {
453 return (actual: Error | string) =>
454 errorEqual(actual, expected, testName, next)
455}
456
457/** Expect the passed function to throw an error at some point. */
458export function expectThrowViaFunction(
459 expected: Error | string,
460 fn: () => never,
461 testName = 'expect error via function assertion',
462 next?: Errback
463) {
464 let actual = null
465 try {
466 fn()
467 } catch (error) {
468 actual = error
469 }
470 errorEqual(actual, expected, testName, next)
471}
472
473/** @deprecated Use {@link expectErrorViaFunction} instead */
474export function expectErrorViaFunction(): never {
475 throw new Error(
476 'expectErrorViaFunction has been deprecated, use expectThrowViaFunction instead'
477 )
478}
479
480/** @deprecated Use {@link expectErrorViaFunction} instead */
481export function expectFunctionToThrow(): never {
482 throw new Error(
483 'expectFunctionToThrow has been deprecated, use expectThrowViaFunction instead'
484 )
485}