1 | 'use strict';
|
2 | const concordance = require('concordance');
|
3 | const isError = require('is-error');
|
4 | const isPromise = require('is-promise');
|
5 | const concordanceOptions = require('./concordance-options').default;
|
6 | const snapshotManager = require('./snapshot-manager');
|
7 |
|
8 | function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) {
|
9 | options = {...options, ...concordanceOptions};
|
10 | return {
|
11 | label: 'Difference:',
|
12 | formatted: concordance.diffDescriptors(actualDescriptor, expectedDescriptor, options)
|
13 | };
|
14 | }
|
15 |
|
16 | function formatDescriptorWithLabel(label, descriptor) {
|
17 | return {
|
18 | label,
|
19 | formatted: concordance.formatDescriptor(descriptor, concordanceOptions)
|
20 | };
|
21 | }
|
22 |
|
23 | function formatWithLabel(label, value) {
|
24 | return formatDescriptorWithLabel(label, concordance.describe(value, concordanceOptions));
|
25 | }
|
26 |
|
27 | function formatPowerAssertValue(value) {
|
28 | return concordance.format(value, concordanceOptions);
|
29 | }
|
30 |
|
31 | const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop);
|
32 | const noop = () => {};
|
33 | const notImplemented = () => {
|
34 | throw new Error('not implemented');
|
35 | };
|
36 |
|
37 | class AssertionError extends Error {
|
38 | constructor(options) {
|
39 | super(options.message || '');
|
40 | this.name = 'AssertionError';
|
41 |
|
42 | this.assertion = options.assertion;
|
43 | this.fixedSource = options.fixedSource;
|
44 | this.improperUsage = options.improperUsage || false;
|
45 | this.actualStack = options.actualStack;
|
46 | this.operator = options.operator;
|
47 | this.values = options.values || [];
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | this.raw = options.raw;
|
53 |
|
54 |
|
55 | this.statements = [];
|
56 |
|
57 | if (options.savedError) {
|
58 | this.savedError = options.savedError;
|
59 | } else {
|
60 | this.savedError = getErrorWithLongStackTrace();
|
61 | }
|
62 | }
|
63 | }
|
64 | exports.AssertionError = AssertionError;
|
65 |
|
66 | function getErrorWithLongStackTrace() {
|
67 | const limitBefore = Error.stackTraceLimit;
|
68 | Error.stackTraceLimit = Infinity;
|
69 | const err = new Error();
|
70 | Error.stackTraceLimit = limitBefore;
|
71 | return err;
|
72 | }
|
73 |
|
74 | function validateExpectations(assertion, expectations, numberArgs) {
|
75 | if (numberArgs === 1 || expectations === null || expectations === undefined) {
|
76 | expectations = {};
|
77 | } else if (
|
78 | typeof expectations === 'function' ||
|
79 | typeof expectations === 'string' ||
|
80 | expectations instanceof RegExp ||
|
81 | typeof expectations !== 'object' ||
|
82 | Array.isArray(expectations) ||
|
83 | Object.keys(expectations).length === 0
|
84 | ) {
|
85 | throw new AssertionError({
|
86 | assertion,
|
87 | message: `The second argument to \`t.${assertion}()\` must be an expectation object, \`null\` or \`undefined\``,
|
88 | values: [formatWithLabel('Called with:', expectations)]
|
89 | });
|
90 | } else {
|
91 | if (hasOwnProperty(expectations, 'instanceOf') && typeof expectations.instanceOf !== 'function') {
|
92 | throw new AssertionError({
|
93 | assertion,
|
94 | message: `The \`instanceOf\` property of the second argument to \`t.${assertion}()\` must be a function`,
|
95 | values: [formatWithLabel('Called with:', expectations)]
|
96 | });
|
97 | }
|
98 |
|
99 | if (hasOwnProperty(expectations, 'message') && typeof expectations.message !== 'string' && !(expectations.message instanceof RegExp)) {
|
100 | throw new AssertionError({
|
101 | assertion,
|
102 | message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string or regular expression`,
|
103 | values: [formatWithLabel('Called with:', expectations)]
|
104 | });
|
105 | }
|
106 |
|
107 | if (hasOwnProperty(expectations, 'name') && typeof expectations.name !== 'string') {
|
108 | throw new AssertionError({
|
109 | assertion,
|
110 | message: `The \`name\` property of the second argument to \`t.${assertion}()\` must be a string`,
|
111 | values: [formatWithLabel('Called with:', expectations)]
|
112 | });
|
113 | }
|
114 |
|
115 | if (hasOwnProperty(expectations, 'code') && typeof expectations.code !== 'string' && typeof expectations.code !== 'number') {
|
116 | throw new AssertionError({
|
117 | assertion,
|
118 | message: `The \`code\` property of the second argument to \`t.${assertion}()\` must be a string or number`,
|
119 | values: [formatWithLabel('Called with:', expectations)]
|
120 | });
|
121 | }
|
122 |
|
123 | for (const key of Object.keys(expectations)) {
|
124 | switch (key) {
|
125 | case 'instanceOf':
|
126 | case 'is':
|
127 | case 'message':
|
128 | case 'name':
|
129 | case 'code':
|
130 | continue;
|
131 | default:
|
132 | throw new AssertionError({
|
133 | assertion,
|
134 | message: `The second argument to \`t.${assertion}()\` contains unexpected properties`,
|
135 | values: [formatWithLabel('Called with:', expectations)]
|
136 | });
|
137 | }
|
138 | }
|
139 | }
|
140 |
|
141 | return expectations;
|
142 | }
|
143 |
|
144 |
|
145 |
|
146 | function assertExpectations({assertion, actual, expectations, message, prefix, savedError}) {
|
147 | if (!isError(actual)) {
|
148 | throw new AssertionError({
|
149 | assertion,
|
150 | message,
|
151 | savedError,
|
152 | values: [formatWithLabel(`${prefix} exception that is not an error:`, actual)]
|
153 | });
|
154 | }
|
155 |
|
156 | const actualStack = actual.stack;
|
157 |
|
158 | if (hasOwnProperty(expectations, 'is') && actual !== expectations.is) {
|
159 | throw new AssertionError({
|
160 | assertion,
|
161 | message,
|
162 | savedError,
|
163 | actualStack,
|
164 | values: [
|
165 | formatWithLabel(`${prefix} unexpected exception:`, actual),
|
166 | formatWithLabel('Expected to be strictly equal to:', expectations.is)
|
167 | ]
|
168 | });
|
169 | }
|
170 |
|
171 | if (expectations.instanceOf && !(actual instanceof expectations.instanceOf)) {
|
172 | throw new AssertionError({
|
173 | assertion,
|
174 | message,
|
175 | savedError,
|
176 | actualStack,
|
177 | values: [
|
178 | formatWithLabel(`${prefix} unexpected exception:`, actual),
|
179 | formatWithLabel('Expected instance of:', expectations.instanceOf)
|
180 | ]
|
181 | });
|
182 | }
|
183 |
|
184 | if (typeof expectations.name === 'string' && actual.name !== expectations.name) {
|
185 | throw new AssertionError({
|
186 | assertion,
|
187 | message,
|
188 | savedError,
|
189 | actualStack,
|
190 | values: [
|
191 | formatWithLabel(`${prefix} unexpected exception:`, actual),
|
192 | formatWithLabel('Expected name to equal:', expectations.name)
|
193 | ]
|
194 | });
|
195 | }
|
196 |
|
197 | if (typeof expectations.message === 'string' && actual.message !== expectations.message) {
|
198 | throw new AssertionError({
|
199 | assertion,
|
200 | message,
|
201 | savedError,
|
202 | actualStack,
|
203 | values: [
|
204 | formatWithLabel(`${prefix} unexpected exception:`, actual),
|
205 | formatWithLabel('Expected message to equal:', expectations.message)
|
206 | ]
|
207 | });
|
208 | }
|
209 |
|
210 | if (expectations.message instanceof RegExp && !expectations.message.test(actual.message)) {
|
211 | throw new AssertionError({
|
212 | assertion,
|
213 | message,
|
214 | savedError,
|
215 | actualStack,
|
216 | values: [
|
217 | formatWithLabel(`${prefix} unexpected exception:`, actual),
|
218 | formatWithLabel('Expected message to match:', expectations.message)
|
219 | ]
|
220 | });
|
221 | }
|
222 |
|
223 | if (typeof expectations.code !== 'undefined' && actual.code !== expectations.code) {
|
224 | throw new AssertionError({
|
225 | assertion,
|
226 | message,
|
227 | savedError,
|
228 | actualStack,
|
229 | values: [
|
230 | formatWithLabel(`${prefix} unexpected exception:`, actual),
|
231 | formatWithLabel('Expected code to equal:', expectations.code)
|
232 | ]
|
233 | });
|
234 | }
|
235 | }
|
236 |
|
237 | class Assertions {
|
238 | constructor({
|
239 | pass = notImplemented,
|
240 | pending = notImplemented,
|
241 | fail = notImplemented,
|
242 | skip = notImplemented,
|
243 | compareWithSnapshot = notImplemented,
|
244 | powerAssert
|
245 | } = {}) {
|
246 | const withSkip = assertionFn => {
|
247 | assertionFn.skip = skip;
|
248 | return assertionFn;
|
249 | };
|
250 |
|
251 |
|
252 |
|
253 | const withPowerAssert = (pattern, assertionFn) => powerAssert.empower(assertionFn, {
|
254 | onError: event => {
|
255 | if (event.powerAssertContext) {
|
256 | event.error.statements = powerAssert.format(event.powerAssertContext, formatPowerAssertValue);
|
257 | }
|
258 |
|
259 | fail(event.error);
|
260 | },
|
261 | onSuccess: () => {
|
262 | pass();
|
263 | },
|
264 | bindReceiver: false,
|
265 | patterns: [pattern]
|
266 | });
|
267 |
|
268 | const checkMessage = (assertion, message, powerAssert = false) => {
|
269 | if (typeof message === 'undefined' || typeof message === 'string') {
|
270 | return true;
|
271 | }
|
272 |
|
273 | const error = new AssertionError({
|
274 | assertion,
|
275 | improperUsage: true,
|
276 | message: 'The assertion message must be a string',
|
277 | values: [formatWithLabel('Called with:', message)]
|
278 | });
|
279 |
|
280 | if (powerAssert) {
|
281 | throw error;
|
282 | }
|
283 |
|
284 | fail(error);
|
285 | return false;
|
286 | };
|
287 |
|
288 | this.pass = withSkip(() => {
|
289 | pass();
|
290 | });
|
291 |
|
292 | this.fail = withSkip(message => {
|
293 | if (!checkMessage('fail', message)) {
|
294 | return;
|
295 | }
|
296 |
|
297 | fail(new AssertionError({
|
298 | assertion: 'fail',
|
299 | message: message || 'Test failed via `t.fail()`'
|
300 | }));
|
301 | });
|
302 |
|
303 | this.is = withSkip((actual, expected, message) => {
|
304 | if (!checkMessage('is', message)) {
|
305 | return;
|
306 | }
|
307 |
|
308 | if (Object.is(actual, expected)) {
|
309 | pass();
|
310 | } else {
|
311 | const result = concordance.compare(actual, expected, concordanceOptions);
|
312 | const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
|
313 | const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
|
314 |
|
315 | if (result.pass) {
|
316 | fail(new AssertionError({
|
317 | assertion: 'is',
|
318 | message,
|
319 | raw: {actual, expected},
|
320 | values: [formatDescriptorWithLabel('Values are deeply equal to each other, but they are not the same:', actualDescriptor)]
|
321 | }));
|
322 | } else {
|
323 | fail(new AssertionError({
|
324 | assertion: 'is',
|
325 | message,
|
326 | raw: {actual, expected},
|
327 | values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)]
|
328 | }));
|
329 | }
|
330 | }
|
331 | });
|
332 |
|
333 | this.not = withSkip((actual, expected, message) => {
|
334 | if (!checkMessage('not', message)) {
|
335 | return;
|
336 | }
|
337 |
|
338 | if (Object.is(actual, expected)) {
|
339 | fail(new AssertionError({
|
340 | assertion: 'not',
|
341 | message,
|
342 | raw: {actual, expected},
|
343 | values: [formatWithLabel('Value is the same as:', actual)]
|
344 | }));
|
345 | } else {
|
346 | pass();
|
347 | }
|
348 | });
|
349 |
|
350 | this.deepEqual = withSkip((actual, expected, message) => {
|
351 | if (!checkMessage('deepEqual', message)) {
|
352 | return;
|
353 | }
|
354 |
|
355 | const result = concordance.compare(actual, expected, concordanceOptions);
|
356 | if (result.pass) {
|
357 | pass();
|
358 | } else {
|
359 | const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
|
360 | const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
|
361 | fail(new AssertionError({
|
362 | assertion: 'deepEqual',
|
363 | message,
|
364 | raw: {actual, expected},
|
365 | values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)]
|
366 | }));
|
367 | }
|
368 | });
|
369 |
|
370 | this.notDeepEqual = withSkip((actual, expected, message) => {
|
371 | if (!checkMessage('notDeepEqual', message)) {
|
372 | return;
|
373 | }
|
374 |
|
375 | const result = concordance.compare(actual, expected, concordanceOptions);
|
376 | if (result.pass) {
|
377 | const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
|
378 | fail(new AssertionError({
|
379 | assertion: 'notDeepEqual',
|
380 | message,
|
381 | raw: {actual, expected},
|
382 | values: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)]
|
383 | }));
|
384 | } else {
|
385 | pass();
|
386 | }
|
387 | });
|
388 |
|
389 | this.throws = withSkip((...args) => {
|
390 |
|
391 |
|
392 |
|
393 | let [fn, expectations, message] = args;
|
394 |
|
395 | if (!checkMessage('throws', message)) {
|
396 | return;
|
397 | }
|
398 |
|
399 | if (typeof fn !== 'function') {
|
400 | fail(new AssertionError({
|
401 | assertion: 'throws',
|
402 | improperUsage: true,
|
403 | message: '`t.throws()` must be called with a function',
|
404 | values: [formatWithLabel('Called with:', fn)]
|
405 | }));
|
406 | return;
|
407 | }
|
408 |
|
409 | try {
|
410 | expectations = validateExpectations('throws', expectations, args.length);
|
411 | } catch (error) {
|
412 | fail(error);
|
413 | return;
|
414 | }
|
415 |
|
416 | let retval;
|
417 | let actual = null;
|
418 | try {
|
419 | retval = fn();
|
420 | if (isPromise(retval)) {
|
421 |
|
422 | Promise.resolve(retval).catch(noop);
|
423 | fail(new AssertionError({
|
424 | assertion: 'throws',
|
425 | message,
|
426 | values: [formatWithLabel('Function returned a promise. Use `t.throwsAsync()` instead:', retval)]
|
427 | }));
|
428 | return;
|
429 | }
|
430 | } catch (error) {
|
431 | actual = error;
|
432 | }
|
433 |
|
434 | if (!actual) {
|
435 | fail(new AssertionError({
|
436 | assertion: 'throws',
|
437 | message,
|
438 | values: [formatWithLabel('Function returned:', retval)]
|
439 | }));
|
440 | return;
|
441 | }
|
442 |
|
443 | try {
|
444 | assertExpectations({
|
445 | assertion: 'throws',
|
446 | actual,
|
447 | expectations,
|
448 | message,
|
449 | prefix: 'Function threw'
|
450 | });
|
451 | pass();
|
452 | return actual;
|
453 | } catch (error) {
|
454 | fail(error);
|
455 | }
|
456 | });
|
457 |
|
458 | this.throwsAsync = withSkip((...args) => {
|
459 | let [thrower, expectations, message] = args;
|
460 |
|
461 | if (!checkMessage('throwsAsync', message)) {
|
462 | return Promise.resolve();
|
463 | }
|
464 |
|
465 | if (typeof thrower !== 'function' && !isPromise(thrower)) {
|
466 | fail(new AssertionError({
|
467 | assertion: 'throwsAsync',
|
468 | improperUsage: true,
|
469 | message: '`t.throwsAsync()` must be called with a function or promise',
|
470 | values: [formatWithLabel('Called with:', thrower)]
|
471 | }));
|
472 | return Promise.resolve();
|
473 | }
|
474 |
|
475 | try {
|
476 | expectations = validateExpectations('throwsAsync', expectations, args.length);
|
477 | } catch (error) {
|
478 | fail(error);
|
479 | return Promise.resolve();
|
480 | }
|
481 |
|
482 | const handlePromise = (promise, wasReturned) => {
|
483 |
|
484 | const savedError = getErrorWithLongStackTrace();
|
485 |
|
486 | const intermediate = Promise.resolve(promise).then(value => {
|
487 | throw new AssertionError({
|
488 | assertion: 'throwsAsync',
|
489 | message,
|
490 | savedError,
|
491 | values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} resolved with:`, value)]
|
492 | });
|
493 | }, error => {
|
494 | assertExpectations({
|
495 | assertion: 'throwsAsync',
|
496 | actual: error,
|
497 | expectations,
|
498 | message,
|
499 | prefix: `${wasReturned ? 'Returned promise' : 'Promise'} rejected with`,
|
500 | savedError
|
501 | });
|
502 | return error;
|
503 | });
|
504 |
|
505 | pending(intermediate);
|
506 |
|
507 | return intermediate.catch(noop);
|
508 | };
|
509 |
|
510 | if (isPromise(thrower)) {
|
511 | return handlePromise(thrower, false);
|
512 | }
|
513 |
|
514 | let retval;
|
515 | let actual = null;
|
516 | try {
|
517 | retval = thrower();
|
518 | } catch (error) {
|
519 | actual = error;
|
520 | }
|
521 |
|
522 | if (actual) {
|
523 | fail(new AssertionError({
|
524 | assertion: 'throwsAsync',
|
525 | message,
|
526 | actualStack: actual.stack,
|
527 | values: [formatWithLabel('Function threw synchronously. Use `t.throws()` instead:', actual)]
|
528 | }));
|
529 | return Promise.resolve();
|
530 | }
|
531 |
|
532 | if (isPromise(retval)) {
|
533 | return handlePromise(retval, true);
|
534 | }
|
535 |
|
536 | fail(new AssertionError({
|
537 | assertion: 'throwsAsync',
|
538 | message,
|
539 | values: [formatWithLabel('Function returned:', retval)]
|
540 | }));
|
541 | return Promise.resolve();
|
542 | });
|
543 |
|
544 | this.notThrows = withSkip((fn, message) => {
|
545 | if (!checkMessage('notThrows', message)) {
|
546 | return;
|
547 | }
|
548 |
|
549 | if (typeof fn !== 'function') {
|
550 | fail(new AssertionError({
|
551 | assertion: 'notThrows',
|
552 | improperUsage: true,
|
553 | message: '`t.notThrows()` must be called with a function',
|
554 | values: [formatWithLabel('Called with:', fn)]
|
555 | }));
|
556 | return;
|
557 | }
|
558 |
|
559 | try {
|
560 | fn();
|
561 | } catch (error) {
|
562 | fail(new AssertionError({
|
563 | assertion: 'notThrows',
|
564 | message,
|
565 | actualStack: error.stack,
|
566 | values: [formatWithLabel('Function threw:', error)]
|
567 | }));
|
568 | return;
|
569 | }
|
570 |
|
571 | pass();
|
572 | });
|
573 |
|
574 | this.notThrowsAsync = withSkip((nonThrower, message) => {
|
575 | if (!checkMessage('notThrowsAsync', message)) {
|
576 | return Promise.resolve();
|
577 | }
|
578 |
|
579 | if (typeof nonThrower !== 'function' && !isPromise(nonThrower)) {
|
580 | fail(new AssertionError({
|
581 | assertion: 'notThrowsAsync',
|
582 | improperUsage: true,
|
583 | message: '`t.notThrowsAsync()` must be called with a function or promise',
|
584 | values: [formatWithLabel('Called with:', nonThrower)]
|
585 | }));
|
586 | return Promise.resolve();
|
587 | }
|
588 |
|
589 | const handlePromise = (promise, wasReturned) => {
|
590 |
|
591 | const savedError = getErrorWithLongStackTrace();
|
592 |
|
593 | const intermediate = Promise.resolve(promise).then(noop, error => {
|
594 | throw new AssertionError({
|
595 | assertion: 'notThrowsAsync',
|
596 | message,
|
597 | savedError,
|
598 | values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, error)]
|
599 | });
|
600 | });
|
601 | pending(intermediate);
|
602 |
|
603 | return intermediate.catch(noop);
|
604 | };
|
605 |
|
606 | if (isPromise(nonThrower)) {
|
607 | return handlePromise(nonThrower, false);
|
608 | }
|
609 |
|
610 | let retval;
|
611 | try {
|
612 | retval = nonThrower();
|
613 | } catch (error) {
|
614 | fail(new AssertionError({
|
615 | assertion: 'notThrowsAsync',
|
616 | message,
|
617 | actualStack: error.stack,
|
618 | values: [formatWithLabel('Function threw:', error)]
|
619 | }));
|
620 | return Promise.resolve();
|
621 | }
|
622 |
|
623 | if (!isPromise(retval)) {
|
624 | fail(new AssertionError({
|
625 | assertion: 'notThrowsAsync',
|
626 | message,
|
627 | values: [formatWithLabel('Function did not return a promise. Use `t.notThrows()` instead:', retval)]
|
628 | }));
|
629 | return Promise.resolve();
|
630 | }
|
631 |
|
632 | return handlePromise(retval, true);
|
633 | });
|
634 |
|
635 | this.snapshot = withSkip((expected, ...rest) => {
|
636 | let message;
|
637 | let snapshotOptions;
|
638 | if (rest.length > 1) {
|
639 | [snapshotOptions, message] = rest;
|
640 | } else {
|
641 | const [optionsOrMessage] = rest;
|
642 | if (typeof optionsOrMessage === 'object') {
|
643 | snapshotOptions = optionsOrMessage;
|
644 | } else {
|
645 | message = optionsOrMessage;
|
646 | }
|
647 | }
|
648 |
|
649 | if (!checkMessage('snapshot', message)) {
|
650 | return;
|
651 | }
|
652 |
|
653 | let result;
|
654 | try {
|
655 | result = compareWithSnapshot({
|
656 | expected,
|
657 | id: snapshotOptions ? snapshotOptions.id : undefined,
|
658 | message
|
659 | });
|
660 | } catch (error) {
|
661 | if (!(error instanceof snapshotManager.SnapshotError)) {
|
662 | throw error;
|
663 | }
|
664 |
|
665 | const improperUsage = {name: error.name, snapPath: error.snapPath};
|
666 | if (error instanceof snapshotManager.VersionMismatchError) {
|
667 | improperUsage.snapVersion = error.snapVersion;
|
668 | improperUsage.expectedVersion = error.expectedVersion;
|
669 | }
|
670 |
|
671 | fail(new AssertionError({
|
672 | assertion: 'snapshot',
|
673 | message: message || 'Could not compare snapshot',
|
674 | improperUsage
|
675 | }));
|
676 | return;
|
677 | }
|
678 |
|
679 | if (result.pass) {
|
680 | pass();
|
681 | } else if (result.actual) {
|
682 | fail(new AssertionError({
|
683 | assertion: 'snapshot',
|
684 | message: message || 'Did not match snapshot',
|
685 | values: [formatDescriptorDiff(result.actual, result.expected, {invert: true})]
|
686 | }));
|
687 | } else {
|
688 |
|
689 | fail(new AssertionError({
|
690 | assertion: 'snapshot',
|
691 | message: message || 'No snapshot available — new snapshots are not created in CI environments'
|
692 | }));
|
693 | }
|
694 | });
|
695 |
|
696 | this.truthy = withSkip((actual, message) => {
|
697 | if (!checkMessage('truthy', message)) {
|
698 | return;
|
699 | }
|
700 |
|
701 | if (actual) {
|
702 | pass();
|
703 | } else {
|
704 | fail(new AssertionError({
|
705 | assertion: 'truthy',
|
706 | message,
|
707 | operator: '!!',
|
708 | values: [formatWithLabel('Value is not truthy:', actual)]
|
709 | }));
|
710 | }
|
711 | });
|
712 |
|
713 | this.falsy = withSkip((actual, message) => {
|
714 | if (!checkMessage('falsy', message)) {
|
715 | return;
|
716 | }
|
717 |
|
718 | if (actual) {
|
719 | fail(new AssertionError({
|
720 | assertion: 'falsy',
|
721 | message,
|
722 | operator: '!',
|
723 | values: [formatWithLabel('Value is not falsy:', actual)]
|
724 | }));
|
725 | } else {
|
726 | pass();
|
727 | }
|
728 | });
|
729 |
|
730 | this.true = withSkip((actual, message) => {
|
731 | if (!checkMessage('true', message)) {
|
732 | return;
|
733 | }
|
734 |
|
735 | if (actual === true) {
|
736 | pass();
|
737 | } else {
|
738 | fail(new AssertionError({
|
739 | assertion: 'true',
|
740 | message,
|
741 | values: [formatWithLabel('Value is not `true`:', actual)]
|
742 | }));
|
743 | }
|
744 | });
|
745 |
|
746 | this.false = withSkip((actual, message) => {
|
747 | if (!checkMessage('false', message)) {
|
748 | return;
|
749 | }
|
750 |
|
751 | if (actual === false) {
|
752 | pass();
|
753 | } else {
|
754 | fail(new AssertionError({
|
755 | assertion: 'false',
|
756 | message,
|
757 | values: [formatWithLabel('Value is not `false`:', actual)]
|
758 | }));
|
759 | }
|
760 | });
|
761 |
|
762 | this.regex = withSkip((string, regex, message) => {
|
763 | if (!checkMessage('regex', message)) {
|
764 | return;
|
765 | }
|
766 |
|
767 | if (typeof string !== 'string') {
|
768 | fail(new AssertionError({
|
769 | assertion: 'regex',
|
770 | improperUsage: true,
|
771 | message: '`t.regex()` must be called with a string',
|
772 | values: [formatWithLabel('Called with:', string)]
|
773 | }));
|
774 | return;
|
775 | }
|
776 |
|
777 | if (!(regex instanceof RegExp)) {
|
778 | fail(new AssertionError({
|
779 | assertion: 'regex',
|
780 | improperUsage: true,
|
781 | message: '`t.regex()` must be called with a regular expression',
|
782 | values: [formatWithLabel('Called with:', regex)]
|
783 | }));
|
784 | return;
|
785 | }
|
786 |
|
787 | if (!regex.test(string)) {
|
788 | fail(new AssertionError({
|
789 | assertion: 'regex',
|
790 | message,
|
791 | values: [
|
792 | formatWithLabel('Value must match expression:', string),
|
793 | formatWithLabel('Regular expression:', regex)
|
794 | ]
|
795 | }));
|
796 | return;
|
797 | }
|
798 |
|
799 | pass();
|
800 | });
|
801 |
|
802 | this.notRegex = withSkip((string, regex, message) => {
|
803 | if (!checkMessage('notRegex', message)) {
|
804 | return;
|
805 | }
|
806 |
|
807 | if (typeof string !== 'string') {
|
808 | fail(new AssertionError({
|
809 | assertion: 'notRegex',
|
810 | improperUsage: true,
|
811 | message: '`t.notRegex()` must be called with a string',
|
812 | values: [formatWithLabel('Called with:', string)]
|
813 | }));
|
814 | return;
|
815 | }
|
816 |
|
817 | if (!(regex instanceof RegExp)) {
|
818 | fail(new AssertionError({
|
819 | assertion: 'notRegex',
|
820 | improperUsage: true,
|
821 | message: '`t.notRegex()` must be called with a regular expression',
|
822 | values: [formatWithLabel('Called with:', regex)]
|
823 | }));
|
824 | return;
|
825 | }
|
826 |
|
827 | if (regex.test(string)) {
|
828 | fail(new AssertionError({
|
829 | assertion: 'notRegex',
|
830 | message,
|
831 | values: [
|
832 | formatWithLabel('Value must not match expression:', string),
|
833 | formatWithLabel('Regular expression:', regex)
|
834 | ]
|
835 | }));
|
836 | return;
|
837 | }
|
838 |
|
839 | pass();
|
840 | });
|
841 |
|
842 | if (powerAssert === undefined) {
|
843 | this.assert = withSkip((actual, message) => {
|
844 | if (!checkMessage('assert', message)) {
|
845 | return;
|
846 | }
|
847 |
|
848 | if (!actual) {
|
849 | fail(new AssertionError({
|
850 | assertion: 'assert',
|
851 | message,
|
852 | operator: '!!',
|
853 | values: [formatWithLabel('Value is not truthy:', actual)]
|
854 | }));
|
855 | return;
|
856 | }
|
857 |
|
858 | pass();
|
859 | });
|
860 | } else {
|
861 | this.assert = withSkip(withPowerAssert(
|
862 | 'assert(value, [message])',
|
863 | (actual, message) => {
|
864 | checkMessage('assert', message, true);
|
865 |
|
866 | if (!actual) {
|
867 | throw new AssertionError({
|
868 | assertion: 'assert',
|
869 | message,
|
870 | operator: '!!',
|
871 | values: [formatWithLabel('Value is not truthy:', actual)]
|
872 | });
|
873 | }
|
874 | })
|
875 | );
|
876 | }
|
877 | }
|
878 | }
|
879 | exports.Assertions = Assertions;
|