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