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