@samitouri / QOS-React / commits / 608edcc90a

[tests] add `assertConsole<method>Dev` helpers (#28732)

## Overview **Internal React repo tests only** Depends on https://github.com/facebook/react/pull/28710 Adds three new assertions: - `assertConsoleLogDev` - `assertConsoleWarnDev` - `assertConsoleErrorDev` These will replace this pattern: ```js await expect(async () => { await expect(async () => { await act(() => { root.render(<Fail />) }); }).toThrow(); }).toWarnDev('Warning'); ``` With this: ```js await expect(async () => { await act(() => { root.render(<Fail />) }); }).toThrow(); assertConsoleWarnDev('Warning'); ``` It works similar to our other `assertLog` matchers which clear the log and assert on it, failing the tests if the log is not asserted before the test ends. ## Diffs There are a few improvements I also added including better log diffs and more logging. When there's a failure, the output will look something like: <img width="655" alt="Screenshot 2024-04-03 at 11 50 08 AM" src="https://github.com/facebook/react/assets/2440089/0c4bf1b2-5f63-4204-8af3-09e0c2d752ad"> Check out the test suite for snapshots of all the failures we may log.

Ricky committed Apr 11, 2024 at 08:19 UTC 608edcc90a9e1dcee896c6c93cb381d0a61aac37
6 files changed +2881 -16
packages/internal-test-utils/ReactInternalTestUtils.js
+24 -1
@@ -10,8 +10,14 @@ import {diff} from 'jest-diff';
10 import {equals} from '@jest/expect-utils';
11 import enqueueTask from './enqueueTask';
12 import simulateBrowserEventDispatch from './simulateBrowserEventDispatch';
13 -
13 +import {
14 + clearLogs,
15 + clearWarnings,
16 + clearErrors,
17 + createLogAssertion,
18 +} from './consoleMock';
19 export {act} from './internalAct';
20 +const {assertConsoleLogsCleared} = require('internal-test-utils/consoleMock');
21
22 import {thrownErrors, actingUpdatesScopeDepth} from './internalAct';
23
@@ -24,6 +30,7 @@ function assertYieldsWereCleared(caller) {
30 Error.captureStackTrace(error, caller);
31 throw error;
32 }
33 + assertConsoleLogsCleared();
34 }
35
36 export async function waitForMicrotasks() {
@@ -317,6 +324,22 @@ ${diff(expectedLog, actualLog)}
324 throw error;
325 }
326
327 +export const assertConsoleLogDev = createLogAssertion(
328 + 'log',
329 + 'assertConsoleLogDev',
330 + clearLogs,
331 +);
332 +export const assertConsoleWarnDev = createLogAssertion(
333 + 'warn',
334 + 'assertConsoleWarnDev',
335 + clearWarnings,
336 +);
337 +export const assertConsoleErrorDev = createLogAssertion(
338 + 'error',
339 + 'assertConsoleErrorDev',
340 + clearErrors,
341 +);
342 +
343 // Simulates dispatching events, waiting for microtasks in between.
344 // This matches the browser behavior, which will flush microtasks
345 // between each event handler. This will allow discrete events to
packages/internal-test-utils/__tests__/ReactInternalTestUtils-test.js
+2417
@@ -11,6 +11,7 @@
11 'use strict';
12
13 const React = require('react');
14 +const stripAnsi = require('strip-ansi');
15 const {startTransition, useDeferredValue} = React;
16 const chalk = require('chalk');
17 const ReactNoop = require('react-noop-renderer');
@@ -28,6 +29,11 @@ const {
29 resetAllUnexpectedConsoleCalls,
30 patchConsoleMethods,
31 } = require('../consoleMock');
32 +const {
33 + assertConsoleLogDev,
34 + assertConsoleWarnDev,
35 + assertConsoleErrorDev,
36 +} = require('../ReactInternalTestUtils');
37
38 describe('ReactInternalTestUtils', () => {
39 test('waitFor', async () => {
@@ -301,3 +307,2414 @@ describe('ReactInternalTestUtils console mocks', () => {
307 });
308 });
309 });
310 +
311 +// Helper method to capture assertion failure.
312 +const expectToThrowFailure = expectBlock => {
313 + let caughtError;
314 + try {
315 + expectBlock();
316 + } catch (error) {
317 + caughtError = error;
318 + }
319 + expect(caughtError).toBeDefined();
320 + return stripAnsi(caughtError.message);
321 +};
322 +
323 +// Helper method to capture assertion failure with act.
324 +const awaitExpectToThrowFailure = async expectBlock => {
325 + let caughtError;
326 + try {
327 + await expectBlock();
328 + } catch (error) {
329 + caughtError = error;
330 + }
331 + expect(caughtError).toBeDefined();
332 + return stripAnsi(caughtError.message);
333 +};
334 +
335 +describe('ReactInternalTestUtils console assertions', () => {
336 + beforeAll(() => {
337 + patchConsoleMethods({includeLog: true});
338 + });
339 +
340 + describe('assertConsoleLogDev', () => {
341 + // @gate __DEV__
342 + it('passes for a single log', () => {
343 + console.log('Hello');
344 + assertConsoleLogDev(['Hello']);
345 + });
346 +
347 + // @gate __DEV__
348 + it('passes for multiple logs', () => {
349 + console.log('Hello');
350 + console.log('Good day');
351 + console.log('Bye');
352 + assertConsoleLogDev(['Hello', 'Good day', 'Bye']);
353 + });
354 +
355 + it('fails if act is called without assertConsoleLogDev', async () => {
356 + const Yield = ({id}) => {
357 + console.log(id);
358 + return id;
359 + };
360 +
361 + function App() {
362 + return (
363 + <div>
364 + <Yield id="A" />
365 + <Yield id="B" />
366 + <Yield id="C" />
367 + </div>
368 + );
369 + }
370 +
371 + const root = ReactNoop.createRoot();
372 + await act(() => {
373 + root.render(<App />);
374 + });
375 + const message = await awaitExpectToThrowFailure(async () => {
376 + await act(() => {
377 + root.render(<App />);
378 + });
379 + });
380 +
381 + expect(message).toMatchInlineSnapshot(`
382 + "asserConsoleLogsCleared(expected)
383 +
384 + console.log was called without assertConsoleLogDev:
385 + + A
386 + + B
387 + + C
388 +
389 + You must call one of the assertConsoleDev helpers between each act call."
390 + `);
391 + });
392 +
393 + // @gate __DEV__
394 + it('fails if first expected log is not included', () => {
395 + const message = expectToThrowFailure(() => {
396 + console.log('Wow');
397 + console.log('Bye');
398 + assertConsoleLogDev(['Hi', 'Wow', 'Bye']);
399 + });
400 + expect(message).toMatchInlineSnapshot(`
401 + "assertConsoleLogDev(expected)
402 +
403 + Unexpected log(s) recorded.
404 +
405 + - Expected logs
406 + + Received logs
407 +
408 + - Hi
409 + Wow
410 + Bye"
411 + `);
412 + });
413 +
414 + // @gate __DEV__
415 + it('fails if middle expected log is not included', () => {
416 + const message = expectToThrowFailure(() => {
417 + console.log('Hi');
418 + console.log('Bye');
419 + assertConsoleLogDev(['Hi', 'Wow', 'Bye']);
420 + });
421 + expect(message).toMatchInlineSnapshot(`
422 + "assertConsoleLogDev(expected)
423 +
424 + Unexpected log(s) recorded.
425 +
426 + - Expected logs
427 + + Received logs
428 +
429 + Hi
430 + - Wow
431 + Bye"
432 + `);
433 + });
434 +
435 + // @gate __DEV__
436 + it('fails if last expected log is not included', () => {
437 + const message = expectToThrowFailure(() => {
438 + console.log('Hi');
439 + console.log('Wow');
440 + assertConsoleLogDev(['Hi', 'Wow', 'Bye']);
441 + });
442 + expect(message).toMatchInlineSnapshot(`
443 + "assertConsoleLogDev(expected)
444 +
445 + Expected log was not recorded.
446 +
447 + - Expected logs
448 + + Received logs
449 +
450 + Hi
451 + Wow
452 + - Bye"
453 + `);
454 + });
455 +
456 + // @gate __DEV__
457 + it('fails if first received log is not included', () => {
458 + const message = expectToThrowFailure(() => {
459 + console.log('Hi');
460 + console.log('Wow');
461 + console.log('Bye');
462 + assertConsoleLogDev(['Wow', 'Bye']);
463 + });
464 + expect(message).toMatchInlineSnapshot(`
465 + "assertConsoleLogDev(expected)
466 +
467 + Unexpected log(s) recorded.
468 +
469 + - Expected logs
470 + + Received logs
471 +
472 + + Hi
473 + Wow
474 + Bye"
475 + `);
476 + });
477 +
478 + // @gate __DEV__
479 + it('fails if middle received log is not included', () => {
480 + const message = expectToThrowFailure(() => {
481 + console.log('Hi');
482 + console.log('Wow');
483 + console.log('Bye');
484 + assertConsoleLogDev(['Hi', 'Bye']);
485 + });
486 + expect(message).toMatchInlineSnapshot(`
487 + "assertConsoleLogDev(expected)
488 +
489 + Unexpected log(s) recorded.
490 +
491 + - Expected logs
492 + + Received logs
493 +
494 + Hi
495 + + Wow
496 + Bye"
497 + `);
498 + });
499 +
500 + // @gate __DEV__
501 + it('fails if last received log is not included', () => {
502 + const message = expectToThrowFailure(() => {
503 + console.log('Hi');
504 + console.log('Wow');
505 + console.log('Bye');
506 + assertConsoleLogDev(['Hi', 'Wow']);
507 + });
508 + expect(message).toMatchInlineSnapshot(`
509 + "assertConsoleLogDev(expected)
510 +
511 + Unexpected log(s) recorded.
512 +
513 + - Expected logs
514 + + Received logs
515 +
516 + Hi
517 + Wow
518 + + Bye"
519 + `);
520 + });
521 +
522 + // @gate __DEV__
523 + it('fails if both expected and received mismatch', () => {
524 + const message = expectToThrowFailure(() => {
525 + console.log('Hi');
526 + console.log('Wow');
527 + console.log('Bye');
528 + assertConsoleLogDev(['Hi', 'Wow', 'Yikes']);
529 + });
530 + expect(message).toMatchInlineSnapshot(`
531 + "assertConsoleLogDev(expected)
532 +
533 + Unexpected log(s) recorded.
534 +
535 + - Expected logs
536 + + Received logs
537 +
538 + Hi
539 + Wow
540 + - Yikes
541 + + Bye"
542 + `);
543 + });
544 +
545 + // @gate __DEV__
546 + it('fails if both expected and received mismatch with multiple lines', () => {
547 + const message = expectToThrowFailure(() => {
548 + console.log('Hi\nFoo');
549 + console.log('Wow\nBar');
550 + console.log('Bye\nBaz');
551 + assertConsoleLogDev(['Hi\nFoo', 'Wow\nBar', 'Yikes\nFaz']);
552 + });
553 + expect(message).toMatchInlineSnapshot(`
554 + "assertConsoleLogDev(expected)
555 +
556 + Unexpected log(s) recorded.
557 +
558 + - Expected logs
559 + + Received logs
560 +
561 + Hi Foo
562 + Wow Bar
563 + - Yikes Faz
564 + + Bye Baz"
565 + `);
566 + });
567 +
568 + // @gate __DEV__
569 + it('fails if local withoutStack passed to assertConsoleLogDev', () => {
570 + const message = expectToThrowFailure(() => {
571 + console.log('Hello');
572 + assertConsoleLogDev([['Hello', {withoutStack: true}]]);
573 + });
574 +
575 + expect(message).toMatchInlineSnapshot(`
576 + "assertConsoleLogDev(expected)
577 +
578 + Do not pass withoutStack to assertConsoleLogDev logs, console.log does not have component stacks."
579 + `);
580 + });
581 +
582 + // @gate __DEV__
583 + it('fails if global withoutStack passed to assertConsoleLogDev', () => {
584 + const message = expectToThrowFailure(() => {
585 + console.log('Hello');
586 + assertConsoleLogDev(['Hello'], {withoutStack: true});
587 + });
588 +
589 + expect(message).toMatchInlineSnapshot(`
590 + "assertConsoleLogDev(expected)
591 +
592 + Do not pass withoutStack to assertConsoleLogDev, console.log does not have component stacks."
593 + `);
594 +
595 + assertConsoleLogDev(['Hello']);
596 + });
597 +
598 + // @gate __DEV__
599 + it('fails if the args is greater than %s argument number', () => {
600 + const message = expectToThrowFailure(() => {
601 + console.log('Hi %s', 'Sara', 'extra');
602 + assertConsoleLogDev(['Hi']);
603 + });
604 + expect(message).toMatchInlineSnapshot(`
605 + "assertConsoleLogDev(expected)
606 +
607 + Received 2 arguments for a message with 1 placeholders:
608 + "Hi %s""
609 + `);
610 + });
611 +
612 + // @gate __DEV__
613 + it('fails if the args is greater than %s argument number for multiple logs', () => {
614 + const message = expectToThrowFailure(() => {
615 + console.log('Hi %s', 'Sara', 'extra');
616 + console.log('Bye %s', 'Sara', 'extra');
617 + assertConsoleLogDev(['Hi', 'Bye']);
618 + });
619 + expect(message).toMatchInlineSnapshot(`
620 + "assertConsoleLogDev(expected)
621 +
622 + Received 2 arguments for a message with 1 placeholders:
623 + "Hi %s"
624 +
625 + Received 2 arguments for a message with 1 placeholders:
626 + "Bye %s""
627 + `);
628 + });
629 +
630 + // @gate __DEV__
631 + it('fails if the %s argument number is greater than args', () => {
632 + const message = expectToThrowFailure(() => {
633 + console.log('Hi %s');
634 + assertConsoleLogDev(['Hi']);
635 + });
636 + expect(message).toMatchInlineSnapshot(`
637 + "assertConsoleLogDev(expected)
638 +
639 + Received 0 arguments for a message with 1 placeholders:
640 + "Hi %s""
641 + `);
642 + });
643 +
644 + // @gate __DEV__
645 + it('fails if the %s argument number is greater than args for multiple logs', () => {
646 + const message = expectToThrowFailure(() => {
647 + console.log('Hi %s');
648 + console.log('Bye %s');
649 + assertConsoleLogDev(['Hi', 'Bye']);
650 + });
651 + expect(message).toMatchInlineSnapshot(`
652 + "assertConsoleLogDev(expected)
653 +
654 + Received 0 arguments for a message with 1 placeholders:
655 + "Hi %s"
656 +
657 + Received 0 arguments for a message with 1 placeholders:
658 + "Bye %s""
659 + `);
660 + });
661 +
662 + // @gate __DEV__
663 + it('fails if first arg is not an array', () => {
664 + const message = expectToThrowFailure(() => {
665 + console.log('Hi');
666 + console.log('Bye');
667 + assertConsoleLogDev('Hi', 'Bye');
668 + });
669 + expect(message).toMatchInlineSnapshot(`
670 + "assertConsoleLogDev(expected)
671 +
672 + Expected messages should be an array of strings but was given type "string"."
673 + `);
674 +
675 + assertConsoleLogDev(['Hi', 'Bye']);
676 + });
677 +
678 + it('should fail if waitFor is called before asserting', async () => {
679 + const Yield = ({id}) => {
680 + Scheduler.log(id);
681 + return id;
682 + };
683 +
684 + const root = ReactNoop.createRoot();
685 + startTransition(() => {
686 + root.render(
687 + <div>
688 + <Yield id="foo" />
689 + <Yield id="bar" />
690 + <Yield id="baz" />
691 + </div>
692 + );
693 + });
694 +
695 + console.log('Not asserted');
696 +
697 + const message = await awaitExpectToThrowFailure(async () => {
698 + await waitFor(['foo', 'bar']);
699 + });
700 + expect(message).toMatchInlineSnapshot(`
701 + "asserConsoleLogsCleared(expected)
702 +
703 + console.log was called without assertConsoleLogDev:
704 + + Not asserted
705 +
706 + You must call one of the assertConsoleDev helpers between each act call."
707 + `);
708 +
709 + await waitForAll(['foo', 'bar', 'baz']);
710 + });
711 +
712 + test('should fail if waitForThrow is called before asserting', async () => {
713 + const Yield = ({id}) => {
714 + Scheduler.log(id);
715 + return id;
716 + };
717 +
718 + function BadRender() {
719 + throw new Error('Oh no!');
720 + }
721 +
722 + function App() {
723 + return (
724 + <div>
725 + <Yield id="A" />
726 + <Yield id="B" />
727 + <BadRender />
728 + <Yield id="C" />
729 + <Yield id="D" />
730 + </div>
731 + );
732 + }
733 +
734 + const root = ReactNoop.createRoot();
735 + root.render(<App />);
736 +
737 + console.log('Not asserted');
738 +
739 + const message = await awaitExpectToThrowFailure(async () => {
740 + await waitForThrow('Oh no!');
741 + });
742 + expect(message).toMatchInlineSnapshot(`
743 + "asserConsoleLogsCleared(expected)
744 +
745 + console.log was called without assertConsoleLogDev:
746 + + Not asserted
747 +
748 + You must call one of the assertConsoleDev helpers between each act call."
749 + `);
750 +
751 + await waitForAll(['A', 'B', 'A', 'B']);
752 + });
753 +
754 + test('should fail if waitForPaint is called before asserting', async () => {
755 + function App({prop}) {
756 + const deferred = useDeferredValue(prop);
757 + const text = `Urgent: ${prop}, Deferred: ${deferred}`;
758 + Scheduler.log(text);
759 + return text;
760 + }
761 +
762 + const root = ReactNoop.createRoot();
763 + root.render(<App prop="A" />);
764 +
765 + await waitForAll(['Urgent: A, Deferred: A']);
766 + expect(root).toMatchRenderedOutput('Urgent: A, Deferred: A');
767 +
768 + // This update will result in two separate paints: an urgent one, and a
769 + // deferred one.
770 + root.render(<App prop="B" />);
771 +
772 + console.log('Not asserted');
773 + const message = await awaitExpectToThrowFailure(async () => {
774 + await waitForPaint(['Urgent: B, Deferred: A']);
775 + });
776 +
777 + expect(message).toMatchInlineSnapshot(`
778 + "asserConsoleLogsCleared(expected)
779 +
780 + console.log was called without assertConsoleLogDev:
781 + + Not asserted
782 +
783 + You must call one of the assertConsoleDev helpers between each act call."
784 + `);
785 +
786 + await waitForAll(['Urgent: B, Deferred: A', 'Urgent: B, Deferred: B']);
787 + });
788 +
789 + it('should fail if waitForAll is called before asserting', async () => {
790 + const Yield = ({id}) => {
791 + Scheduler.log(id);
792 + return id;
793 + };
794 +
795 + const root = ReactNoop.createRoot();
796 + startTransition(() => {
797 + root.render(
798 + <div>
799 + <Yield id="foo" />
800 + <Yield id="bar" />
801 + <Yield id="baz" />
802 + </div>
803 + );
804 + });
805 +
806 + console.log('Not asserted');
807 +
808 + const message = await awaitExpectToThrowFailure(async () => {
809 + await waitForAll(['foo', 'bar', 'baz']);
810 + });
811 + expect(message).toMatchInlineSnapshot(`
812 + "asserConsoleLogsCleared(expected)
813 +
814 + console.log was called without assertConsoleLogDev:
815 + + Not asserted
816 +
817 + You must call one of the assertConsoleDev helpers between each act call."
818 + `);
819 +
820 + await waitForAll(['foo', 'bar', 'baz']);
821 + });
822 + it('should fail if toMatchRenderedOutput is called before asserting', async () => {
823 + const Yield = ({id}) => {
824 + Scheduler.log(id);
825 + console.log('Not asserted');
826 + return id;
827 + };
828 +
829 + const root = ReactNoop.createRoot();
830 + startTransition(() => {
831 + root.render(
832 + <div>
833 + <Yield id="foo" />
834 + <Yield id="bar" />
835 + <Yield id="baz" />
836 + </div>
837 + );
838 + });
839 +
840 + assertLog([]);
841 +
842 + await waitForAll(['foo', 'bar', 'baz']);
843 + const message = expectToThrowFailure(() => {
844 + expect(root).toMatchRenderedOutput(<div>foobarbaz</div>);
845 + });
846 + expect(message).toMatchInlineSnapshot(`
847 + "asserConsoleLogsCleared(expected)
848 +
849 + console.log was called without assertConsoleLogDev:
850 + + Not asserted
851 + + Not asserted
852 + + Not asserted
853 +
854 + You must call one of the assertConsoleDev helpers between each act call."
855 + `);
856 +
857 + expect(root).toMatchRenderedOutput(<div>foobarbaz</div>);
858 + });
859 + });
860 +
861 + describe('assertConsoleWarnDev', () => {
862 + // @gate __DEV__
863 + it('passes if an warning contains a stack', () => {
864 + console.warn('Hello\n in div');
865 + assertConsoleWarnDev(['Hello']);
866 + });
867 +
868 + // @gate __DEV__
869 + it('passes if all warnings contain a stack', () => {
870 + console.warn('Hello\n in div');
871 + console.warn('Good day\n in div');
872 + console.warn('Bye\n in div');
873 + assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
874 + });
875 +
876 + it('fails if act is called without assertConsoleWarnDev', async () => {
877 + const Yield = ({id}) => {
878 + console.warn(id);
879 + return id;
880 + };
881 +
882 + function App() {
883 + return (
884 + <div>
885 + <Yield id="A" />
886 + <Yield id="B" />
887 + <Yield id="C" />
888 + </div>
889 + );
890 + }
891 +
892 + const root = ReactNoop.createRoot();
893 + await act(() => {
894 + root.render(<App />);
895 + });
896 + const message = await awaitExpectToThrowFailure(async () => {
897 + await act(() => {
898 + root.render(<App />);
899 + });
900 + });
901 +
902 + expect(message).toMatchInlineSnapshot(`
903 + "asserConsoleLogsCleared(expected)
904 +
905 + console.warn was called without assertConsoleWarnDev:
906 + + A
907 + + B
908 + + C
909 +
910 + You must call one of the assertConsoleDev helpers between each act call."
911 + `);
912 + });
913 +
914 + it('fails if act is called without any assertConsoleDev helpers', async () => {
915 + const Yield = ({id}) => {
916 + console.log(id);
917 + console.warn(id);
918 + console.error(id);
919 + return id;
920 + };
921 +
922 + function App() {
923 + return (
924 + <div>
925 + <Yield id="A" />
926 + <Yield id="B" />
927 + <Yield id="C" />
928 + </div>
929 + );
930 + }
931 +
932 + const root = ReactNoop.createRoot();
933 + await act(() => {
934 + root.render(<App />);
935 + });
936 + const message = await awaitExpectToThrowFailure(async () => {
937 + await act(() => {
938 + root.render(<App />);
939 + });
940 + });
941 +
942 + expect(message).toMatchInlineSnapshot(`
943 + "asserConsoleLogsCleared(expected)
944 +
945 + console.log was called without assertConsoleLogDev:
946 + + A
947 + + B
948 + + C
949 +
950 + console.warn was called without assertConsoleWarnDev:
951 + + A
952 + + B
953 + + C
954 +
955 + console.error was called without assertConsoleErrorDev:
956 + + A
957 + + B
958 + + C
959 +
960 + You must call one of the assertConsoleDev helpers between each act call."
961 + `);
962 + });
963 +
964 + // @gate __DEV__
965 + it('fails if first expected warning is not included', () => {
966 + const message = expectToThrowFailure(() => {
967 + console.warn('Wow \n in div');
968 + console.warn('Bye \n in div');
969 + assertConsoleWarnDev(['Hi', 'Wow', 'Bye']);
970 + });
971 + expect(message).toMatchInlineSnapshot(`
972 + "assertConsoleWarnDev(expected)
973 +
974 + Unexpected warning(s) recorded.
975 +
976 + - Expected warnings
977 + + Received warnings
978 +
979 + - Hi
980 + - Wow
981 + - Bye
982 + + Wow <component stack>
983 + + Bye <component stack>"
984 + `);
985 + });
986 +
987 + // @gate __DEV__
988 + it('fails if middle expected warning is not included', () => {
989 + const message = expectToThrowFailure(() => {
990 + console.warn('Hi \n in div');
991 + console.warn('Bye \n in div');
992 + assertConsoleWarnDev(['Hi', 'Wow', 'Bye']);
993 + });
994 + expect(message).toMatchInlineSnapshot(`
995 + "assertConsoleWarnDev(expected)
996 +
997 + Unexpected warning(s) recorded.
998 +
999 + - Expected warnings
1000 + + Received warnings
1001 +
1002 + - Hi
1003 + - Wow
1004 + - Bye
1005 + + Hi <component stack>
1006 + + Bye <component stack>"
1007 + `);
1008 + });
1009 +
1010 + // @gate __DEV__
1011 + it('fails if last expected warning is not included', () => {
1012 + const message = expectToThrowFailure(() => {
1013 + console.warn('Hi \n in div');
1014 + console.warn('Wow \n in div');
1015 + assertConsoleWarnDev(['Hi', 'Wow', 'Bye']);
1016 + });
1017 + expect(message).toMatchInlineSnapshot(`
1018 + "assertConsoleWarnDev(expected)
1019 +
1020 + Expected warning was not recorded.
1021 +
1022 + - Expected warnings
1023 + + Received warnings
1024 +
1025 + - Hi
1026 + - Wow
1027 + - Bye
1028 + + Hi <component stack>
1029 + + Wow <component stack>"
1030 + `);
1031 + });
1032 +
1033 + // @gate __DEV__
1034 + it('fails if first received warning is not included', () => {
1035 + const message = expectToThrowFailure(() => {
1036 + console.warn('Hi \n in div');
1037 + console.warn('Wow \n in div');
1038 + console.warn('Bye \n in div');
1039 + assertConsoleWarnDev(['Wow', 'Bye']);
1040 + });
1041 + expect(message).toMatchInlineSnapshot(`
1042 + "assertConsoleWarnDev(expected)
1043 +
1044 + Unexpected warning(s) recorded.
1045 +
1046 + - Expected warnings
1047 + + Received warnings
1048 +
1049 + - Wow
1050 + - Bye
1051 + + Hi <component stack>
1052 + + Wow <component stack>
1053 + + Bye <component stack>"
1054 + `);
1055 + });
1056 +
1057 + // @gate __DEV__
1058 + it('fails if middle received warning is not included', () => {
1059 + const message = expectToThrowFailure(() => {
1060 + console.warn('Hi \n in div');
1061 + console.warn('Wow \n in div');
1062 + console.warn('Bye \n in div');
1063 + assertConsoleWarnDev(['Hi', 'Bye']);
1064 + });
1065 + expect(message).toMatchInlineSnapshot(`
1066 + "assertConsoleWarnDev(expected)
1067 +
1068 + Unexpected warning(s) recorded.
1069 +
1070 + - Expected warnings
1071 + + Received warnings
1072 +
1073 + - Hi
1074 + - Bye
1075 + + Hi <component stack>
1076 + + Wow <component stack>
1077 + + Bye <component stack>"
1078 + `);
1079 + });
1080 +
1081 + // @gate __DEV__
1082 + it('fails if last received warning is not included', () => {
1083 + const message = expectToThrowFailure(() => {
1084 + console.warn('Hi \n in div');
1085 + console.warn('Wow \n in div');
1086 + console.warn('Bye \n in div');
1087 + assertConsoleWarnDev(['Hi', 'Wow']);
1088 + });
1089 + expect(message).toMatchInlineSnapshot(`
1090 + "assertConsoleWarnDev(expected)
1091 +
1092 + Unexpected warning(s) recorded.
1093 +
1094 + - Expected warnings
1095 + + Received warnings
1096 +
1097 + - Hi
1098 + - Wow
1099 + + Hi <component stack>
1100 + + Wow <component stack>
1101 + + Bye <component stack>"
1102 + `);
1103 + });
1104 +
1105 + // @gate __DEV__
1106 + it('fails if only warning does not contain a stack', () => {
1107 + const message = expectToThrowFailure(() => {
1108 + console.warn('Hello');
1109 + assertConsoleWarnDev(['Hello']);
1110 + });
1111 + expect(message).toMatchInlineSnapshot(`
1112 + "assertConsoleWarnDev(expected)
1113 +
1114 + Missing component stack for:
1115 + "Hello"
1116 +
1117 + If this warning should omit a component stack, pass [log, {withoutStack: true}].
1118 + If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1119 + `);
1120 + });
1121 +
1122 + // @gate __DEV__
1123 + it('fails if first warning does not contain a stack', () => {
1124 + const message = expectToThrowFailure(() => {
1125 + console.warn('Hello');
1126 + console.warn('Good day\n in div');
1127 + console.warn('Bye\n in div');
1128 + assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
1129 + });
1130 + expect(message).toMatchInlineSnapshot(`
1131 + "assertConsoleWarnDev(expected)
1132 +
1133 + Missing component stack for:
1134 + "Hello"
1135 +
1136 + If this warning should omit a component stack, pass [log, {withoutStack: true}].
1137 + If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1138 + `);
1139 + });
1140 +
1141 + // @gate __DEV__
1142 + it('fails if middle warning does not contain a stack', () => {
1143 + const message = expectToThrowFailure(() => {
1144 + console.warn('Hello\n in div');
1145 + console.warn('Good day');
1146 + console.warn('Bye\n in div');
1147 + assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
1148 + });
1149 + expect(message).toMatchInlineSnapshot(`
1150 + "assertConsoleWarnDev(expected)
1151 +
1152 + Missing component stack for:
1153 + "Good day"
1154 +
1155 + If this warning should omit a component stack, pass [log, {withoutStack: true}].
1156 + If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1157 + `);
1158 + });
1159 +
1160 + // @gate __DEV__
1161 + it('fails if last warning does not contain a stack', () => {
1162 + const message = expectToThrowFailure(() => {
1163 + console.warn('Hello\n in div');
1164 + console.warn('Good day\n in div');
1165 + console.warn('Bye');
1166 + assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
1167 + });
1168 + expect(message).toMatchInlineSnapshot(`
1169 + "assertConsoleWarnDev(expected)
1170 +
1171 + Missing component stack for:
1172 + "Bye"
1173 +
1174 + If this warning should omit a component stack, pass [log, {withoutStack: true}].
1175 + If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1176 + `);
1177 + });
1178 +
1179 + // @gate __DEV__
1180 + it('fails if all warnings do not contain a stack', () => {
1181 + const message = expectToThrowFailure(() => {
1182 + console.warn('Hello');
1183 + console.warn('Good day');
1184 + console.warn('Bye');
1185 + assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
1186 + });
1187 + expect(message).toMatchInlineSnapshot(`
1188 + "assertConsoleWarnDev(expected)
1189 +
1190 + Missing component stack for:
1191 + "Hello"
1192 +
1193 + Missing component stack for:
1194 + "Good day"
1195 +
1196 + Missing component stack for:
1197 + "Bye"
1198 +
1199 + If this warning should omit a component stack, pass [log, {withoutStack: true}].
1200 + If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1201 + `);
1202 + });
1203 +
1204 + describe('global withoutStack', () => {
1205 + // @gate __DEV__
1206 + it('passes if warnings without stack explicitly opt out', () => {
1207 + console.warn('Hello');
1208 + assertConsoleWarnDev(['Hello'], {withoutStack: true});
1209 +
1210 + console.warn('Hello');
1211 + console.warn('Good day');
1212 + console.warn('Bye');
1213 +
1214 + assertConsoleWarnDev(['Hello', 'Good day', 'Bye'], {
1215 + withoutStack: true,
1216 + });
1217 + });
1218 +
1219 + // @gate __DEV__
1220 + it('fails if withoutStack is invalid null value', () => {
1221 + const message = expectToThrowFailure(() => {
1222 + console.warn('Hi');
1223 + assertConsoleWarnDev(['Hi'], {withoutStack: null});
1224 + });
1225 + expect(message).toMatchInlineSnapshot(`
1226 + "assertConsoleWarnDev(expected)
1227 +
1228 + The second argument must be {withoutStack: true}.
1229 +
1230 + Instead received {"withoutStack":null}."
1231 + `);
1232 + assertConsoleWarnDev(['Hi'], {withoutStack: true});
1233 + });
1234 +
1235 + // @gate __DEV__
1236 + it('fails if withoutStack is invalid {} value', () => {
1237 + const message = expectToThrowFailure(() => {
1238 + console.warn('Hi');
1239 + assertConsoleWarnDev(['Hi'], {withoutStack: {}});
1240 + });
1241 + expect(message).toMatchInlineSnapshot(`
1242 + "assertConsoleWarnDev(expected)
1243 +
1244 + The second argument must be {withoutStack: true}.
1245 +
1246 + Instead received {"withoutStack":{}}."
1247 + `);
1248 + assertConsoleWarnDev(['Hi'], {withoutStack: true});
1249 + });
1250 +
1251 + // @gate __DEV__
1252 + it('fails if withoutStack is invalid string value', () => {
1253 + const message = expectToThrowFailure(() => {
1254 + console.warn('Hi');
1255 + assertConsoleWarnDev(['Hi'], {withoutStack: 'haha'});
1256 + });
1257 + expect(message).toMatchInlineSnapshot(`
1258 + "assertConsoleWarnDev(expected)
1259 +
1260 + The second argument must be {withoutStack: true}.
1261 +
1262 + Instead received {"withoutStack":"haha"}."
1263 + `);
1264 + assertConsoleWarnDev(['Hi'], {withoutStack: true});
1265 + });
1266 +
1267 + // @gate __DEV__
1268 + it('fails if only warning is not expected to have a stack, but does', () => {
1269 + const message = expectToThrowFailure(() => {
1270 + console.warn('Hello\n in div');
1271 + assertConsoleWarnDev(['Hello'], {withoutStack: true});
1272 + });
1273 + expect(message).toMatchInlineSnapshot(`
1274 + "assertConsoleWarnDev(expected)
1275 +
1276 + Unexpected component stack for:
1277 + "Hello <component stack>"
1278 +
1279 + If this warning should include a component stack, remove {withoutStack: true} from this warning.
1280 + If all warnings should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleWarnDev call."
1281 + `);
1282 + });
1283 +
1284 + // @gate __DEV__
1285 + it('fails if warnings are not expected to have a stack, but some do', () => {
1286 + const message = expectToThrowFailure(() => {
1287 + console.warn('Hello\n in div');
1288 + console.warn('Good day');
1289 + console.warn('Bye\n in div');
1290 + assertConsoleWarnDev(['Hello', 'Good day', 'Bye'], {
1291 + withoutStack: true,
1292 + });
1293 + });
1294 + expect(message).toMatchInlineSnapshot(`
1295 + "assertConsoleWarnDev(expected)
1296 +
1297 + Unexpected component stack for:
1298 + "Hello <component stack>"
1299 +
1300 + Unexpected component stack for:
1301 + "Bye <component stack>"
1302 +
1303 + If this warning should include a component stack, remove {withoutStack: true} from this warning.
1304 + If all warnings should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleWarnDev call."
1305 + `);
1306 + });
1307 + });
1308 + describe('local withoutStack', () => {
1309 + // @gate __DEV__
1310 + it('passes when expected withoutStack logs matches the actual logs', () => {
1311 + console.warn('Hello\n in div');
1312 + console.warn('Good day');
1313 + console.warn('Bye\n in div');
1314 + assertConsoleWarnDev([
1315 + 'Hello',
1316 + ['Good day', {withoutStack: true}],
1317 + 'Bye',
1318 + ]);
1319 + });
1320 +
1321 + // @gate __DEV__
1322 + it('fails if withoutStack is invalid null value', () => {
1323 + const message = expectToThrowFailure(() => {
1324 + console.warn('Hi');
1325 + assertConsoleWarnDev([['Hi', {withoutStack: null}]]);
1326 + });
1327 + expect(message).toMatchInlineSnapshot(`
1328 + "assertConsoleWarnDev(expected)
1329 +
1330 + Log entries that are arrays must be of the form [string, {withoutStack: true}]
1331 +
1332 + Instead received [string, {"withoutStack":null}]."
1333 + `);
1334 + });
1335 +
1336 + // @gate __DEV__
1337 + it('fails if withoutStack is invalid {} value', () => {
1338 + const message = expectToThrowFailure(() => {
1339 + console.warn('Hi');
1340 + assertConsoleWarnDev([['Hi', {withoutStack: {}}]]);
1341 + });
1342 + expect(message).toMatchInlineSnapshot(`
1343 + "assertConsoleWarnDev(expected)
1344 +
1345 + Log entries that are arrays must be of the form [string, {withoutStack: true}]
1346 +
1347 + Instead received [string, {"withoutStack":{}}]."
1348 + `);
1349 + });
1350 +
1351 + // @gate __DEV__
1352 + it('fails if withoutStack is invalid string value', () => {
1353 + const message = expectToThrowFailure(() => {
1354 + console.warn('Hi');
1355 + assertConsoleWarnDev([['Hi', {withoutStack: 'haha'}]]);
1356 + });
1357 + expect(message).toMatchInlineSnapshot(`
1358 + "assertConsoleWarnDev(expected)
1359 +
1360 + Log entries that are arrays must be of the form [string, {withoutStack: true}]
1361 +
1362 + Instead received [string, {"withoutStack":"haha"}]."
1363 + `);
1364 + });
1365 +
1366 + // @gate __DEV__
1367 + it('fails if withoutStack is invalid number value', () => {
1368 + const message = expectToThrowFailure(() => {
1369 + console.warn('Hi');
1370 + assertConsoleWarnDev([['Hi', {withoutStack: 4}]]);
1371 + });
1372 + expect(message).toMatchInlineSnapshot(`
1373 + "assertConsoleWarnDev(expected)
1374 +
1375 + Log entries that are arrays must be of the form [string, {withoutStack: true}]
1376 +
1377 + Instead received [string, {"withoutStack":4}]."
1378 + `);
1379 + });
1380 +
1381 + // @gate __DEV__
1382 + it('fails if you forget to wrap local withoutStack in array', () => {
1383 + const message = expectToThrowFailure(() => {
1384 + console.warn('Hello\n in div');
1385 + console.warn('Bye\n in div');
1386 + assertConsoleWarnDev(['Hello', {withoutStack: true}, 'Bye']);
1387 + });
1388 + expect(message).toMatchInlineSnapshot(`
1389 + "assertConsoleWarnDev(expected)
1390 +
1391 + Did you forget to wrap a log with withoutStack in an array?
1392 +
1393 + The expected message for assertConsoleWarnDev() must be a string or an array of length 2.
1394 +
1395 + Instead received {"withoutStack":true}."
1396 + `);
1397 + });
1398 +
1399 + // @gate __DEV__
1400 + it('fails if you wrap in an array unnecessarily', () => {
1401 + const message = expectToThrowFailure(() => {
1402 + console.warn('Hello');
1403 + assertConsoleWarnDev([['Hello']]);
1404 + });
1405 + expect(message).toMatchInlineSnapshot(`
1406 + "assertConsoleWarnDev(expected)
1407 +
1408 + Did you forget to remove the array around the log?
1409 +
1410 + The expected message for assertConsoleWarnDev() must be a string or an array of length 2, but there's only one item in the array. If this is intentional, remove the extra array."
1411 + `);
1412 + });
1413 +
1414 + // @gate __DEV__
1415 + it('fails if only warning is not expected to have a stack, but does', () => {
1416 + const message = expectToThrowFailure(() => {
1417 + console.warn('Hello\n in div');
1418 + assertConsoleWarnDev([['Hello', {withoutStack: true}]]);
1419 + });
1420 + expect(message).toMatchInlineSnapshot(`
1421 + "assertConsoleWarnDev(expected)
1422 +
1423 + Unexpected component stack for:
1424 + "Hello <component stack>"
1425 +
1426 + If this warning should include a component stack, remove {withoutStack: true} from this warning.
1427 + If all warnings should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleWarnDev call."
1428 + `);
1429 + });
1430 +
1431 + // @gate __DEV__
1432 + it('fails if warnings are not expected to have a stack, but some do', () => {
1433 + const message = expectToThrowFailure(() => {
1434 + console.warn('Hello\n in div');
1435 + console.warn('Good day');
1436 + console.warn('Bye\n in div');
1437 + assertConsoleWarnDev([
1438 + [
1439 + 'Hello',
1440 + {
1441 + withoutStack: true,
1442 + },
1443 + ],
1444 + 'Good day',
1445 + [
1446 + 'Bye',
1447 + {
1448 + withoutStack: true,
1449 + },
1450 + ],
1451 + ]);
1452 + });
1453 + expect(message).toMatchInlineSnapshot(`
1454 + "assertConsoleWarnDev(expected)
1455 +
1456 + Unexpected component stack for:
1457 + "Hello <component stack>"
1458 +
1459 + Unexpected component stack for:
1460 + "Bye <component stack>"
1461 +
1462 + If this warning should include a component stack, remove {withoutStack: true} from this warning.
1463 + If all warnings should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleWarnDev call."
1464 + `);
1465 + });
1466 + });
1467 +
1468 + // @gate __DEV__
1469 + it('fails if the args is greater than %s argument number', () => {
1470 + const message = expectToThrowFailure(() => {
1471 + console.warn('Hi %s', 'Sara', 'extra');
1472 + assertConsoleWarnDev(['Hi'], {withoutStack: true});
1473 + });
1474 + expect(message).toMatchInlineSnapshot(`
1475 + "assertConsoleWarnDev(expected)
1476 +
1477 + Received 2 arguments for a message with 1 placeholders:
1478 + "Hi %s""
1479 + `);
1480 + });
1481 +
1482 + // @gate __DEV__
1483 + it('fails if the args is greater than %s argument number for multiple warnings', () => {
1484 + const message = expectToThrowFailure(() => {
1485 + console.warn('Hi %s', 'Sara', 'extra');
1486 + console.warn('Bye %s', 'Sara', 'extra');
1487 + assertConsoleWarnDev(['Hi', 'Bye'], {withoutStack: true});
1488 + });
1489 + expect(message).toMatchInlineSnapshot(`
1490 + "assertConsoleWarnDev(expected)
1491 +
1492 + Received 2 arguments for a message with 1 placeholders:
1493 + "Hi %s"
1494 +
1495 + Received 2 arguments for a message with 1 placeholders:
1496 + "Bye %s""
1497 + `);
1498 + });
1499 +
1500 + // @gate __DEV__
1501 + it('fails if the %s argument number is greater than args', () => {
1502 + const message = expectToThrowFailure(() => {
1503 + console.warn('Hi %s');
1504 + assertConsoleWarnDev(['Hi'], {withoutStack: true});
1505 + });
1506 + expect(message).toMatchInlineSnapshot(`
1507 + "assertConsoleWarnDev(expected)
1508 +
1509 + Received 0 arguments for a message with 1 placeholders:
1510 + "Hi %s""
1511 + `);
1512 + });
1513 +
1514 + // @gate __DEV__
1515 + it('fails if the %s argument number is greater than args for multiple warnings', () => {
1516 + const message = expectToThrowFailure(() => {
1517 + console.warn('Hi %s');
1518 + console.warn('Bye %s');
1519 + assertConsoleWarnDev(['Hi', 'Bye'], {withoutStack: true});
1520 + });
1521 + expect(message).toMatchInlineSnapshot(`
1522 + "assertConsoleWarnDev(expected)
1523 +
1524 + Received 0 arguments for a message with 1 placeholders:
1525 + "Hi %s"
1526 +
1527 + Received 0 arguments for a message with 1 placeholders:
1528 + "Bye %s""
1529 + `);
1530 + });
1531 +
1532 + // @gate __DEV__
1533 + it('fails if component stack is passed twice', () => {
1534 + const message = expectToThrowFailure(() => {
1535 + console.warn('Hi %s%s', '\n in div', '\n in div');
1536 + assertConsoleWarnDev(['Hi']);
1537 + });
1538 + expect(message).toMatchInlineSnapshot(`
1539 + "assertConsoleWarnDev(expected)
1540 +
1541 + Received more than one component stack for a warning:
1542 + "Hi %s%s""
1543 + `);
1544 + });
1545 +
1546 + // @gate __DEV__
1547 + it('fails if multiple logs pass component stack twice', () => {
1548 + const message = expectToThrowFailure(() => {
1549 + console.warn('Hi %s%s', '\n in div', '\n in div');
1550 + console.warn('Bye %s%s', '\n in div', '\n in div');
1551 + assertConsoleWarnDev(['Hi', 'Bye']);
1552 + });
1553 + expect(message).toMatchInlineSnapshot(`
1554 + "assertConsoleWarnDev(expected)
1555 +
1556 + Received more than one component stack for a warning:
1557 + "Hi %s%s"
1558 +
1559 + Received more than one component stack for a warning:
1560 + "Bye %s%s""
1561 + `);
1562 + });
1563 +
1564 + // @gate __DEV__
1565 + it('fails if multiple strings are passed without an array wrapper for single log', () => {
1566 + const message = expectToThrowFailure(() => {
1567 + console.warn('Hi \n in div');
1568 + console.warn('Bye \n in div');
1569 + assertConsoleWarnDev('Hi', 'Bye');
1570 + });
1571 + expect(message).toMatchInlineSnapshot(`
1572 + "assertConsoleWarnDev(expected)
1573 +
1574 + Expected messages should be an array of strings but was given type "string"."
1575 + `);
1576 + assertConsoleWarnDev(['Hi', 'Bye']);
1577 + });
1578 +
1579 + // @gate __DEV__
1580 + it('fails if multiple strings are passed without an array wrapper for multiple logs', () => {
1581 + const message = expectToThrowFailure(() => {
1582 + console.warn('Hi \n in div');
1583 + console.warn('Bye \n in div');
1584 + assertConsoleWarnDev('Hi', 'Bye');
1585 + });
1586 + expect(message).toMatchInlineSnapshot(`
1587 + "assertConsoleWarnDev(expected)
1588 +
1589 + Expected messages should be an array of strings but was given type "string"."
1590 + `);
1591 + assertConsoleWarnDev(['Hi', 'Bye']);
1592 + });
1593 +
1594 + // @gate __DEV__
1595 + it('fails on more than two arguments', () => {
1596 + const message = expectToThrowFailure(() => {
1597 + console.warn('Hi \n in div');
1598 + console.warn('Wow \n in div');
1599 + console.warn('Bye \n in div');
1600 + assertConsoleWarnDev('Hi', undefined, 'Bye');
1601 + });
1602 + expect(message).toMatchInlineSnapshot(`
1603 + "assertConsoleWarnDev(expected)
1604 +
1605 + Expected messages should be an array of strings but was given type "string"."
1606 + `);
1607 + assertConsoleWarnDev(['Hi', 'Wow', 'Bye']);
1608 + });
1609 +
1610 + it('should fail if waitFor is called before asserting', async () => {
1611 + const Yield = ({id}) => {
1612 + Scheduler.log(id);
1613 + return id;
1614 + };
1615 +
1616 + const root = ReactNoop.createRoot();
1617 + startTransition(() => {
1618 + root.render(
1619 + <div>
1620 + <Yield id="foo" />
1621 + <Yield id="bar" />
1622 + <Yield id="baz" />
1623 + </div>
1624 + );
1625 + });
1626 +
1627 + console.warn('Not asserted');
1628 +
1629 + const message = await awaitExpectToThrowFailure(async () => {
1630 + await waitFor(['foo', 'bar']);
1631 + });
1632 + expect(message).toMatchInlineSnapshot(`
1633 + "asserConsoleLogsCleared(expected)
1634 +
1635 + console.warn was called without assertConsoleWarnDev:
1636 + + Not asserted
1637 +
1638 + You must call one of the assertConsoleDev helpers between each act call."
1639 + `);
1640 +
1641 + await waitForAll(['foo', 'bar', 'baz']);
1642 + });
1643 +
1644 + test('should fail if waitForThrow is called before asserting', async () => {
1645 + const Yield = ({id}) => {
1646 + Scheduler.log(id);
1647 + return id;
1648 + };
1649 +
1650 + function BadRender() {
1651 + throw new Error('Oh no!');
1652 + }
1653 +
1654 + function App() {
1655 + return (
1656 + <div>
1657 + <Yield id="A" />
1658 + <Yield id="B" />
1659 + <BadRender />
1660 + <Yield id="C" />
1661 + <Yield id="D" />
1662 + </div>
1663 + );
1664 + }
1665 +
1666 + const root = ReactNoop.createRoot();
1667 + root.render(<App />);
1668 +
1669 + console.warn('Not asserted');
1670 +
1671 + const message = await awaitExpectToThrowFailure(async () => {
1672 + await waitForThrow('Oh no!');
1673 + });
1674 + expect(message).toMatchInlineSnapshot(`
1675 + "asserConsoleLogsCleared(expected)
1676 +
1677 + console.warn was called without assertConsoleWarnDev:
1678 + + Not asserted
1679 +
1680 + You must call one of the assertConsoleDev helpers between each act call."
1681 + `);
1682 +
1683 + await waitForAll(['A', 'B', 'A', 'B']);
1684 + });
1685 +
1686 + test('should fail if waitForPaint is called before asserting', async () => {
1687 + function App({prop}) {
1688 + const deferred = useDeferredValue(prop);
1689 + const text = `Urgent: ${prop}, Deferred: ${deferred}`;
1690 + Scheduler.log(text);
1691 + return text;
1692 + }
1693 +
1694 + const root = ReactNoop.createRoot();
1695 + root.render(<App prop="A" />);
1696 +
1697 + await waitForAll(['Urgent: A, Deferred: A']);
1698 + expect(root).toMatchRenderedOutput('Urgent: A, Deferred: A');
1699 +
1700 + // This update will result in two separate paints: an urgent one, and a
1701 + // deferred one.
1702 + root.render(<App prop="B" />);
1703 +
1704 + console.warn('Not asserted');
1705 + const message = await awaitExpectToThrowFailure(async () => {
1706 + await waitForPaint(['Urgent: B, Deferred: A']);
1707 + });
1708 +
1709 + expect(message).toMatchInlineSnapshot(`
1710 + "asserConsoleLogsCleared(expected)
1711 +
1712 + console.warn was called without assertConsoleWarnDev:
1713 + + Not asserted
1714 +
1715 + You must call one of the assertConsoleDev helpers between each act call."
1716 + `);
1717 +
1718 + await waitForAll(['Urgent: B, Deferred: A', 'Urgent: B, Deferred: B']);
1719 + });
1720 +
1721 + it('should fail if waitForAll is called before asserting', async () => {
1722 + const Yield = ({id}) => {
1723 + Scheduler.log(id);
1724 + return id;
1725 + };
1726 +
1727 + const root = ReactNoop.createRoot();
1728 + startTransition(() => {
1729 + root.render(
1730 + <div>
1731 + <Yield id="foo" />
1732 + <Yield id="bar" />
1733 + <Yield id="baz" />
1734 + </div>
1735 + );
1736 + });
1737 +
1738 + console.warn('Not asserted');
1739 +
1740 + const message = await awaitExpectToThrowFailure(async () => {
1741 + await waitForAll(['foo', 'bar', 'baz']);
1742 + });
1743 + expect(message).toMatchInlineSnapshot(`
1744 + "asserConsoleLogsCleared(expected)
1745 +
1746 + console.warn was called without assertConsoleWarnDev:
1747 + + Not asserted
1748 +
1749 + You must call one of the assertConsoleDev helpers between each act call."
1750 + `);
1751 +
1752 + await waitForAll(['foo', 'bar', 'baz']);
1753 + });
1754 + it('should fail if toMatchRenderedOutput is called before asserting', async () => {
1755 + const Yield = ({id}) => {
1756 + Scheduler.log(id);
1757 + console.warn('Not asserted');
1758 + return id;
1759 + };
1760 +
1761 + const root = ReactNoop.createRoot();
1762 + startTransition(() => {
1763 + root.render(
1764 + <div>
1765 + <Yield id="foo" />
1766 + <Yield id="bar" />
1767 + <Yield id="baz" />
1768 + </div>
1769 + );
1770 + });
1771 +
1772 + assertLog([]);
1773 +
1774 + await waitForAll(['foo', 'bar', 'baz']);
1775 + const message = expectToThrowFailure(() => {
1776 + expect(root).toMatchRenderedOutput(<div>foobarbaz</div>);
1777 + });
1778 + expect(message).toMatchInlineSnapshot(`
1779 + "asserConsoleLogsCleared(expected)
1780 +
1781 + console.warn was called without assertConsoleWarnDev:
1782 + + Not asserted
1783 + + Not asserted
1784 + + Not asserted
1785 +
1786 + You must call one of the assertConsoleDev helpers between each act call."
1787 + `);
1788 +
1789 + expect(root).toMatchRenderedOutput(<div>foobarbaz</div>);
1790 + });
1791 + });
1792 +
1793 + describe('assertConsoleErrorDev', () => {
1794 + // @gate __DEV__
1795 + it('passes if an error contains a stack', () => {
1796 + console.error('Hello\n in div');
1797 + assertConsoleErrorDev(['Hello']);
1798 + });
1799 +
1800 + // @gate __DEV__
1801 + it('passes if all errors contain a stack', () => {
1802 + console.error('Hello\n in div');
1803 + console.error('Good day\n in div');
1804 + console.error('Bye\n in div');
1805 + assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
1806 + });
1807 +
1808 + it('fails if act is called without assertConsoleErrorDev', async () => {
1809 + const Yield = ({id}) => {
1810 + console.error(id);
1811 + return id;
1812 + };
1813 +
1814 + function App() {
1815 + return (
1816 + <div>
1817 + <Yield id="A" />
1818 + <Yield id="B" />
1819 + <Yield id="C" />
1820 + </div>
1821 + );
1822 + }
1823 +
1824 + const root = ReactNoop.createRoot();
1825 + await act(() => {
1826 + root.render(<App />);
1827 + });
1828 + const message = await awaitExpectToThrowFailure(async () => {
1829 + await act(() => {
1830 + root.render(<App />);
1831 + });
1832 + });
1833 +
1834 + expect(message).toMatchInlineSnapshot(`
1835 + "asserConsoleLogsCleared(expected)
1836 +
1837 + console.error was called without assertConsoleErrorDev:
1838 + + A
1839 + + B
1840 + + C
1841 +
1842 + You must call one of the assertConsoleDev helpers between each act call."
1843 + `);
1844 + });
1845 +
1846 + it('fails if act is called without any assertConsoleDev helpers', async () => {
1847 + const Yield = ({id}) => {
1848 + console.log(id);
1849 + console.warn(id);
1850 + console.error(id);
1851 + return id;
1852 + };
1853 +
1854 + function App() {
1855 + return (
1856 + <div>
1857 + <Yield id="A" />
1858 + <Yield id="B" />
1859 + <Yield id="C" />
1860 + </div>
1861 + );
1862 + }
1863 +
1864 + const root = ReactNoop.createRoot();
1865 + await act(() => {
1866 + root.render(<App />);
1867 + });
1868 + const message = await awaitExpectToThrowFailure(async () => {
1869 + await act(() => {
1870 + root.render(<App />);
1871 + });
1872 + });
1873 +
1874 + expect(message).toMatchInlineSnapshot(`
1875 + "asserConsoleLogsCleared(expected)
1876 +
1877 + console.log was called without assertConsoleLogDev:
1878 + + A
1879 + + B
1880 + + C
1881 +
1882 + console.warn was called without assertConsoleWarnDev:
1883 + + A
1884 + + B
1885 + + C
1886 +
1887 + console.error was called without assertConsoleErrorDev:
1888 + + A
1889 + + B
1890 + + C
1891 +
1892 + You must call one of the assertConsoleDev helpers between each act call."
1893 + `);
1894 + });
1895 +
1896 + // @gate __DEV__
1897 + it('fails if first expected error is not included', () => {
1898 + const message = expectToThrowFailure(() => {
1899 + console.error('Wow \n in div');
1900 + console.error('Bye \n in div');
1901 + assertConsoleErrorDev(['Hi', 'Wow', 'Bye']);
1902 + });
1903 + expect(message).toMatchInlineSnapshot(`
1904 + "assertConsoleErrorDev(expected)
1905 +
1906 + Unexpected error(s) recorded.
1907 +
1908 + - Expected errors
1909 + + Received errors
1910 +
1911 + - Hi
1912 + - Wow
1913 + - Bye
1914 + + Wow <component stack>
1915 + + Bye <component stack>"
1916 + `);
1917 + });
1918 +
1919 + // @gate __DEV__
1920 + it('fails if middle expected error is not included', () => {
1921 + const message = expectToThrowFailure(() => {
1922 + console.error('Hi \n in div');
1923 + console.error('Bye \n in div');
1924 + assertConsoleErrorDev(['Hi', 'Wow', 'Bye']);
1925 + });
1926 + expect(message).toMatchInlineSnapshot(`
1927 + "assertConsoleErrorDev(expected)
1928 +
1929 + Unexpected error(s) recorded.
1930 +
1931 + - Expected errors
1932 + + Received errors
1933 +
1934 + - Hi
1935 + - Wow
1936 + - Bye
1937 + + Hi <component stack>
1938 + + Bye <component stack>"
1939 + `);
1940 + });
1941 +
1942 + // @gate __DEV__
1943 + it('fails if last expected error is not included', () => {
1944 + const message = expectToThrowFailure(() => {
1945 + console.error('Hi \n in div');
1946 + console.error('Wow \n in div');
1947 + assertConsoleErrorDev(['Hi', 'Wow', 'Bye']);
1948 + });
1949 + expect(message).toMatchInlineSnapshot(`
1950 + "assertConsoleErrorDev(expected)
1951 +
1952 + Expected error was not recorded.
1953 +
1954 + - Expected errors
1955 + + Received errors
1956 +
1957 + - Hi
1958 + - Wow
1959 + - Bye
1960 + + Hi <component stack>
1961 + + Wow <component stack>"
1962 + `);
1963 + });
1964 +
1965 + // @gate __DEV__
1966 + it('fails if first received error is not included', () => {
1967 + const message = expectToThrowFailure(() => {
1968 + console.error('Hi \n in div');
1969 + console.error('Wow \n in div');
1970 + console.error('Bye \n in div');
1971 + assertConsoleErrorDev(['Wow', 'Bye']);
1972 + });
1973 + expect(message).toMatchInlineSnapshot(`
1974 + "assertConsoleErrorDev(expected)
1975 +
1976 + Unexpected error(s) recorded.
1977 +
1978 + - Expected errors
1979 + + Received errors
1980 +
1981 + - Wow
1982 + - Bye
1983 + + Hi <component stack>
1984 + + Wow <component stack>
1985 + + Bye <component stack>"
1986 + `);
1987 + });
1988 +
1989 + // @gate __DEV__
1990 + it('fails if middle received error is not included', () => {
1991 + const message = expectToThrowFailure(() => {
1992 + console.error('Hi \n in div');
1993 + console.error('Wow \n in div');
1994 + console.error('Bye \n in div');
1995 + assertConsoleErrorDev(['Hi', 'Bye']);
1996 + });
1997 + expect(message).toMatchInlineSnapshot(`
1998 + "assertConsoleErrorDev(expected)
1999 +
2000 + Unexpected error(s) recorded.
2001 +
2002 + - Expected errors
2003 + + Received errors
2004 +
2005 + - Hi
2006 + - Bye
2007 + + Hi <component stack>
2008 + + Wow <component stack>
2009 + + Bye <component stack>"
2010 + `);
2011 + });
2012 +
2013 + // @gate __DEV__
2014 + it('fails if last received error is not included', () => {
2015 + const message = expectToThrowFailure(() => {
2016 + console.error('Hi \n in div');
2017 + console.error('Wow \n in div');
2018 + console.error('Bye \n in div');
2019 + assertConsoleErrorDev(['Hi', 'Wow']);
2020 + });
2021 + expect(message).toMatchInlineSnapshot(`
2022 + "assertConsoleErrorDev(expected)
2023 +
2024 + Unexpected error(s) recorded.
2025 +
2026 + - Expected errors
2027 + + Received errors
2028 +
2029 + - Hi
2030 + - Wow
2031 + + Hi <component stack>
2032 + + Wow <component stack>
2033 + + Bye <component stack>"
2034 + `);
2035 + });
2036 + // @gate __DEV__
2037 + it('fails if only error does not contain a stack', () => {
2038 + const message = expectToThrowFailure(() => {
2039 + console.error('Hello');
2040 + assertConsoleErrorDev(['Hello']);
2041 + });
2042 + expect(message).toMatchInlineSnapshot(`
2043 + "assertConsoleErrorDev(expected)
2044 +
2045 + Missing component stack for:
2046 + "Hello"
2047 +
2048 + If this error should omit a component stack, pass [log, {withoutStack: true}].
2049 + If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2050 + `);
2051 + });
2052 +
2053 + // @gate __DEV__
2054 + it('fails if first error does not contain a stack', () => {
2055 + const message = expectToThrowFailure(() => {
2056 + console.error('Hello\n in div');
2057 + console.error('Good day\n in div');
2058 + console.error('Bye');
2059 + assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
2060 + });
2061 + expect(message).toMatchInlineSnapshot(`
2062 + "assertConsoleErrorDev(expected)
2063 +
2064 + Missing component stack for:
2065 + "Bye"
2066 +
2067 + If this error should omit a component stack, pass [log, {withoutStack: true}].
2068 + If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2069 + `);
2070 + });
2071 + // @gate __DEV__
2072 + it('fails if last error does not contain a stack', () => {
2073 + const message = expectToThrowFailure(() => {
2074 + console.error('Hello');
2075 + console.error('Good day\n in div');
2076 + console.error('Bye\n in div');
2077 + assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
2078 + });
2079 + expect(message).toMatchInlineSnapshot(`
2080 + "assertConsoleErrorDev(expected)
2081 +
2082 + Missing component stack for:
2083 + "Hello"
2084 +
2085 + If this error should omit a component stack, pass [log, {withoutStack: true}].
2086 + If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2087 + `);
2088 + });
2089 + // @gate __DEV__
2090 + it('fails if middle error does not contain a stack', () => {
2091 + const message = expectToThrowFailure(() => {
2092 + console.error('Hello\n in div');
2093 + console.error('Good day');
2094 + console.error('Bye\n in div');
2095 + assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
2096 + });
2097 + expect(message).toMatchInlineSnapshot(`
2098 + "assertConsoleErrorDev(expected)
2099 +
2100 + Missing component stack for:
2101 + "Good day"
2102 +
2103 + If this error should omit a component stack, pass [log, {withoutStack: true}].
2104 + If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2105 + `);
2106 + });
2107 + // @gate __DEV__
2108 + it('fails if all errors do not contain a stack', () => {
2109 + const message = expectToThrowFailure(() => {
2110 + console.error('Hello');
2111 + console.error('Good day');
2112 + console.error('Bye');
2113 + assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
2114 + });
2115 + expect(message).toMatchInlineSnapshot(`
2116 + "assertConsoleErrorDev(expected)
2117 +
2118 + Missing component stack for:
2119 + "Hello"
2120 +
2121 + Missing component stack for:
2122 + "Good day"
2123 +
2124 + Missing component stack for:
2125 + "Bye"
2126 +
2127 + If this error should omit a component stack, pass [log, {withoutStack: true}].
2128 + If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2129 + `);
2130 + });
2131 +
2132 + describe('global withoutStack', () => {
2133 + // @gate __DEV__
2134 + it('passes if errors without stack explicitly opt out', () => {
2135 + console.error('Hello');
2136 + assertConsoleErrorDev(['Hello'], {withoutStack: true});
2137 +
2138 + console.error('Hello');
2139 + console.error('Good day');
2140 + console.error('Bye');
2141 +
2142 + assertConsoleErrorDev(['Hello', 'Good day', 'Bye'], {
2143 + withoutStack: true,
2144 + });
2145 + });
2146 +
2147 + // @gate __DEV__
2148 + it('fails if withoutStack is invalid null value', () => {
2149 + const message = expectToThrowFailure(() => {
2150 + console.error('Hi');
2151 + assertConsoleErrorDev(['Hi'], {withoutStack: null});
2152 + });
2153 + expect(message).toMatchInlineSnapshot(`
2154 + "assertConsoleErrorDev(expected)
2155 +
2156 + The second argument must be {withoutStack: true}.
2157 +
2158 + Instead received {"withoutStack":null}."
2159 + `);
2160 + assertConsoleErrorDev(['Hi'], {withoutStack: true});
2161 + });
2162 +
2163 + // @gate __DEV__
2164 + it('fails if withoutStack is invalid {} value', () => {
2165 + const message = expectToThrowFailure(() => {
2166 + console.error('Hi');
2167 + assertConsoleErrorDev(['Hi'], {withoutStack: {}});
2168 + });
2169 + expect(message).toMatchInlineSnapshot(`
2170 + "assertConsoleErrorDev(expected)
2171 +
2172 + The second argument must be {withoutStack: true}.
2173 +
2174 + Instead received {"withoutStack":{}}."
2175 + `);
2176 + assertConsoleErrorDev(['Hi'], {withoutStack: true});
2177 + });
2178 +
2179 + // @gate __DEV__
2180 + it('fails if withoutStack is invalid string value', () => {
2181 + const message = expectToThrowFailure(() => {
2182 + console.error('Hi');
2183 + assertConsoleErrorDev(['Hi'], {withoutStack: 'haha'});
2184 + });
2185 + expect(message).toMatchInlineSnapshot(`
2186 + "assertConsoleErrorDev(expected)
2187 +
2188 + The second argument must be {withoutStack: true}.
2189 +
2190 + Instead received {"withoutStack":"haha"}."
2191 + `);
2192 + assertConsoleErrorDev(['Hi'], {withoutStack: true});
2193 + });
2194 +
2195 + // @gate __DEV__
2196 + it('fails if only error is not expected to have a stack, but does', () => {
2197 + const message = expectToThrowFailure(() => {
2198 + console.error('Hello\n in div');
2199 + assertConsoleErrorDev(['Hello'], {withoutStack: true});
2200 + });
2201 + expect(message).toMatchInlineSnapshot(`
2202 + "assertConsoleErrorDev(expected)
2203 +
2204 + Unexpected component stack for:
2205 + "Hello <component stack>"
2206 +
2207 + If this error should include a component stack, remove {withoutStack: true} from this error.
2208 + If all errors should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleErrorDev call."
2209 + `);
2210 + });
2211 +
2212 + // @gate __DEV__
2213 + it('fails if errors are not expected to have a stack, but some do', () => {
2214 + const message = expectToThrowFailure(() => {
2215 + console.error('Hello\n in div');
2216 + console.error('Good day');
2217 + console.error('Bye\n in div');
2218 + assertConsoleErrorDev(['Hello', 'Good day', 'Bye'], {
2219 + withoutStack: true,
2220 + });
2221 + });
2222 + expect(message).toMatchInlineSnapshot(`
2223 + "assertConsoleErrorDev(expected)
2224 +
2225 + Unexpected component stack for:
2226 + "Hello <component stack>"
2227 +
2228 + Unexpected component stack for:
2229 + "Bye <component stack>"
2230 +
2231 + If this error should include a component stack, remove {withoutStack: true} from this error.
2232 + If all errors should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleErrorDev call."
2233 + `);
2234 + });
2235 + });
2236 + describe('local withoutStack', () => {
2237 + // @gate __DEV__
2238 + it('passes when expected withoutStack logs matches the actual logs', () => {
2239 + console.error('Hello\n in div');
2240 + console.error('Good day');
2241 + console.error('Bye\n in div');
2242 + assertConsoleErrorDev([
2243 + 'Hello',
2244 + ['Good day', {withoutStack: true}],
2245 + 'Bye',
2246 + ]);
2247 + });
2248 +
2249 + // @gate __DEV__
2250 + it('fails if withoutStack is invalid null value', () => {
2251 + const message = expectToThrowFailure(() => {
2252 + console.error('Hi');
2253 + assertConsoleErrorDev([['Hi', {withoutStack: null}]]);
2254 + });
2255 + expect(message).toMatchInlineSnapshot(`
2256 + "assertConsoleErrorDev(expected)
2257 +
2258 + Log entries that are arrays must be of the form [string, {withoutStack: true}]
2259 +
2260 + Instead received [string, {"withoutStack":null}]."
2261 + `);
2262 + });
2263 +
2264 + // @gate __DEV__
2265 + it('fails if withoutStack is invalid {} value', () => {
2266 + const message = expectToThrowFailure(() => {
2267 + console.error('Hi');
2268 + assertConsoleErrorDev([['Hi', {withoutStack: {}}]]);
2269 + });
2270 + expect(message).toMatchInlineSnapshot(`
2271 + "assertConsoleErrorDev(expected)
2272 +
2273 + Log entries that are arrays must be of the form [string, {withoutStack: true}]
2274 +
2275 + Instead received [string, {"withoutStack":{}}]."
2276 + `);
2277 + });
2278 +
2279 + // @gate __DEV__
2280 + it('fails if withoutStack is invalid string value', () => {
2281 + const message = expectToThrowFailure(() => {
2282 + console.error('Hi');
2283 + assertConsoleErrorDev([['Hi', {withoutStack: 'haha'}]]);
2284 + });
2285 + expect(message).toMatchInlineSnapshot(`
2286 + "assertConsoleErrorDev(expected)
2287 +
2288 + Log entries that are arrays must be of the form [string, {withoutStack: true}]
2289 +
2290 + Instead received [string, {"withoutStack":"haha"}]."
2291 + `);
2292 + });
2293 +
2294 + // @gate __DEV__
2295 + it('fails if withoutStack is invalid number value', () => {
2296 + const message = expectToThrowFailure(() => {
2297 + console.error('Hi');
2298 + assertConsoleErrorDev([['Hi', {withoutStack: 4}]]);
2299 + });
2300 + expect(message).toMatchInlineSnapshot(`
2301 + "assertConsoleErrorDev(expected)
2302 +
2303 + Log entries that are arrays must be of the form [string, {withoutStack: true}]
2304 +
2305 + Instead received [string, {"withoutStack":4}]."
2306 + `);
2307 + });
2308 +
2309 + // @gate __DEV__
2310 + it('fails if you forget to wrap local withoutStack in array', () => {
2311 + const message = expectToThrowFailure(() => {
2312 + console.error('Hello\n in div');
2313 + console.error('Bye\n in div');
2314 + assertConsoleErrorDev(['Hello', {withoutStack: true}, 'Bye']);
2315 + });
2316 + expect(message).toMatchInlineSnapshot(`
2317 + "assertConsoleErrorDev(expected)
2318 +
2319 + Did you forget to wrap a log with withoutStack in an array?
2320 +
2321 + The expected message for assertConsoleErrorDev() must be a string or an array of length 2.
2322 +
2323 + Instead received {"withoutStack":true}."
2324 + `);
2325 + });
2326 +
2327 + // @gate __DEV__
2328 + it('fails if you wrap in an array unnecessarily', () => {
2329 + const message = expectToThrowFailure(() => {
2330 + console.error('Hello');
2331 + assertConsoleErrorDev([['Hello']]);
2332 + });
2333 + expect(message).toMatchInlineSnapshot(`
2334 + "assertConsoleErrorDev(expected)
2335 +
2336 + Did you forget to remove the array around the log?
2337 +
2338 + The expected message for assertConsoleErrorDev() must be a string or an array of length 2, but there's only one item in the array. If this is intentional, remove the extra array."
2339 + `);
2340 + });
2341 +
2342 + // @gate __DEV__
2343 + it('fails if only error is not expected to have a stack, but does', () => {
2344 + const message = expectToThrowFailure(() => {
2345 + console.error('Hello\n in div');
2346 + assertConsoleErrorDev([['Hello', {withoutStack: true}]]);
2347 + });
2348 + expect(message).toMatchInlineSnapshot(`
2349 + "assertConsoleErrorDev(expected)
2350 +
2351 + Unexpected component stack for:
2352 + "Hello <component stack>"
2353 +
2354 + If this error should include a component stack, remove {withoutStack: true} from this error.
2355 + If all errors should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleErrorDev call."
2356 + `);
2357 + });
2358 +
2359 + // @gate __DEV__
2360 + it('fails if errors are not expected to have a stack, but some do', () => {
2361 + const message = expectToThrowFailure(() => {
2362 + console.error('Hello\n in div');
2363 + console.error('Good day');
2364 + console.error('Bye\n in div');
2365 + assertConsoleErrorDev([
2366 + [
2367 + 'Hello',
2368 + {
2369 + withoutStack: true,
2370 + },
2371 + ],
2372 + 'Good day',
2373 + [
2374 + 'Bye',
2375 + {
2376 + withoutStack: true,
2377 + },
2378 + ],
2379 + ]);
2380 + });
2381 + expect(message).toMatchInlineSnapshot(`
2382 + "assertConsoleErrorDev(expected)
2383 +
2384 + Unexpected component stack for:
2385 + "Hello <component stack>"
2386 +
2387 + Unexpected component stack for:
2388 + "Bye <component stack>"
2389 +
2390 + If this error should include a component stack, remove {withoutStack: true} from this error.
2391 + If all errors should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleErrorDev call."
2392 + `);
2393 + });
2394 + });
2395 +
2396 + // @gate __DEV__
2397 + it('fails if the args is greater than %s argument number', () => {
2398 + const message = expectToThrowFailure(() => {
2399 + console.error('Hi %s', 'Sara', 'extra');
2400 + assertConsoleErrorDev(['Hi'], {withoutStack: true});
2401 + });
2402 + expect(message).toMatchInlineSnapshot(`
2403 + "assertConsoleErrorDev(expected)
2404 +
2405 + Received 2 arguments for a message with 1 placeholders:
2406 + "Hi %s""
2407 + `);
2408 + });
2409 +
2410 + // @gate __DEV__
2411 + it('fails if the args is greater than %s argument number for multiple errors', () => {
2412 + const message = expectToThrowFailure(() => {
2413 + console.error('Hi %s', 'Sara', 'extra');
2414 + console.error('Bye %s', 'Sara', 'extra');
2415 + assertConsoleErrorDev(['Hi', 'Bye'], {withoutStack: true});
2416 + });
2417 + expect(message).toMatchInlineSnapshot(`
2418 + "assertConsoleErrorDev(expected)
2419 +
2420 + Received 2 arguments for a message with 1 placeholders:
2421 + "Hi %s"
2422 +
2423 + Received 2 arguments for a message with 1 placeholders:
2424 + "Bye %s""
2425 + `);
2426 + });
2427 +
2428 + // @gate __DEV__
2429 + it('fails if the %s argument number is greater than args', () => {
2430 + const message = expectToThrowFailure(() => {
2431 + console.error('Hi %s');
2432 + assertConsoleErrorDev(['Hi'], {withoutStack: true});
2433 + });
2434 + expect(message).toMatchInlineSnapshot(`
2435 + "assertConsoleErrorDev(expected)
2436 +
2437 + Received 0 arguments for a message with 1 placeholders:
2438 + "Hi %s""
2439 + `);
2440 + });
2441 +
2442 + // @gate __DEV__
2443 + it('fails if the %s argument number is greater than args for multiple errors', () => {
2444 + const message = expectToThrowFailure(() => {
2445 + console.error('Hi %s');
2446 + console.error('Bye %s');
2447 + assertConsoleErrorDev(['Hi', 'Bye'], {withoutStack: true});
2448 + });
2449 + expect(message).toMatchInlineSnapshot(`
2450 + "assertConsoleErrorDev(expected)
2451 +
2452 + Received 0 arguments for a message with 1 placeholders:
2453 + "Hi %s"
2454 +
2455 + Received 0 arguments for a message with 1 placeholders:
2456 + "Bye %s""
2457 + `);
2458 + });
2459 +
2460 + // @gate __DEV__
2461 + it('fails if component stack is passed twice', () => {
2462 + const message = expectToThrowFailure(() => {
2463 + console.error('Hi %s%s', '\n in div', '\n in div');
2464 + assertConsoleErrorDev(['Hi']);
2465 + });
2466 + expect(message).toMatchInlineSnapshot(`
2467 + "assertConsoleErrorDev(expected)
2468 +
2469 + Received more than one component stack for a warning:
2470 + "Hi %s%s""
2471 + `);
2472 + });
2473 +
2474 + // @gate __DEV__
2475 + it('fails if multiple logs pass component stack twice', () => {
2476 + const message = expectToThrowFailure(() => {
2477 + console.error('Hi %s%s', '\n in div', '\n in div');
2478 + console.error('Bye %s%s', '\n in div', '\n in div');
2479 + assertConsoleErrorDev(['Hi', 'Bye']);
2480 + });
2481 + expect(message).toMatchInlineSnapshot(`
2482 + "assertConsoleErrorDev(expected)
2483 +
2484 + Received more than one component stack for a warning:
2485 + "Hi %s%s"
2486 +
2487 + Received more than one component stack for a warning:
2488 + "Bye %s%s""
2489 + `);
2490 + });
2491 +
2492 + // @gate __DEV__
2493 + it('fails if multiple strings are passed without an array wrapper for single log', () => {
2494 + const message = expectToThrowFailure(() => {
2495 + console.error('Hi \n in div');
2496 + console.error('Bye \n in div');
2497 + assertConsoleErrorDev('Hi', 'Bye');
2498 + });
2499 + expect(message).toMatchInlineSnapshot(`
2500 + "assertConsoleErrorDev(expected)
2501 +
2502 + Expected messages should be an array of strings but was given type "string"."
2503 + `);
2504 + assertConsoleErrorDev(['Hi', 'Bye']);
2505 + });
2506 +
2507 + // @gate __DEV__
2508 + it('fails if multiple strings are passed without an array wrapper for multiple logs', () => {
2509 + const message = expectToThrowFailure(() => {
2510 + console.error('Hi \n in div');
2511 + console.error('Bye \n in div');
2512 + assertConsoleErrorDev('Hi', 'Bye');
2513 + });
2514 + expect(message).toMatchInlineSnapshot(`
2515 + "assertConsoleErrorDev(expected)
2516 +
2517 + Expected messages should be an array of strings but was given type "string"."
2518 + `);
2519 + assertConsoleErrorDev(['Hi', 'Bye']);
2520 + });
2521 +
2522 + // @gate __DEV__
2523 + it('fails on more than two arguments', () => {
2524 + const message = expectToThrowFailure(() => {
2525 + console.error('Hi \n in div');
2526 + console.error('Wow \n in div');
2527 + console.error('Bye \n in div');
2528 + assertConsoleErrorDev('Hi', undefined, 'Bye');
2529 + });
2530 + expect(message).toMatchInlineSnapshot(`
2531 + "assertConsoleErrorDev(expected)
2532 +
2533 + Expected messages should be an array of strings but was given type "string"."
2534 + `);
2535 + assertConsoleErrorDev(['Hi', 'Wow', 'Bye']);
2536 + });
2537 +
2538 + it('should fail if waitFor is called before asserting', async () => {
2539 + const Yield = ({id}) => {
2540 + Scheduler.log(id);
2541 + return id;
2542 + };
2543 +
2544 + const root = ReactNoop.createRoot();
2545 + startTransition(() => {
2546 + root.render(
2547 + <div>
2548 + <Yield id="foo" />
2549 + <Yield id="bar" />
2550 + <Yield id="baz" />
2551 + </div>
2552 + );
2553 + });
2554 +
2555 + console.error('Not asserted');
2556 +
2557 + const message = await awaitExpectToThrowFailure(async () => {
2558 + await waitFor(['foo', 'bar']);
2559 + });
2560 + expect(message).toMatchInlineSnapshot(`
2561 + "asserConsoleLogsCleared(expected)
2562 +
2563 + console.error was called without assertConsoleErrorDev:
2564 + + Not asserted
2565 +
2566 + You must call one of the assertConsoleDev helpers between each act call."
2567 + `);
2568 +
2569 + await waitForAll(['foo', 'bar', 'baz']);
2570 + });
2571 +
2572 + test('should fail if waitForThrow is called before asserting', async () => {
2573 + const Yield = ({id}) => {
2574 + Scheduler.log(id);
2575 + return id;
2576 + };
2577 +
2578 + function BadRender() {
2579 + throw new Error('Oh no!');
2580 + }
2581 +
2582 + function App() {
2583 + return (
2584 + <div>
2585 + <Yield id="A" />
2586 + <Yield id="B" />
2587 + <BadRender />
2588 + <Yield id="C" />
2589 + <Yield id="D" />
2590 + </div>
2591 + );
2592 + }
2593 +
2594 + const root = ReactNoop.createRoot();
2595 + root.render(<App />);
2596 +
2597 + console.error('Not asserted');
2598 +
2599 + const message = await awaitExpectToThrowFailure(async () => {
2600 + await waitForThrow('Oh no!');
2601 + });
2602 + expect(message).toMatchInlineSnapshot(`
2603 + "asserConsoleLogsCleared(expected)
2604 +
2605 + console.error was called without assertConsoleErrorDev:
2606 + + Not asserted
2607 +
2608 + You must call one of the assertConsoleDev helpers between each act call."
2609 + `);
2610 +
2611 + await waitForAll(['A', 'B', 'A', 'B']);
2612 + });
2613 +
2614 + test('should fail if waitForPaint is called before asserting', async () => {
2615 + function App({prop}) {
2616 + const deferred = useDeferredValue(prop);
2617 + const text = `Urgent: ${prop}, Deferred: ${deferred}`;
2618 + Scheduler.log(text);
2619 + return text;
2620 + }
2621 +
2622 + const root = ReactNoop.createRoot();
2623 + root.render(<App prop="A" />);
2624 +
2625 + await waitForAll(['Urgent: A, Deferred: A']);
2626 + expect(root).toMatchRenderedOutput('Urgent: A, Deferred: A');
2627 +
2628 + // This update will result in two separate paints: an urgent one, and a
2629 + // deferred one.
2630 + root.render(<App prop="B" />);
2631 +
2632 + console.error('Not asserted');
2633 + const message = await awaitExpectToThrowFailure(async () => {
2634 + await waitForPaint(['Urgent: B, Deferred: A']);
2635 + });
2636 +
2637 + expect(message).toMatchInlineSnapshot(`
2638 + "asserConsoleLogsCleared(expected)
2639 +
2640 + console.error was called without assertConsoleErrorDev:
2641 + + Not asserted
2642 +
2643 + You must call one of the assertConsoleDev helpers between each act call."
2644 + `);
2645 +
2646 + await waitForAll(['Urgent: B, Deferred: A', 'Urgent: B, Deferred: B']);
2647 + });
2648 +
2649 + it('should fail if waitForAll is called before asserting', async () => {
2650 + const Yield = ({id}) => {
2651 + Scheduler.log(id);
2652 + return id;
2653 + };
2654 +
2655 + const root = ReactNoop.createRoot();
2656 + startTransition(() => {
2657 + root.render(
2658 + <div>
2659 + <Yield id="foo" />
2660 + <Yield id="bar" />
2661 + <Yield id="baz" />
2662 + </div>
2663 + );
2664 + });
2665 +
2666 + console.error('Not asserted');
2667 +
2668 + const message = await awaitExpectToThrowFailure(async () => {
2669 + await waitForAll(['foo', 'bar', 'baz']);
2670 + });
2671 + expect(message).toMatchInlineSnapshot(`
2672 + "asserConsoleLogsCleared(expected)
2673 +
2674 + console.error was called without assertConsoleErrorDev:
2675 + + Not asserted
2676 +
2677 + You must call one of the assertConsoleDev helpers between each act call."
2678 + `);
2679 +
2680 + await waitForAll(['foo', 'bar', 'baz']);
2681 + });
2682 + it('should fail if toMatchRenderedOutput is called before asserting', async () => {
2683 + const Yield = ({id}) => {
2684 + Scheduler.log(id);
2685 + console.error('Not asserted');
2686 + return id;
2687 + };
2688 +
2689 + const root = ReactNoop.createRoot();
2690 + startTransition(() => {
2691 + root.render(
2692 + <div>
2693 + <Yield id="foo" />
2694 + <Yield id="bar" />
2695 + <Yield id="baz" />
2696 + </div>
2697 + );
2698 + });
2699 +
2700 + assertLog([]);
2701 +
2702 + await waitForAll(['foo', 'bar', 'baz']);
2703 + const message = expectToThrowFailure(() => {
2704 + expect(root).toMatchRenderedOutput(<div>foobarbaz</div>);
2705 + });
2706 + expect(message).toMatchInlineSnapshot(`
2707 + "asserConsoleLogsCleared(expected)
2708 +
2709 + console.error was called without assertConsoleErrorDev:
2710 + + Not asserted
2711 + + Not asserted
2712 + + Not asserted
2713 +
2714 + You must call one of the assertConsoleDev helpers between each act call."
2715 + `);
2716 +
2717 + expect(root).toMatchRenderedOutput(<div>foobarbaz</div>);
2718 + });
2719 + });
2720 +});
packages/internal-test-utils/consoleMock.js
+431 -14
@@ -10,14 +10,28 @@ const chalk = require('chalk');
10 const util = require('util');
11 const shouldIgnoreConsoleError = require('./shouldIgnoreConsoleError');
12 const shouldIgnoreConsoleWarn = require('./shouldIgnoreConsoleWarn');
13 +import {diff} from 'jest-diff';
14 +import {printReceived} from 'jest-matcher-utils';
15
14 -const unexpectedErrorCallStacks = [];
15 -const unexpectedWarnCallStacks = [];
16 -const unexpectedLogCallStacks = [];
16 +// Annoying: need to store the log array on the global or it would
17 +// change reference whenever you call jest.resetModules after patch.
18 +const loggedErrors = (global.__loggedErrors = global.__loggedErrors || []);
19 +const loggedWarns = (global.__loggedWarns = global.__loggedWarns || []);
20 +const loggedLogs = (global.__loggedLogs = global.__loggedLogs || []);
21
18 -// TODO: Consider consolidating this with `yieldValue`. In both cases, tests
19 -// should not be allowed to exit without asserting on the entire log.
20 -const patchConsoleMethod = (methodName, unexpectedConsoleCallStacks) => {
22 +// TODO: delete these after code modding away from toWarnDev.
23 +const unexpectedErrorCallStacks = (global.__unexpectedErrorCallStacks =
24 + global.__unexpectedErrorCallStacks || []);
25 +const unexpectedWarnCallStacks = (global.__unexpectedWarnCallStacks =
26 + global.__unexpectedWarnCallStacks || []);
27 +const unexpectedLogCallStacks = (global.__unexpectedLogCallStacks =
28 + global.__unexpectedLogCallStacks || []);
29 +
30 +const patchConsoleMethod = (
31 + methodName,
32 + unexpectedConsoleCallStacks,
33 + logged,
34 +) => {
35 const newMethod = function (format, ...args) {
36 // Ignore uncaught errors reported by jsdom
37 // and React addendums because they're too noisy.
@@ -38,6 +52,7 @@ const patchConsoleMethod = (methodName, unexpectedConsoleCallStacks) => {
52 stack.slice(stack.indexOf('\n') + 1),
53 util.format(format, ...args),
54 ]);
55 + logged.push([format, ...args]);
56 };
57
58 console[methodName] = newMethod;
@@ -75,8 +90,7 @@ const flushUnexpectedConsoleCalls = (
90 `console.${methodName}()`,
91 )}.\n\n` +
92 `If the ${type} is expected, test for it explicitly by:\n` +
78 - `1. Using the ${chalk.bold('.' + expectedMatcher + '()')} ` +
79 - `matcher, or...\n` +
93 + `1. Using ${chalk.bold(expectedMatcher + '()')} or...\n` +
94 `2. Mock it out using ${chalk.bold(
95 'spyOnDev',
96 )}(console, '${methodName}') or ${chalk.bold(
@@ -91,13 +105,21 @@ let errorMethod;
105 let warnMethod;
106 let logMethod;
107 export function patchConsoleMethods({includeLog} = {includeLog: false}) {
94 - errorMethod = patchConsoleMethod('error', unexpectedErrorCallStacks);
95 - warnMethod = patchConsoleMethod('warn', unexpectedWarnCallStacks);
108 + errorMethod = patchConsoleMethod(
109 + 'error',
110 + unexpectedErrorCallStacks,
111 + loggedErrors,
112 + );
113 + warnMethod = patchConsoleMethod(
114 + 'warn',
115 + unexpectedWarnCallStacks,
116 + loggedWarns,
117 + );
118
119 // Only assert console.log isn't called in CI so you can debug tests in DEV.
120 // The matchers will still work in DEV, so you can assert locally.
121 if (includeLog) {
100 - logMethod = patchConsoleMethod('log', unexpectedLogCallStacks);
122 + logMethod = patchConsoleMethod('log', unexpectedLogCallStacks, loggedLogs);
123 }
124 }
125
@@ -105,20 +127,20 @@ export function flushAllUnexpectedConsoleCalls() {
127 flushUnexpectedConsoleCalls(
128 errorMethod,
129 'error',
108 - 'toErrorDev',
130 + 'assertConsoleErrorDev',
131 unexpectedErrorCallStacks,
132 );
133 flushUnexpectedConsoleCalls(
134 warnMethod,
135 'warn',
114 - 'toWarnDev',
136 + 'assertConsoleWarnDev',
137 unexpectedWarnCallStacks,
138 );
139 if (logMethod) {
140 flushUnexpectedConsoleCalls(
141 logMethod,
142 'log',
121 - 'toLogDev',
143 + 'assertConsoleLogDev',
144 unexpectedLogCallStacks,
145 );
146 unexpectedLogCallStacks.length = 0;
@@ -128,9 +150,404 @@ export function flushAllUnexpectedConsoleCalls() {
150 }
151
152 export function resetAllUnexpectedConsoleCalls() {
153 + loggedErrors.length = 0;
154 + loggedWarns.length = 0;
155 unexpectedErrorCallStacks.length = 0;
156 unexpectedWarnCallStacks.length = 0;
157 if (logMethod) {
158 + loggedLogs.length = 0;
159 unexpectedLogCallStacks.length = 0;
160 }
161 }
162 +
163 +export function clearLogs() {
164 + const logs = Array.from(loggedLogs);
165 + unexpectedLogCallStacks.length = 0;
166 + loggedLogs.length = 0;
167 + return logs;
168 +}
169 +
170 +export function clearWarnings() {
171 + const warnings = Array.from(loggedWarns);
172 + unexpectedWarnCallStacks.length = 0;
173 + loggedWarns.length = 0;
174 + return warnings;
175 +}
176 +
177 +export function clearErrors() {
178 + const errors = Array.from(loggedErrors);
179 + unexpectedErrorCallStacks.length = 0;
180 + loggedErrors.length = 0;
181 + return errors;
182 +}
183 +
184 +export function assertConsoleLogsCleared() {
185 + const logs = clearLogs();
186 + const warnings = clearWarnings();
187 + const errors = clearErrors();
188 +
189 + if (logs.length > 0 || errors.length > 0 || warnings.length > 0) {
190 + let message = `${chalk.dim('asserConsoleLogsCleared')}(${chalk.red(
191 + 'expected',
192 + )})\n`;
193 +
194 + if (logs.length > 0) {
195 + message += `\nconsole.log was called without assertConsoleLogDev:\n${diff(
196 + '',
197 + logs.join('\n'),
198 + {
199 + omitAnnotationLines: true,
200 + },
201 + )}\n`;
202 + }
203 +
204 + if (warnings.length > 0) {
205 + message += `\nconsole.warn was called without assertConsoleWarnDev:\n${diff(
206 + '',
207 + warnings.join('\n'),
208 + {
209 + omitAnnotationLines: true,
210 + },
211 + )}\n`;
212 + }
213 + if (errors.length > 0) {
214 + message += `\nconsole.error was called without assertConsoleErrorDev:\n${diff(
215 + '',
216 + errors.join('\n'),
217 + {
218 + omitAnnotationLines: true,
219 + },
220 + )}\n`;
221 + }
222 +
223 + message += `\nYou must call one of the assertConsoleDev helpers between each act call.`;
224 +
225 + const error = Error(message);
226 + Error.captureStackTrace(error, assertConsoleLogsCleared);
227 + throw error;
228 + }
229 +}
230 +
231 +function replaceComponentStack(str) {
232 + if (typeof str !== 'string') {
233 + return str;
234 + }
235 + // This special case exists only for the special source location in
236 + // ReactElementValidator. That will go away if we remove source locations.
237 + str = str.replace(/Check your code at .+?:\d+/g, 'Check your code at **');
238 + // V8 format:
239 + // at Component (/path/filename.js:123:45)
240 + // React format:
241 + // in Component (at filename.js:123)
242 + return str.replace(/\n +(?:at|in) ([\S]+)[^\n]*.*/, function (m, name) {
243 + return chalk.dim(' <component stack>');
244 + });
245 +}
246 +
247 +const isLikelyAComponentStack = message =>
248 + typeof message === 'string' &&
249 + (message.indexOf('<component stack>') > -1 ||
250 + message.includes('\n in ') ||
251 + message.includes('\n at '));
252 +
253 +export function createLogAssertion(
254 + consoleMethod,
255 + matcherName,
256 + clearObservedErrors,
257 +) {
258 + function logName() {
259 + switch (consoleMethod) {
260 + case 'log':
261 + return 'log';
262 + case 'error':
263 + return 'error';
264 + case 'warn':
265 + return 'warning';
266 + }
267 + }
268 +
269 + return function assertConsoleLog(expectedMessages, options = {}) {
270 + if (__DEV__) {
271 + // eslint-disable-next-line no-inner-declarations
272 + function throwFormattedError(message) {
273 + const error = new Error(
274 + `${chalk.dim(matcherName)}(${chalk.red(
275 + 'expected',
276 + )})\n\n${message.trim()}`,
277 + );
278 + Error.captureStackTrace(error, assertConsoleLog);
279 + throw error;
280 + }
281 +
282 + // Warn about incorrect usage first arg.
283 + if (!Array.isArray(expectedMessages)) {
284 + throwFormattedError(
285 + `Expected messages should be an array of strings ` +
286 + `but was given type "${typeof expectedMessages}".`,
287 + );
288 + }
289 +
290 + // Warn about incorrect usage second arg.
291 + if (options != null) {
292 + if (typeof options !== 'object' || Array.isArray(options)) {
293 + throwFormattedError(
294 + `The second argument should be an object. ` +
295 + 'Did you forget to wrap the messages into an array?',
296 + );
297 + }
298 + }
299 +
300 + const withoutStack = options.withoutStack;
301 +
302 + // Warn about invalid global withoutStack values.
303 + if (consoleMethod === 'log' && withoutStack !== undefined) {
304 + throwFormattedError(
305 + `Do not pass withoutStack to assertConsoleLogDev, console.log does not have component stacks.`,
306 + );
307 + } else if (withoutStack !== undefined && withoutStack !== true) {
308 + // withoutStack can only have a value true.
309 + throwFormattedError(
310 + `The second argument must be {withoutStack: true}.` +
311 + `\n\nInstead received ${JSON.stringify(options)}.`,
312 + );
313 + }
314 +
315 + const observedLogs = clearObservedErrors();
316 + const receivedLogs = [];
317 + const missingExpectedLogs = Array.from(expectedMessages);
318 +
319 + const unexpectedLogs = [];
320 + const unexpectedMissingComponentStack = [];
321 + const unexpectedIncludingComponentStack = [];
322 + const logsMismatchingFormat = [];
323 + const logsWithExtraComponentStack = [];
324 +
325 + // Loop over all the observed logs to determine:
326 + // - Which expected logs are missing
327 + // - Which received logs are unexpected
328 + // - Which logs have a component stack
329 + // - Which logs have the wrong format
330 + // - Which logs have extra stacks
331 + for (let index = 0; index < observedLogs.length; index++) {
332 + const log = observedLogs[index];
333 + const [format, ...args] = log;
334 + const message = util.format(format, ...args);
335 +
336 + // Ignore uncaught errors reported by jsdom
337 + // and React addendums because they're too noisy.
338 + if (shouldIgnoreConsoleError(format, args)) {
339 + return;
340 + }
341 +
342 + let expectedMessage;
343 + let expectedWithoutStack;
344 + const expectedMessageOrArray = expectedMessages[index];
345 + if (
346 + expectedMessageOrArray != null &&
347 + Array.isArray(expectedMessageOrArray)
348 + ) {
349 + // Should be in the local form assert([['log', {withoutStack: true}]])
350 +
351 + // Some validations for common mistakes.
352 + if (expectedMessageOrArray.length === 1) {
353 + throwFormattedError(
354 + `Did you forget to remove the array around the log?` +
355 + `\n\nThe expected message for ${matcherName}() must be a string or an array of length 2, but there's only one item in the array. If this is intentional, remove the extra array.`,
356 + );
357 + } else if (expectedMessageOrArray.length !== 2) {
358 + throwFormattedError(
359 + `The expected message for ${matcherName}() must be a string or an array of length 2. ` +
360 + `Instead received ${expectedMessageOrArray}.`,
361 + );
362 + } else if (consoleMethod === 'log') {
363 + // We don't expect any console.log calls to have a stack.
364 + throwFormattedError(
365 + `Do not pass withoutStack to assertConsoleLogDev logs, console.log does not have component stacks.`,
366 + );
367 + }
368 +
369 + // Format is correct, check the values.
370 + const currentExpectedMessage = expectedMessageOrArray[0];
371 + const currentExpectedOptions = expectedMessageOrArray[1];
372 + if (
373 + typeof currentExpectedMessage !== 'string' ||
374 + typeof currentExpectedOptions !== 'object' ||
375 + currentExpectedOptions.withoutStack !== true
376 + ) {
377 + throwFormattedError(
378 + `Log entries that are arrays must be of the form [string, {withoutStack: true}]` +
379 + `\n\nInstead received [${typeof currentExpectedMessage}, ${JSON.stringify(
380 + currentExpectedOptions,
381 + )}].`,
382 + );
383 + }
384 +
385 + expectedMessage = replaceComponentStack(currentExpectedMessage);
386 + expectedWithoutStack = expectedMessageOrArray[1].withoutStack;
387 + } else if (typeof expectedMessageOrArray === 'string') {
388 + // Should be in the form assert(['log']) or assert(['log'], {withoutStack: true})
389 + expectedMessage = replaceComponentStack(expectedMessageOrArray[0]);
390 + if (consoleMethod === 'log') {
391 + expectedWithoutStack = true;
392 + } else {
393 + expectedWithoutStack = withoutStack;
394 + }
395 + } else if (
396 + typeof expectedMessageOrArray === 'object' &&
397 + expectedMessageOrArray != null &&
398 + expectedMessageOrArray.withoutStack != null
399 + ) {
400 + // Special case for common case of a wrong withoutStack value.
401 + throwFormattedError(
402 + `Did you forget to wrap a log with withoutStack in an array?` +
403 + `\n\nThe expected message for ${matcherName}() must be a string or an array of length 2.` +
404 + `\n\nInstead received ${JSON.stringify(expectedMessageOrArray)}.`,
405 + );
406 + } else if (expectedMessageOrArray != null) {
407 + throwFormattedError(
408 + `The expected message for ${matcherName}() must be a string or an array of length 2. ` +
409 + `Instead received ${JSON.stringify(expectedMessageOrArray)}.`,
410 + );
411 + }
412 +
413 + const normalizedMessage = replaceComponentStack(message);
414 + receivedLogs.push(normalizedMessage);
415 +
416 + // Check the number of %s interpolations.
417 + // We'll fail the test if they mismatch.
418 + let argIndex = 0;
419 + // console.* could have been called with a non-string e.g. `console.error(new Error())`
420 + // eslint-disable-next-line react-internal/safe-string-coercion
421 + String(format).replace(/%s/g, () => argIndex++);
422 + if (argIndex !== args.length) {
423 + logsMismatchingFormat.push({
424 + format,
425 + args,
426 + expectedArgCount: argIndex,
427 + });
428 + }
429 +
430 + // Check for extra component stacks
431 + if (
432 + args.length >= 2 &&
433 + isLikelyAComponentStack(args[args.length - 1]) &&
434 + isLikelyAComponentStack(args[args.length - 2])
435 + ) {
436 + logsWithExtraComponentStack.push({
437 + format,
438 + });
439 + }
440 +
441 + // Main logic to check if log is expected, with the component stack.
442 + if (
443 + normalizedMessage === expectedMessage ||
444 + normalizedMessage.includes(expectedMessage)
445 + ) {
446 + if (isLikelyAComponentStack(normalizedMessage)) {
447 + if (expectedWithoutStack === true) {
448 + unexpectedIncludingComponentStack.push(normalizedMessage);
449 + }
450 + } else if (expectedWithoutStack !== true) {
451 + unexpectedMissingComponentStack.push(normalizedMessage);
452 + }
453 +
454 + // Found expected log, remove it from missing.
455 + missingExpectedLogs.splice(0, 1);
456 + } else {
457 + unexpectedLogs.push(normalizedMessage);
458 + }
459 + }
460 +
461 + // Helper for pretty printing diffs consistently.
462 + // We inline multi-line logs for better diff printing.
463 + // eslint-disable-next-line no-inner-declarations
464 + function printDiff() {
465 + return `${diff(
466 + expectedMessages
467 + .map(message => message.replace('\n', ' '))
468 + .join('\n'),
469 + receivedLogs.map(message => message.replace('\n', ' ')).join('\n'),
470 + {
471 + aAnnotation: `Expected ${logName()}s`,
472 + bAnnotation: `Received ${logName()}s`,
473 + },
474 + )}`;
475 + }
476 +
477 + // Any unexpected warnings should be treated as a failure.
478 + if (unexpectedLogs.length > 0) {
479 + throwFormattedError(
480 + `Unexpected ${logName()}(s) recorded.\n\n${printDiff()}`,
481 + );
482 + }
483 +
484 + // Any remaining messages indicate a failed expectations.
485 + if (missingExpectedLogs.length > 0) {
486 + throwFormattedError(
487 + `Expected ${logName()} was not recorded.\n\n${printDiff()}`,
488 + );
489 + }
490 +
491 + // Any logs that include a component stack but shouldn't.
492 + if (unexpectedIncludingComponentStack.length > 0) {
493 + throwFormattedError(
494 + `${unexpectedIncludingComponentStack
495 + .map(
496 + stack =>
497 + `Unexpected component stack for:\n ${printReceived(stack)}`,
498 + )
499 + .join(
500 + '\n\n',
501 + )}\n\nIf this ${logName()} should include a component stack, remove {withoutStack: true} from this ${logName()}.` +
502 + `\nIf all ${logName()}s should include the component stack, you may need to remove {withoutStack: true} from the ${matcherName} call.`,
503 + );
504 + }
505 +
506 + // Any logs that are missing a component stack without withoutStack.
507 + if (unexpectedMissingComponentStack.length > 0) {
508 + throwFormattedError(
509 + `${unexpectedMissingComponentStack
510 + .map(
511 + stack =>
512 + `Missing component stack for:\n ${printReceived(stack)}`,
513 + )
514 + .join(
515 + '\n\n',
516 + )}\n\nIf this ${logName()} should omit a component stack, pass [log, {withoutStack: true}].` +
517 + `\nIf all ${logName()}s should omit the component stack, add {withoutStack: true} to the ${matcherName} call.`,
518 + );
519 + }
520 +
521 + // Wrong %s formatting is a failure.
522 + // This is a common mistake when creating new warnings.
523 + if (logsMismatchingFormat.length > 0) {
524 + throwFormattedError(
525 + logsMismatchingFormat
526 + .map(
527 + item =>
528 + `Received ${item.args.length} arguments for a message with ${
529 + item.expectedArgCount
530 + } placeholders:\n ${printReceived(item.format)}`,
531 + )
532 + .join('\n\n'),
533 + );
534 + }
535 +
536 + // Duplicate component stacks is a failure.
537 + // This used to be a common mistake when creating new warnings,
538 + // but might not be an issue anymore.
539 + if (logsWithExtraComponentStack.length > 0) {
540 + throwFormattedError(
541 + logsWithExtraComponentStack
542 + .map(
543 + item =>
544 + `Received more than one component stack for a warning:\n ${printReceived(
545 + item.format,
546 + )}`,
547 + )
548 + .join('\n\n'),
549 + );
550 + }
551 + }
552 + };
553 +}
packages/internal-test-utils/internalAct.js
+5
@@ -19,6 +19,7 @@ import type {Thenable} from 'shared/ReactTypes';
19 import * as Scheduler from 'scheduler/unstable_mock';
20
21 import enqueueTask from './enqueueTask';
22 +import {assertConsoleLogsCleared} from './consoleMock';
23 import {diff} from 'jest-diff';
24
25 export let actingUpdatesScopeDepth: number = 0;
@@ -58,6 +59,10 @@ export async function act<T>(scope: () => Thenable<T>): Thenable<T> {
59 throw error;
60 }
61
62 + // We require every `act` call to assert console logs
63 + // with one of the assertion helpers. Fails if not empty.
64 + assertConsoleLogsCleared();
65 +
66 // $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
67 if (!jest.isMockFunction(setTimeout)) {
68 throw Error(
packages/jest-react/src/JestReact.js
+2
@@ -7,6 +7,7 @@
7
8 import {REACT_ELEMENT_TYPE, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
9 import {disableStringRefs, enableRefAsProp} from 'shared/ReactFeatureFlags';
10 +const {assertConsoleLogsCleared} = require('internal-test-utils/consoleMock');
11
12 import isArray from 'shared/isArray';
13
@@ -37,6 +38,7 @@ function assertYieldsWereCleared(root) {
38 Error.captureStackTrace(error, assertYieldsWereCleared);
39 throw error;
40 }
41 + assertConsoleLogsCleared();
42 }
43
44 function createJSXElementForTestComparison(type, props) {
scripts/jest/matchers/reactTestMatchers.js
+2 -1
@@ -1,7 +1,7 @@
1 'use strict';
2
3 const JestReact = require('jest-react');
4 -
4 +const {assertConsoleLogsCleared} = require('internal-test-utils/consoleMock');
5 // TODO: Move to ReactInternalTestUtils
6
7 function captureAssertion(fn) {
@@ -29,6 +29,7 @@ function assertYieldsWereCleared(Scheduler, caller) {
29 Error.captureStackTrace(error, caller);
30 throw error;
31 }
32 + assertConsoleLogsCleared();
33 }
34
35 function toMatchRenderedOutput(ReactNoop, expectedJSX) {