Best JavaScript code snippet using playwright-internal
ReactHooksWithNoopRenderer.test.js
Source:ReactHooksWithNoopRenderer.test.js
...421 }422 ReactNoop.render(<Counter count={0} />);423 expect(ReactNoop.flush()).toEqual(['Count: 0']);424 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);425 ReactNoop.flushPassiveEffects();426 expect(ReactNoop.clearYields()).toEqual(['Did commit [0]']);427 ReactNoop.render(<Counter count={1} />);428 expect(ReactNoop.flush()).toEqual(['Count: 1']);429 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);430 // Effects are deferred until after the commit431 ReactNoop.flushPassiveEffects();432 expect(ReactNoop.clearYields()).toEqual(['Did commit [1]']);433 });434 // tested in react-dom435 it.skip('flushes passive effects even with sibling deletions', () => {436 function LayoutEffect(props) {437 useLayoutEffect(() => {438 ReactNoop.yield(`Layout effect`);439 });440 return <Text text="Layout" />;441 }442 function PassiveEffect(props) {443 useEffect(() => {444 ReactNoop.yield(`Passive effect`);445 }, []);446 return <Text text="Passive" />;447 }448 let passive = <PassiveEffect key="p" />;449 ReactNoop.render([<LayoutEffect key="l" />, passive]);450 expect(ReactNoop.flush()).toEqual(['Layout', 'Passive', 'Layout effect']);451 expect(ReactNoop.getChildren()).toEqual([span('Layout'), span('Passive')]);452 // Destroying the first child shouldn't prevent the passive effect from453 // being executed454 ReactNoop.render([passive]);455 expect(ReactNoop.flush()).toEqual(['Passive effect']);456 expect(ReactNoop.getChildren()).toEqual([span('Passive')]);457 // (No effects are left to flush.)458 ReactNoop.flushPassiveEffects();459 expect(ReactNoop.clearYields()).toEqual(null);460 });461 it.skip('flushes passive effects even if siblings schedule an update', () => {462 function PassiveEffect(props) {463 useEffect(() => {464 ReactNoop.yield('Passive effect');465 });466 return <Text text="Passive" />;467 }468 function LayoutEffect(props) {469 let [count, setCount] = useState(0);470 useLayoutEffect(() => {471 // Scheduling work shouldn't interfere with the queued passive effect472 if (count === 0) {473 setCount(1);474 }475 ReactNoop.yield('Layout effect ' + count);476 });477 return <Text text="Layout" />;478 }479 ReactNoop.render([<PassiveEffect key="p" />, <LayoutEffect key="l" />]);480 act(() => {481 expect(ReactNoop.flush()).toEqual([482 'Passive',483 'Layout',484 'Layout effect 0',485 'Passive effect',486 'Layout',487 'Layout effect 1',488 ]);489 });490 expect(ReactNoop.getChildren()).toEqual([span('Passive'), span('Layout')]);491 });492 it.skip('flushes passive effects even if siblings schedule a new root', () => {493 function PassiveEffect(props) {494 useEffect(() => {495 ReactNoop.yield('Passive effect');496 }, []);497 return <Text text="Passive" />;498 }499 function LayoutEffect(props) {500 useLayoutEffect(() => {501 ReactNoop.yield('Layout effect');502 // Scheduling work shouldn't interfere with the queued passive effect503 ReactNoop.renderToRootWithID(<Text text="New Root" />, 'root2');504 });505 return <Text text="Layout" />;506 }507 ReactNoop.render([<PassiveEffect key="p" />, <LayoutEffect key="l" />]);508 expect(ReactNoop.flush()).toEqual(['Passive', 'Layout', 'Layout effect', 'Passive effect', 'New Root']);509 expect(ReactNoop.getChildren()).toEqual([span('Passive'), span('Layout')]);510 });511 it(512 'flushes effects serially by flushing old effects before flushing ' + "new ones, if they haven't already fired",513 () => {514 function getCommittedText() {515 const children = ReactNoop.getChildren();516 if (children === null) {517 return null;518 }519 return children[0].prop;520 }521 function Counter(props) {522 useEffect(() => {523 ReactNoop.yield(`Committed state when effect was fired: ${getCommittedText()}`);524 });525 return <Text text={props.count} />;526 }527 ReactNoop.render(<Counter count={0} />);528 expect(ReactNoop.flush()).toEqual([0]);529 expect(ReactNoop.getChildren()).toEqual([span(0)]);530 // Before the effects have a chance to flush, schedule another update531 ReactNoop.render(<Counter count={1} />);532 expect(ReactNoop.flush()).toEqual([533 // The previous effect flushes before the reconciliation534 'Committed state when effect was fired: 0',535 1,536 ]);537 expect(ReactNoop.getChildren()).toEqual([span(1)]);538 ReactNoop.flushPassiveEffects();539 expect(ReactNoop.clearYields()).toEqual(['Committed state when effect was fired: 1']);540 },541 );542 it('updates have async priority', () => {543 function Counter(props) {544 const [count, updateCount] = useState('(empty)');545 useEffect(() => {546 ReactNoop.yield(`Schedule update [${props.count}]`);547 updateCount(props.count);548 }, [props.count]);549 return <Text text={'Count: ' + count} />;550 }551 ReactNoop.render(<Counter count={0} />);552 expect(ReactNoop.flush()).toEqual(['Count: (empty)']);553 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);554 ReactNoop.flushPassiveEffects();555 expect(ReactNoop.clearYields()).toEqual(['Schedule update [0]']);556 expect(ReactNoop.flush()).toEqual(['Count: 0']);557 ReactNoop.render(<Counter count={1} />);558 expect(ReactNoop.flush()).toEqual(['Count: 0']);559 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);560 ReactNoop.flushPassiveEffects();561 expect(ReactNoop.clearYields()).toEqual(['Schedule update [1]']);562 expect(ReactNoop.flush()).toEqual(['Count: 1']);563 });564 it.skip('updates have async priority even if effects are flushed early', () => {565 function Counter(props) {566 const [count, updateCount] = useState('(empty)');567 useEffect(() => {568 ReactNoop.yield(`Schedule update [${props.count}]`);569 updateCount(props.count);570 }, [props.count]);571 return <Text text={'Count: ' + count} />;572 }573 ReactNoop.render(<Counter count={0} />);574 expect(ReactNoop.flush()).toEqual(['Count: (empty)']);575 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);576 // Rendering again should flush the previous commit's effects577 ReactNoop.render(<Counter count={1} />);578 ReactNoop.flushThrough(['Schedule update [0]', 'Count: 0']);579 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);580 ReactNoop.batchedUpdates(() => {581 expect(ReactNoop.flush()).toEqual([]);582 });583 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);584 ReactNoop.flushPassiveEffects();585 expect(ReactNoop.flush()).toEqual(['Schedule update [1]', 'Count: 1']);586 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);587 });588 it.skip('flushes serial effects before enqueueing work', () => {589 let _updateCount;590 function Counter(props) {591 const [count, updateCount] = useState(0);592 _updateCount = updateCount;593 useEffect(() => {594 ReactNoop.yield(`Will set count to 1`);595 updateCount(1);596 }, []);597 return <Text text={'Count: ' + count} />;598 }599 ReactNoop.render(<Counter count={0} />);600 expect(ReactNoop.flush()).toEqual(['Count: 0']);601 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);602 // Enqueuing this update forces the passive effect to be flushed --603 // updateCount(1) happens first, so 2 wins.604 act(() => _updateCount(2));605 expect(ReactNoop.flush()).toEqual(['Will set count to 1', 'Count: 2']);606 expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]);607 });608 it.skip('flushes serial effects before enqueueing work (with tracing)', () => {609 const onInteractionScheduledWorkCompleted = jest.fn();610 const onWorkCanceled = jest.fn();611 SchedulerTracing.unstable_subscribe({612 onInteractionScheduledWorkCompleted,613 onInteractionTraced: jest.fn(),614 onWorkCanceled,615 onWorkScheduled: jest.fn(),616 onWorkStarted: jest.fn(),617 onWorkStopped: jest.fn(),618 });619 let _updateCount;620 function Counter(props) {621 const [count, updateCount] = useState(0);622 _updateCount = updateCount;623 useEffect(() => {624 expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([tracingEvent]);625 ReactNoop.yield(`Will set count to 1`);626 updateCount(1);627 }, []);628 return <Text text={'Count: ' + count} />;629 }630 const tracingEvent = { id: 0, name: 'hello', timestamp: 0 };631 SchedulerTracing.unstable_trace(tracingEvent.name, tracingEvent.timestamp, () => {632 ReactNoop.render(<Counter count={0} />);633 });634 expect(ReactNoop.flush()).toEqual(['Count: 0']);635 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);636 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0);637 // Enqueuing this update forces the passive effect to be flushed --638 // updateCount(1) happens first, so 2 wins.639 act(() => _updateCount(2));640 expect(ReactNoop.flush()).toEqual(['Will set count to 1', 'Count: 2']);641 expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]);642 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);643 expect(onWorkCanceled).toHaveBeenCalledTimes(0);644 });645 it.skip('in sync mode, useEffect is deferred and updates finish synchronously ' + '(in a single batch)', () => {646 function Counter(props) {647 const [count, updateCount] = useState('(empty)');648 useEffect(() => {649 // Update multiple times. These should all be batched together in650 // a single render.651 updateCount(props.count);652 updateCount(props.count);653 updateCount(props.count);654 updateCount(props.count);655 updateCount(props.count);656 updateCount(props.count);657 }, [props.count]);658 return <Text text={'Count: ' + count} />;659 }660 ReactNoop.renderLegacySyncRoot(<Counter count={0} />);661 // Even in sync mode, effects are deferred until after paint662 expect(ReactNoop.flush()).toEqual(['Count: (empty)']);663 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);664 // Now fire the effects665 ReactNoop.flushPassiveEffects();666 // There were multiple updates, but there should only be a667 // single render668 expect(ReactNoop.clearYields()).toEqual(['Count: 0']);669 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);670 });671 it.skip('flushSync is not allowed', () => {672 function Counter(props) {673 const [count, updateCount] = useState('(empty)');674 useEffect(() => {675 ReactNoop.yield(`Schedule update [${props.count}]`);676 ReactNoop.flushSync(() => {677 updateCount(props.count);678 });679 }, [props.count]);680 return <Text text={'Count: ' + count} />;681 }682 ReactNoop.render(<Counter count={0} />);683 expect(ReactNoop.flush()).toEqual(['Count: (empty)']);684 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);685 expect(() => {686 ReactNoop.flushPassiveEffects();687 }).toThrow('flushSync was called from inside a lifecycle method');688 });689 it('unmounts previous effect', () => {690 function Counter(props) {691 useEffect(() => {692 ReactNoop.yield(`Did create [${props.count}]`);693 return () => {694 ReactNoop.yield(`Did destroy [${props.count}]`);695 };696 });697 return <Text text={'Count: ' + props.count} />;698 }699 ReactNoop.render(<Counter count={0} />);700 expect(ReactNoop.flush()).toEqual(['Count: 0']);701 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);702 ReactNoop.flushPassiveEffects();703 expect(ReactNoop.clearYields()).toEqual(['Did create [0]']);704 ReactNoop.render(<Counter count={1} />);705 expect(ReactNoop.flush()).toEqual(['Count: 1']);706 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);707 ReactNoop.flushPassiveEffects();708 expect(ReactNoop.clearYields()).toEqual(['Did destroy [0]', 'Did create [1]']);709 });710 it('unmounts on deletion', () => {711 function Counter(props) {712 useEffect(() => {713 ReactNoop.yield(`Did create [${props.count}]`);714 return () => {715 ReactNoop.yield(`Did destroy [${props.count}]`);716 };717 });718 return <Text text={'Count: ' + props.count} />;719 }720 ReactNoop.render(<Counter count={0} />);721 expect(ReactNoop.flush()).toEqual(['Count: 0']);722 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);723 ReactNoop.flushPassiveEffects();724 expect(ReactNoop.clearYields()).toEqual(['Did create [0]']);725 ReactNoop.render(null);726 expect(ReactNoop.flush()).toEqual(['Did destroy [0]']);727 expect(ReactNoop.getChildren()).toEqual([]);728 });729 it('unmounts on deletion after skipped effect', () => {730 function Counter(props) {731 useEffect(() => {732 ReactNoop.yield(`Did create [${props.count}]`);733 return () => {734 ReactNoop.yield(`Did destroy [${props.count}]`);735 };736 }, []);737 return <Text text={'Count: ' + props.count} />;738 }739 ReactNoop.render(<Counter count={0} />);740 expect(ReactNoop.flush()).toEqual(['Count: 0']);741 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);742 ReactNoop.flushPassiveEffects();743 expect(ReactNoop.clearYields()).toEqual(['Did create [0]']);744 ReactNoop.render(<Counter count={1} />);745 expect(ReactNoop.flush()).toEqual(['Count: 1']);746 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);747 ReactNoop.flushPassiveEffects();748 expect(ReactNoop.clearYields()).toEqual(null);749 ReactNoop.render(null);750 expect(ReactNoop.flush()).toEqual(['Did destroy [0]']);751 expect(ReactNoop.getChildren()).toEqual([]);752 });753 it('always fires effects if no dependencies are provided', () => {754 function effect() {755 ReactNoop.yield(`Did create`);756 return () => {757 ReactNoop.yield(`Did destroy`);758 };759 }760 function Counter(props) {761 useEffect(effect);762 return <Text text={'Count: ' + props.count} />;763 }764 ReactNoop.render(<Counter count={0} />);765 expect(ReactNoop.flush()).toEqual(['Count: 0']);766 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);767 ReactNoop.flushPassiveEffects();768 expect(ReactNoop.clearYields()).toEqual(['Did create']);769 ReactNoop.render(<Counter count={1} />);770 expect(ReactNoop.flush()).toEqual(['Count: 1']);771 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);772 ReactNoop.flushPassiveEffects();773 expect(ReactNoop.clearYields()).toEqual(['Did destroy', 'Did create']);774 ReactNoop.render(null);775 expect(ReactNoop.flush()).toEqual(['Did destroy']);776 expect(ReactNoop.getChildren()).toEqual([]);777 });778 it('skips effect if inputs have not changed', () => {779 function Counter(props) {780 const text = `${props.label}: ${props.count}`;781 useEffect(() => {782 ReactNoop.yield(`Did create [${text}]`);783 return () => {784 ReactNoop.yield(`Did destroy [${text}]`);785 };786 }, [props.label, props.count]);787 return <Text text={text} />;788 }789 ReactNoop.render(<Counter label="Count" count={0} />);790 expect(ReactNoop.flush()).toEqual(['Count: 0']);791 ReactNoop.flushPassiveEffects();792 expect(ReactNoop.clearYields()).toEqual(['Did create [Count: 0]']);793 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);794 ReactNoop.render(<Counter label="Count" count={1} />);795 // Count changed796 expect(ReactNoop.flush()).toEqual(['Count: 1']);797 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);798 ReactNoop.flushPassiveEffects();799 expect(ReactNoop.clearYields()).toEqual(['Did destroy [Count: 0]', 'Did create [Count: 1]']);800 ReactNoop.render(<Counter label="Count" count={1} />);801 // Nothing changed, so no effect should have fired802 expect(ReactNoop.flush()).toEqual(['Count: 1']);803 ReactNoop.flushPassiveEffects();804 expect(ReactNoop.clearYields()).toEqual(null);805 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);806 ReactNoop.render(<Counter label="Total" count={1} />);807 // Label changed808 expect(ReactNoop.flush()).toEqual(['Total: 1']);809 expect(ReactNoop.getChildren()).toEqual([span('Total: 1')]);810 ReactNoop.flushPassiveEffects();811 expect(ReactNoop.clearYields()).toEqual(['Did destroy [Count: 1]', 'Did create [Total: 1]']);812 });813 it('multiple effects', () => {814 function Counter(props) {815 useEffect(() => {816 ReactNoop.yield(`Did commit 1 [${props.count}]`);817 });818 useEffect(() => {819 ReactNoop.yield(`Did commit 2 [${props.count}]`);820 });821 return <Text text={'Count: ' + props.count} />;822 }823 ReactNoop.render(<Counter count={0} />);824 expect(ReactNoop.flush()).toEqual(['Count: 0']);825 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);826 ReactNoop.flushPassiveEffects();827 expect(ReactNoop.clearYields()).toEqual(['Did commit 1 [0]', 'Did commit 2 [0]']);828 ReactNoop.render(<Counter count={1} />);829 expect(ReactNoop.flush()).toEqual(['Count: 1']);830 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);831 ReactNoop.flushPassiveEffects();832 expect(ReactNoop.clearYields()).toEqual(['Did commit 1 [1]', 'Did commit 2 [1]']);833 });834 it('unmounts all previous effects before creating any new ones', () => {835 function Counter(props) {836 useEffect(() => {837 ReactNoop.yield(`Mount A [${props.count}]`);838 return () => {839 ReactNoop.yield(`Unmount A [${props.count}]`);840 };841 });842 useEffect(() => {843 ReactNoop.yield(`Mount B [${props.count}]`);844 return () => {845 ReactNoop.yield(`Unmount B [${props.count}]`);846 };847 });848 return <Text text={'Count: ' + props.count} />;849 }850 ReactNoop.render(<Counter count={0} />);851 expect(ReactNoop.flush()).toEqual(['Count: 0']);852 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);853 ReactNoop.flushPassiveEffects();854 expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']);855 ReactNoop.render(<Counter count={1} />);856 expect(ReactNoop.flush()).toEqual(['Count: 1']);857 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);858 ReactNoop.flushPassiveEffects();859 expect(ReactNoop.clearYields()).toEqual(['Unmount A [0]', 'Unmount B [0]', 'Mount A [1]', 'Mount B [1]']);860 });861 it.skip('handles errors on mount', () => {862 function Counter(props) {863 useEffect(() => {864 ReactNoop.yield(`Mount A [${props.count}]`);865 return () => {866 ReactNoop.yield(`Unmount A [${props.count}]`);867 };868 });869 useEffect(() => {870 ReactNoop.yield('Oops!');871 throw new Error('Oops!');872 // eslint-disable-next-line no-unreachable873 ReactNoop.yield(`Mount B [${props.count}]`);874 return () => {875 ReactNoop.yield(`Unmount B [${props.count}]`);876 };877 });878 return <Text text={'Count: ' + props.count} />;879 }880 ReactNoop.render(<Counter count={0} />);881 expect(ReactNoop.flush()).toEqual(['Count: 0']);882 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);883 expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');884 expect(ReactNoop.clearYields()).toEqual([885 'Mount A [0]',886 'Oops!',887 // Clean up effect A. There's no effect B to clean-up, because it888 // never mounted.889 'Unmount A [0]',890 ]);891 expect(ReactNoop.getChildren()).toEqual([]);892 });893 it.skip('handles errors on update', () => {894 function Counter(props) {895 useEffect(() => {896 ReactNoop.yield(`Mount A [${props.count}]`);897 return () => {898 ReactNoop.yield(`Unmount A [${props.count}]`);899 };900 });901 useEffect(() => {902 if (props.count === 1) {903 ReactNoop.yield('Oops!');904 throw new Error('Oops!');905 }906 ReactNoop.yield(`Mount B [${props.count}]`);907 return () => {908 ReactNoop.yield(`Unmount B [${props.count}]`);909 };910 });911 return <Text text={'Count: ' + props.count} />;912 }913 ReactNoop.render(<Counter count={0} />);914 expect(ReactNoop.flush()).toEqual(['Count: 0']);915 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);916 ReactNoop.flushPassiveEffects();917 expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']);918 // This update will trigger an errror919 ReactNoop.render(<Counter count={1} />);920 expect(ReactNoop.flush()).toEqual(['Count: 1']);921 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);922 expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');923 expect(ReactNoop.clearYields()).toEqual([924 'Unmount A [0]',925 'Unmount B [0]',926 'Mount A [1]',927 'Oops!',928 // Clean up effect A. There's no effect B to clean-up, because it929 // never mounted.930 'Unmount A [1]',931 ]);932 expect(ReactNoop.getChildren()).toEqual([]);933 });934 it.skip('handles errors on unmount', () => {935 function Counter(props) {936 useEffect(() => {937 ReactNoop.yield(`Mount A [${props.count}]`);938 return () => {939 ReactNoop.yield('Oops!');940 throw new Error('Oops!');941 // eslint-disable-next-line no-unreachable942 ReactNoop.yield(`Unmount A [${props.count}]`);943 };944 });945 useEffect(() => {946 ReactNoop.yield(`Mount B [${props.count}]`);947 return () => {948 ReactNoop.yield(`Unmount B [${props.count}]`);949 };950 });951 return <Text text={'Count: ' + props.count} />;952 }953 ReactNoop.render(<Counter count={0} />);954 expect(ReactNoop.flush()).toEqual(['Count: 0']);955 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);956 ReactNoop.flushPassiveEffects();957 expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']);958 // This update will trigger an errror959 ReactNoop.render(<Counter count={1} />);960 expect(ReactNoop.flush()).toEqual(['Count: 1']);961 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);962 expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops');963 expect(ReactNoop.clearYields()).toEqual([964 'Oops!',965 // B unmounts even though an error was thrown in the previous effect966 'Unmount B [0]',967 ]);968 expect(ReactNoop.getChildren()).toEqual([]);969 });970 it('works with memo', () => {971 function Counter({ count }) {972 useLayoutEffect(() => {973 ReactNoop.yield('Mount: ' + count);974 return () => ReactNoop.yield('Unmount: ' + count);975 });976 return <Text text={'Count: ' + count} />;977 }978 Counter = memo(Counter);979 ReactNoop.render(<Counter count={0} />);980 expect(ReactNoop.flush()).toEqual(['Count: 0', 'Mount: 0']);981 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);982 ReactNoop.render(<Counter count={1} />);983 expect(ReactNoop.flush()).toEqual(['Count: 1', 'Unmount: 0', 'Mount: 1']);984 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);985 ReactNoop.render(null);986 expect(ReactNoop.flush()).toEqual(['Unmount: 1']);987 expect(ReactNoop.getChildren()).toEqual([]);988 });989 });990 describe('useLayoutEffect', () => {991 it('fires layout effects after the host has been mutated', () => {992 function getCommittedText() {993 const children = ReactNoop.getChildren();994 if (children === null) {995 return null;996 }997 return children[0].prop;998 }999 function Counter(props) {1000 useLayoutEffect(() => {1001 ReactNoop.yield(`Current: ${getCommittedText()}`);1002 });1003 return <Text text={props.count} />;1004 }1005 ReactNoop.render(<Counter count={0} />);1006 expect(ReactNoop.flush()).toEqual([0, 'Current: 0']);1007 expect(ReactNoop.getChildren()).toEqual([span(0)]);1008 ReactNoop.render(<Counter count={1} />);1009 expect(ReactNoop.flush()).toEqual([1, 'Current: 1']);1010 expect(ReactNoop.getChildren()).toEqual([span(1)]);1011 });1012 it.skip('force flushes passive effects before firing new layout effects', () => {1013 let committedText = '(empty)';1014 function Counter(props) {1015 useLayoutEffect(() => {1016 // Normally this would go in a mutation effect, but this test1017 // intentionally omits a mutation effect.1018 committedText = props.count + '';1019 ReactNoop.yield(`Mount layout [current: ${committedText}]`);1020 return () => {1021 ReactNoop.yield(`Unmount layout [current: ${committedText}]`);1022 };1023 });1024 useEffect(() => {1025 ReactNoop.yield(`Mount normal [current: ${committedText}]`);1026 return () => {1027 ReactNoop.yield(`Unmount normal [current: ${committedText}]`);1028 };1029 });1030 return null;1031 }1032 ReactNoop.render(<Counter count={0} />);1033 expect(ReactNoop.flush()).toEqual(['Mount layout [current: 0]']);1034 expect(committedText).toEqual('0');1035 ReactNoop.render(<Counter count={1} />);1036 expect(ReactNoop.flush()).toEqual([1037 'Mount normal [current: 0]',1038 'Unmount layout [current: 0]',1039 'Mount layout [current: 1]',1040 ]);1041 expect(committedText).toEqual('1');1042 ReactNoop.flushPassiveEffects();1043 expect(ReactNoop.clearYields()).toEqual(['Unmount normal [current: 1]', 'Mount normal [current: 1]']);1044 });1045 });1046 describe('useCallback', () => {1047 it('memoizes callback by comparing inputs', () => {1048 class IncrementButton extends React.PureComponent {1049 increment = () => {1050 this.props.increment();1051 };1052 render() {1053 return <Text text="Increment" />;1054 }1055 }1056 function Counter({ incrementBy }) {1057 const [count, updateCount] = useState(0);1058 const increment = useCallback(() => updateCount(c => c + incrementBy), [incrementBy]);1059 return (1060 <React.Fragment>1061 <IncrementButton increment={increment} ref={button} />1062 <Text text={'Count: ' + count} />1063 </React.Fragment>1064 );1065 }1066 const button = React.createRef(null);1067 ReactNoop.render(<Counter incrementBy={1} />);1068 expect(ReactNoop.flush()).toEqual(['Increment', 'Count: 0']);1069 expect(ReactNoop.getChildren()).toEqual([span('Increment'), span('Count: 0')]);1070 act(button.current.increment);1071 expect(ReactNoop.flush()).toEqual([1072 // Button should not re-render, because its props haven't changed1073 // 'Increment',1074 'Count: 1',1075 ]);1076 expect(ReactNoop.getChildren()).toEqual([span('Increment'), span('Count: 1')]);1077 // Increase the increment amount1078 ReactNoop.render(<Counter incrementBy={10} />);1079 expect(ReactNoop.flush()).toEqual([1080 // Inputs did change this time1081 'Increment',1082 'Count: 1',1083 ]);1084 expect(ReactNoop.getChildren()).toEqual([span('Increment'), span('Count: 1')]);1085 // Callback should have updated1086 act(button.current.increment);1087 expect(ReactNoop.flush()).toEqual(['Count: 11']);1088 expect(ReactNoop.getChildren()).toEqual([span('Increment'), span('Count: 11')]);1089 });1090 });1091 describe('useMemo', () => {1092 it('memoizes value by comparing to previous inputs', () => {1093 function CapitalizedText(props) {1094 const text = props.text;1095 const capitalizedText = useMemo(() => {1096 ReactNoop.yield(`Capitalize '${text}'`);1097 return text.toUpperCase();1098 }, [text]);1099 return <Text text={capitalizedText} />;1100 }1101 ReactNoop.render(<CapitalizedText text="hello" />);1102 expect(ReactNoop.flush()).toEqual(["Capitalize 'hello'", 'HELLO']);1103 expect(ReactNoop.getChildren()).toEqual([span('HELLO')]);1104 ReactNoop.render(<CapitalizedText text="hi" />);1105 expect(ReactNoop.flush()).toEqual(["Capitalize 'hi'", 'HI']);1106 expect(ReactNoop.getChildren()).toEqual([span('HI')]);1107 ReactNoop.render(<CapitalizedText text="hi" />);1108 expect(ReactNoop.flush()).toEqual(['HI']);1109 expect(ReactNoop.getChildren()).toEqual([span('HI')]);1110 ReactNoop.render(<CapitalizedText text="goodbye" />);1111 expect(ReactNoop.flush()).toEqual(["Capitalize 'goodbye'", 'GOODBYE']);1112 expect(ReactNoop.getChildren()).toEqual([span('GOODBYE')]);1113 });1114 it('always re-computes if no inputs are provided', () => {1115 function LazyCompute(props) {1116 const computed = useMemo(props.compute);1117 return <Text text={computed} />;1118 }1119 function computeA() {1120 ReactNoop.yield('compute A');1121 return 'A';1122 }1123 function computeB() {1124 ReactNoop.yield('compute B');1125 return 'B';1126 }1127 ReactNoop.render(<LazyCompute compute={computeA} />);1128 expect(ReactNoop.flush()).toEqual(['compute A', 'A']);1129 ReactNoop.render(<LazyCompute compute={computeA} />);1130 expect(ReactNoop.flush()).toEqual(['compute A', 'A']);1131 ReactNoop.render(<LazyCompute compute={computeA} />);1132 expect(ReactNoop.flush()).toEqual(['compute A', 'A']);1133 ReactNoop.render(<LazyCompute compute={computeB} />);1134 expect(ReactNoop.flush()).toEqual(['compute B', 'B']);1135 });1136 it('should not invoke memoized function during re-renders unless inputs change', () => {1137 function LazyCompute(props) {1138 const computed = useMemo(() => props.compute(props.input), [props.input]);1139 const [count, setCount] = useState(0);1140 if (count < 3) {1141 setCount(count + 1);1142 }1143 return <Text text={computed} />;1144 }1145 function compute(val) {1146 ReactNoop.yield('compute ' + val);1147 return val;1148 }1149 ReactNoop.render(<LazyCompute compute={compute} input="A" />);1150 expect(ReactNoop.flush()).toEqual(['compute A', 'A']);1151 ReactNoop.render(<LazyCompute compute={compute} input="A" />);1152 expect(ReactNoop.flush()).toEqual(['A']);1153 ReactNoop.render(<LazyCompute compute={compute} input="B" />);1154 expect(ReactNoop.flush()).toEqual(['compute B', 'B']);1155 });1156 });1157 describe('useRef', () => {1158 it('creates a ref object initialized with the provided value', () => {1159 jest.useFakeTimers();1160 function useDebouncedCallback(callback, ms, inputs) {1161 const timeoutID = useRef(-1);1162 useEffect(() => {1163 return function unmount() {1164 clearTimeout(timeoutID.current);1165 };1166 }, []);1167 const debouncedCallback = useCallback(1168 (...args) => {1169 clearTimeout(timeoutID.current);1170 timeoutID.current = setTimeout(callback, ms, ...args);1171 },1172 [callback, ms],1173 );1174 return useCallback(debouncedCallback, inputs);1175 }1176 let ping;1177 function App() {1178 ping = useDebouncedCallback(1179 value => {1180 ReactNoop.yield('ping: ' + value);1181 },1182 100,1183 [],1184 );1185 return null;1186 }1187 ReactNoop.render(<App />);1188 expect(ReactNoop.flush()).toEqual([]);1189 ping(1);1190 ping(2);1191 ping(3);1192 expect(ReactNoop.flush()).toEqual([]);1193 jest.advanceTimersByTime(100);1194 expect(ReactNoop.flush()).toEqual(['ping: 3']);1195 ping(4);1196 jest.advanceTimersByTime(20);1197 ping(5);1198 ping(6);1199 jest.advanceTimersByTime(80);1200 expect(ReactNoop.flush()).toEqual([]);1201 jest.advanceTimersByTime(20);1202 expect(ReactNoop.flush()).toEqual(['ping: 6']);1203 });1204 it('should return the same ref during re-renders', () => {1205 function Counter() {1206 const ref = useRef('val');1207 const [count, setCount] = useState(0);1208 const [firstRef] = useState(ref);1209 if (firstRef !== ref) {1210 throw new Error('should never change');1211 }1212 if (count < 3) {1213 setCount(count + 1);1214 }1215 return <Text text={ref.current} />;1216 }1217 ReactNoop.render(<Counter />);1218 expect(ReactNoop.flush()).toEqual(['val']);1219 ReactNoop.render(<Counter />);1220 expect(ReactNoop.flush()).toEqual(['val']);1221 });1222 });1223 describe('useImperativeHandle', () => {1224 it('does not update when deps are the same', () => {1225 const INCREMENT = 'INCREMENT';1226 function reducer(state, action) {1227 return action === INCREMENT ? state + 1 : state;1228 }1229 function Counter(props, ref) {1230 const [count, dispatch] = useReducer(reducer, 0);1231 useImperativeHandle(ref, () => ({ count, dispatch }), []);1232 return <Text text={'Count: ' + count} />;1233 }1234 Counter = forwardRef(Counter);1235 const counter = React.createRef(null);1236 ReactNoop.render(<Counter ref={counter} />);1237 ReactNoop.flush();1238 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);1239 expect(counter.current.count).toBe(0);1240 act(() => {1241 counter.current.dispatch(INCREMENT);1242 });1243 ReactNoop.flush();1244 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);1245 // Intentionally not updated because of [] deps:1246 expect(counter.current.count).toBe(0);1247 });1248 // Regression test for https://github.com/facebook/react/issues/147821249 it.skip('automatically updates when deps are not specified', () => {1250 const INCREMENT = 'INCREMENT';1251 function reducer(state, action) {1252 return action === INCREMENT ? state + 1 : state;1253 }1254 function Counter(props, ref) {1255 const [count, dispatch] = useReducer(reducer, 0);1256 useImperativeHandle(ref, () => ({ count, dispatch }));1257 return <Text text={'Count: ' + count} />;1258 }1259 Counter = forwardRef(Counter);1260 const counter = React.createRef(null);1261 ReactNoop.render(<Counter ref={counter} />);1262 ReactNoop.flush();1263 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);1264 expect(counter.current.count).toBe(0);1265 act(() => {1266 counter.current.dispatch(INCREMENT);1267 });1268 ReactNoop.flush();1269 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);1270 expect(counter.current.count).toBe(1);1271 });1272 it('updates when deps are different', () => {1273 const INCREMENT = 'INCREMENT';1274 function reducer(state, action) {1275 return action === INCREMENT ? state + 1 : state;1276 }1277 let totalRefUpdates = 0;1278 function Counter(props, ref) {1279 const [count, dispatch] = useReducer(reducer, 0);1280 useImperativeHandle(1281 ref,1282 () => {1283 totalRefUpdates++;1284 return { count, dispatch };1285 },1286 [count],1287 );1288 return <Text text={'Count: ' + count} />;1289 }1290 Counter = forwardRef(Counter);1291 const counter = React.createRef(null);1292 ReactNoop.render(<Counter ref={counter} />);1293 ReactNoop.flush();1294 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);1295 expect(counter.current.count).toBe(0);1296 expect(totalRefUpdates).toBe(1);1297 act(() => {1298 counter.current.dispatch(INCREMENT);1299 });1300 ReactNoop.flush();1301 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);1302 expect(counter.current.count).toBe(1);1303 expect(totalRefUpdates).toBe(2);1304 // Update that doesn't change the ref dependencies1305 ReactNoop.render(<Counter ref={counter} />);1306 ReactNoop.flush();1307 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);1308 expect(counter.current.count).toBe(1);1309 expect(totalRefUpdates).toBe(2); // Should not increase since last time1310 });1311 });1312 describe('progressive enhancement (not supported)', () => {1313 it('mount additional state', () => {1314 let updateA;1315 let updateB;1316 // let updateC;1317 function App(props) {1318 const [A, _updateA] = useState(0);1319 const [B, _updateB] = useState(0);1320 updateA = _updateA;1321 updateB = _updateB;1322 let C;1323 if (props.loadC) {1324 useState(0);1325 } else {1326 C = '[not loaded]';1327 }1328 return <Text text={`A: ${A}, B: ${B}, C: ${C}`} />;1329 }1330 ReactNoop.render(<App loadC={false} />);1331 expect(ReactNoop.flush()).toEqual(['A: 0, B: 0, C: [not loaded]']);1332 expect(ReactNoop.getChildren()).toEqual([span('A: 0, B: 0, C: [not loaded]')]);1333 act(() => {1334 updateA(2);1335 updateB(3);1336 });1337 expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: [not loaded]']);1338 expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: [not loaded]')]);1339 ReactNoop.render(<App loadC={true} />);1340 expect(() => {1341 expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 0']);1342 }).toThrow('Rendered more hooks than during the previous render');1343 // Uncomment if/when we support this again1344 // expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 0')]);1345 // updateC(4);1346 // expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 4']);1347 // expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]);1348 });1349 it('unmount state', () => {1350 let updateA;1351 let updateB;1352 let updateC;1353 function App(props) {1354 const [A, _updateA] = useState(0);1355 const [B, _updateB] = useState(0);1356 updateA = _updateA;1357 updateB = _updateB;1358 let C;1359 if (props.loadC) {1360 const [_C, _updateC] = useState(0);1361 C = _C;1362 updateC = _updateC;1363 } else {1364 C = '[not loaded]';1365 }1366 return <Text text={`A: ${A}, B: ${B}, C: ${C}`} />;1367 }1368 ReactNoop.render(<App loadC={true} />);1369 expect(ReactNoop.flush()).toEqual(['A: 0, B: 0, C: 0']);1370 expect(ReactNoop.getChildren()).toEqual([span('A: 0, B: 0, C: 0')]);1371 act(() => {1372 updateA(2);1373 updateB(3);1374 updateC(4);1375 });1376 expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 4']);1377 expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]);1378 // Skip1379 // ReactNoop.render(<App loadC={false} />);1380 // expect(() => ReactNoop.flush()).toThrow(1381 // 'Rendered fewer hooks than expected. This may be caused by an ' + 'accidental early return statement.',1382 // );1383 });1384 it.skip('unmount effects', () => {1385 function App(props) {1386 useEffect(() => {1387 ReactNoop.yield('Mount A');1388 return () => {1389 ReactNoop.yield('Unmount A');1390 };1391 }, []);1392 if (props.showMore) {1393 useEffect(() => {1394 ReactNoop.yield('Mount B');1395 return () => {1396 ReactNoop.yield('Unmount B');1397 };1398 }, []);1399 }1400 return null;1401 }1402 ReactNoop.render(<App showMore={false} />);1403 expect(ReactNoop.flush()).toEqual([]);1404 ReactNoop.flushPassiveEffects();1405 expect(ReactNoop.clearYields()).toEqual(['Mount A']);1406 ReactNoop.render(<App showMore={true} />);1407 expect(() => {1408 expect(ReactNoop.flush()).toEqual([]);1409 }).toThrow('Rendered more hooks than during the previous render');1410 // Uncomment if/when we support this again1411 // ReactNoop.flushPassiveEffects();1412 // expect(ReactNoop.clearYields()).toEqual(['Mount B']);1413 // ReactNoop.render(<App showMore={false} />);1414 // expect(() => ReactNoop.flush()).toThrow(1415 // 'Rendered fewer hooks than expected. This may be caused by an ' +1416 // 'accidental early return statement.',1417 // );1418 });1419 });1420 describe('useContext', () => {1421 it('simple use', () => {1422 const CounterContext = createContext();1423 const Counter = () => {1424 const count = useContext(CounterContext);1425 return <Text text={count} />;...
ReactHooksWithNoopRenderer-test.internal.js
Source:ReactHooksWithNoopRenderer-test.internal.js
...532 }533 ReactNoop.render(<Counter count={0} />);534 expect(ReactNoop.flush()).toEqual(['Count: 0']);535 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);536 flushPassiveEffects();537 expect(ReactNoop.clearYields()).toEqual(['Did commit [0]']);538 ReactNoop.render(<Counter count={1} />);539 expect(ReactNoop.flush()).toEqual(['Count: 1']);540 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);541 // Effects are deferred until after the commit542 flushPassiveEffects();543 expect(ReactNoop.clearYields()).toEqual(['Did commit [1]']);544 });545 it('flushes passive effects even with sibling deletions', () => {546 function LayoutEffect(props) {547 useLayoutEffect(() => {548 ReactNoop.yield(`Layout effect`);549 });550 return <Text text="Layout" />;551 }552 function PassiveEffect(props) {553 useEffect(() => {554 ReactNoop.yield(`Passive effect`);555 }, []);556 return <Text text="Passive" />;557 }558 let passive = <PassiveEffect key="p" />;559 ReactNoop.render([<LayoutEffect key="l" />, passive]);560 expect(ReactNoop.flush()).toEqual(['Layout', 'Passive', 'Layout effect']);561 expect(ReactNoop.getChildren()).toEqual([562 span('Layout'),563 span('Passive'),564 ]);565 // Destroying the first child shouldn't prevent the passive effect from566 // being executed567 ReactNoop.render([passive]);568 expect(ReactNoop.flush()).toEqual(['Passive effect']);569 expect(ReactNoop.getChildren()).toEqual([span('Passive')]);570 // (No effects are left to flush.)571 flushPassiveEffects();572 expect(ReactNoop.clearYields()).toEqual(null);573 });574 it('flushes passive effects even if siblings schedule an update', () => {575 function PassiveEffect(props) {576 useEffect(() => {577 ReactNoop.yield('Passive effect');578 });579 return <Text text="Passive" />;580 }581 function LayoutEffect(props) {582 let [count, setCount] = useState(0);583 useLayoutEffect(() => {584 // Scheduling work shouldn't interfere with the queued passive effect585 if (count === 0) {586 setCount(1);587 }588 ReactNoop.yield('Layout effect ' + count);589 });590 return <Text text="Layout" />;591 }592 ReactNoop.render([<PassiveEffect key="p" />, <LayoutEffect key="l" />]);593 expect(ReactNoop.flush()).toEqual([594 'Passive',595 'Layout',596 'Layout effect 0',597 'Passive effect',598 'Layout',599 'Layout effect 1',600 ]);601 expect(ReactNoop.getChildren()).toEqual([602 span('Passive'),603 span('Layout'),604 ]);605 });606 it('flushes passive effects even if siblings schedule a new root', () => {607 function PassiveEffect(props) {608 useEffect(() => {609 ReactNoop.yield('Passive effect');610 }, []);611 return <Text text="Passive" />;612 }613 function LayoutEffect(props) {614 useLayoutEffect(() => {615 ReactNoop.yield('Layout effect');616 // Scheduling work shouldn't interfere with the queued passive effect617 ReactNoop.renderToRootWithID(<Text text="New Root" />, 'root2');618 });619 return <Text text="Layout" />;620 }621 ReactNoop.render([<PassiveEffect key="p" />, <LayoutEffect key="l" />]);622 expect(ReactNoop.flush()).toEqual([623 'Passive',624 'Layout',625 'Layout effect',626 'Passive effect',627 'New Root',628 ]);629 expect(ReactNoop.getChildren()).toEqual([630 span('Passive'),631 span('Layout'),632 ]);633 });634 it(635 'flushes effects serially by flushing old effects before flushing ' +636 "new ones, if they haven't already fired",637 () => {638 function getCommittedText() {639 const children = ReactNoop.getChildren();640 if (children === null) {641 return null;642 }643 return children[0].prop;644 }645 function Counter(props) {646 useEffect(() => {647 ReactNoop.yield(648 `Committed state when effect was fired: ${getCommittedText()}`,649 );650 });651 return <Text text={props.count} />;652 }653 ReactNoop.render(<Counter count={0} />);654 expect(ReactNoop.flush()).toEqual([0]);655 expect(ReactNoop.getChildren()).toEqual([span(0)]);656 // Before the effects have a chance to flush, schedule another update657 ReactNoop.render(<Counter count={1} />);658 expect(ReactNoop.flush()).toEqual([659 // The previous effect flushes before the reconciliation660 'Committed state when effect was fired: 0',661 1,662 ]);663 expect(ReactNoop.getChildren()).toEqual([span(1)]);664 flushPassiveEffects();665 expect(ReactNoop.clearYields()).toEqual([666 'Committed state when effect was fired: 1',667 ]);668 },669 );670 it('updates have async priority', () => {671 function Counter(props) {672 const [count, updateCount] = useState('(empty)');673 useEffect(674 () => {675 ReactNoop.yield(`Schedule update [${props.count}]`);676 updateCount(props.count);677 },678 [props.count],679 );680 return <Text text={'Count: ' + count} />;681 }682 ReactNoop.render(<Counter count={0} />);683 expect(ReactNoop.flush()).toEqual(['Count: (empty)']);684 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);685 flushPassiveEffects();686 expect(ReactNoop.clearYields()).toEqual(['Schedule update [0]']);687 expect(ReactNoop.flush()).toEqual(['Count: 0']);688 ReactNoop.render(<Counter count={1} />);689 expect(ReactNoop.flush()).toEqual(['Count: 0']);690 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);691 flushPassiveEffects();692 expect(ReactNoop.clearYields()).toEqual(['Schedule update [1]']);693 expect(ReactNoop.flush()).toEqual(['Count: 1']);694 });695 it('updates have async priority even if effects are flushed early', () => {696 function Counter(props) {697 const [count, updateCount] = useState('(empty)');698 useEffect(699 () => {700 ReactNoop.yield(`Schedule update [${props.count}]`);701 updateCount(props.count);702 },703 [props.count],704 );705 return <Text text={'Count: ' + count} />;706 }707 ReactNoop.render(<Counter count={0} />);708 expect(ReactNoop.flush()).toEqual(['Count: (empty)']);709 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);710 // Rendering again should flush the previous commit's effects711 ReactNoop.render(<Counter count={1} />);712 ReactNoop.flushThrough(['Schedule update [0]', 'Count: 0']);713 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);714 expect(ReactNoop.flush()).toEqual([]);715 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);716 flushPassiveEffects();717 expect(ReactNoop.flush()).toEqual(['Schedule update [1]', 'Count: 1']);718 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);719 });720 it('flushes serial effects before enqueueing work', () => {721 let _updateCount;722 function Counter(props) {723 const [count, updateCount] = useState(0);724 _updateCount = updateCount;725 useEffect(() => {726 ReactNoop.yield(`Will set count to 1`);727 updateCount(1);728 }, []);729 return <Text text={'Count: ' + count} />;730 }731 ReactNoop.render(<Counter count={0} />);732 expect(ReactNoop.flush()).toEqual(['Count: 0']);733 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);734 // Enqueuing this update forces the passive effect to be flushed --735 // updateCount(1) happens first, so 2 wins.736 _updateCount(2);737 expect(ReactNoop.flush()).toEqual(['Will set count to 1', 'Count: 2']);738 expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]);739 });740 it('flushes serial effects before enqueueing work (with tracing)', () => {741 const onInteractionScheduledWorkCompleted = jest.fn();742 const onWorkCanceled = jest.fn();743 SchedulerTracing.unstable_subscribe({744 onInteractionScheduledWorkCompleted,745 onInteractionTraced: jest.fn(),746 onWorkCanceled,747 onWorkScheduled: jest.fn(),748 onWorkStarted: jest.fn(),749 onWorkStopped: jest.fn(),750 });751 let _updateCount;752 function Counter(props) {753 const [count, updateCount] = useState(0);754 _updateCount = updateCount;755 useEffect(() => {756 expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([757 tracingEvent,758 ]);759 ReactNoop.yield(`Will set count to 1`);760 updateCount(1);761 }, []);762 return <Text text={'Count: ' + count} />;763 }764 const tracingEvent = {id: 0, name: 'hello', timestamp: 0};765 SchedulerTracing.unstable_trace(766 tracingEvent.name,767 tracingEvent.timestamp,768 () => {769 ReactNoop.render(<Counter count={0} />);770 },771 );772 expect(ReactNoop.flush()).toEqual(['Count: 0']);773 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);774 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0);775 // Enqueuing this update forces the passive effect to be flushed --776 // updateCount(1) happens first, so 2 wins.777 _updateCount(2);778 expect(ReactNoop.flush()).toEqual(['Will set count to 1', 'Count: 2']);779 expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]);780 expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);781 expect(onWorkCanceled).toHaveBeenCalledTimes(0);782 });783 it(784 'in sync mode, useEffect is deferred and updates finish synchronously ' +785 '(in a single batch)',786 () => {787 function Counter(props) {788 const [count, updateCount] = useState('(empty)');789 useEffect(790 () => {791 // Update multiple times. These should all be batched together in792 // a single render.793 updateCount(props.count);794 updateCount(props.count);795 updateCount(props.count);796 updateCount(props.count);797 updateCount(props.count);798 updateCount(props.count);799 },800 [props.count],801 );802 return <Text text={'Count: ' + count} />;803 }804 ReactNoop.renderLegacySyncRoot(<Counter count={0} />);805 // Even in sync mode, effects are deferred until after paint806 expect(ReactNoop.flush()).toEqual(['Count: (empty)']);807 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);808 // Now fire the effects809 flushPassiveEffects();810 // There were multiple updates, but there should only be a811 // single render812 expect(ReactNoop.clearYields()).toEqual(['Count: 0']);813 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);814 },815 );816 it('flushSync is not allowed', () => {817 function Counter(props) {818 const [count, updateCount] = useState('(empty)');819 useEffect(820 () => {821 ReactNoop.yield(`Schedule update [${props.count}]`);822 ReactNoop.flushSync(() => {823 updateCount(props.count);824 });825 },826 [props.count],827 );828 return <Text text={'Count: ' + count} />;829 }830 ReactNoop.render(<Counter count={0} />);831 expect(ReactNoop.flush()).toEqual(['Count: (empty)']);832 expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);833 expect(() => {834 flushPassiveEffects();835 }).toThrow('flushSync was called from inside a lifecycle method');836 });837 it('unmounts previous effect', () => {838 function Counter(props) {839 useEffect(() => {840 ReactNoop.yield(`Did create [${props.count}]`);841 return () => {842 ReactNoop.yield(`Did destroy [${props.count}]`);843 };844 });845 return <Text text={'Count: ' + props.count} />;846 }847 ReactNoop.render(<Counter count={0} />);848 expect(ReactNoop.flush()).toEqual(['Count: 0']);849 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);850 flushPassiveEffects();851 expect(ReactNoop.clearYields()).toEqual(['Did create [0]']);852 ReactNoop.render(<Counter count={1} />);853 expect(ReactNoop.flush()).toEqual(['Count: 1']);854 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);855 flushPassiveEffects();856 expect(ReactNoop.clearYields()).toEqual([857 'Did destroy [0]',858 'Did create [1]',859 ]);860 });861 it('unmounts on deletion', () => {862 function Counter(props) {863 useEffect(() => {864 ReactNoop.yield(`Did create [${props.count}]`);865 return () => {866 ReactNoop.yield(`Did destroy [${props.count}]`);867 };868 });869 return <Text text={'Count: ' + props.count} />;870 }871 ReactNoop.render(<Counter count={0} />);872 expect(ReactNoop.flush()).toEqual(['Count: 0']);873 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);874 flushPassiveEffects();875 expect(ReactNoop.clearYields()).toEqual(['Did create [0]']);876 ReactNoop.render(null);877 expect(ReactNoop.flush()).toEqual(['Did destroy [0]']);878 expect(ReactNoop.getChildren()).toEqual([]);879 });880 it('unmounts on deletion after skipped effect', () => {881 function Counter(props) {882 useEffect(() => {883 ReactNoop.yield(`Did create [${props.count}]`);884 return () => {885 ReactNoop.yield(`Did destroy [${props.count}]`);886 };887 }, []);888 return <Text text={'Count: ' + props.count} />;889 }890 ReactNoop.render(<Counter count={0} />);891 expect(ReactNoop.flush()).toEqual(['Count: 0']);892 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);893 flushPassiveEffects();894 expect(ReactNoop.clearYields()).toEqual(['Did create [0]']);895 ReactNoop.render(<Counter count={1} />);896 expect(ReactNoop.flush()).toEqual(['Count: 1']);897 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);898 flushPassiveEffects();899 expect(ReactNoop.clearYields()).toEqual(null);900 ReactNoop.render(null);901 expect(ReactNoop.flush()).toEqual(['Did destroy [0]']);902 expect(ReactNoop.getChildren()).toEqual([]);903 });904 it('skips effect if constructor has not changed', () => {905 function effect() {906 ReactNoop.yield(`Did mount`);907 return () => {908 ReactNoop.yield(`Did unmount`);909 };910 }911 function Counter(props) {912 useEffect(effect);913 return <Text text={'Count: ' + props.count} />;914 }915 ReactNoop.render(<Counter count={0} />);916 expect(ReactNoop.flush()).toEqual(['Count: 0']);917 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);918 flushPassiveEffects();919 expect(ReactNoop.clearYields()).toEqual(['Did mount']);920 ReactNoop.render(<Counter count={1} />);921 // No effect, because constructor was hoisted outside render922 expect(ReactNoop.flush()).toEqual(['Count: 1']);923 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);924 ReactNoop.render(null);925 expect(ReactNoop.flush()).toEqual(['Did unmount']);926 expect(ReactNoop.getChildren()).toEqual([]);927 });928 it('skips effect if inputs have not changed', () => {929 function Counter(props) {930 const text = `${props.label}: ${props.count}`;931 useEffect(932 () => {933 ReactNoop.yield(`Did create [${text}]`);934 return () => {935 ReactNoop.yield(`Did destroy [${text}]`);936 };937 },938 [props.label, props.count],939 );940 return <Text text={text} />;941 }942 ReactNoop.render(<Counter label="Count" count={0} />);943 expect(ReactNoop.flush()).toEqual(['Count: 0']);944 flushPassiveEffects();945 expect(ReactNoop.clearYields()).toEqual(['Did create [Count: 0]']);946 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);947 ReactNoop.render(<Counter label="Count" count={1} />);948 // Count changed949 expect(ReactNoop.flush()).toEqual(['Count: 1']);950 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);951 flushPassiveEffects();952 expect(ReactNoop.clearYields()).toEqual([953 'Did destroy [Count: 0]',954 'Did create [Count: 1]',955 ]);956 ReactNoop.render(<Counter label="Count" count={1} />);957 // Nothing changed, so no effect should have fired958 expect(ReactNoop.flush()).toEqual(['Count: 1']);959 flushPassiveEffects();960 expect(ReactNoop.clearYields()).toEqual(null);961 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);962 ReactNoop.render(<Counter label="Total" count={1} />);963 // Label changed964 expect(ReactNoop.flush()).toEqual(['Total: 1']);965 expect(ReactNoop.getChildren()).toEqual([span('Total: 1')]);966 flushPassiveEffects();967 expect(ReactNoop.clearYields()).toEqual([968 'Did destroy [Count: 1]',969 'Did create [Total: 1]',970 ]);971 });972 it('multiple effects', () => {973 function Counter(props) {974 useEffect(() => {975 ReactNoop.yield(`Did commit 1 [${props.count}]`);976 });977 useEffect(() => {978 ReactNoop.yield(`Did commit 2 [${props.count}]`);979 });980 return <Text text={'Count: ' + props.count} />;981 }982 ReactNoop.render(<Counter count={0} />);983 expect(ReactNoop.flush()).toEqual(['Count: 0']);984 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);985 flushPassiveEffects();986 expect(ReactNoop.clearYields()).toEqual([987 'Did commit 1 [0]',988 'Did commit 2 [0]',989 ]);990 ReactNoop.render(<Counter count={1} />);991 expect(ReactNoop.flush()).toEqual(['Count: 1']);992 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);993 flushPassiveEffects();994 expect(ReactNoop.clearYields()).toEqual([995 'Did commit 1 [1]',996 'Did commit 2 [1]',997 ]);998 });999 it('unmounts all previous effects before creating any new ones', () => {1000 function Counter(props) {1001 useEffect(() => {1002 ReactNoop.yield(`Mount A [${props.count}]`);1003 return () => {1004 ReactNoop.yield(`Unmount A [${props.count}]`);1005 };1006 });1007 useEffect(() => {1008 ReactNoop.yield(`Mount B [${props.count}]`);1009 return () => {1010 ReactNoop.yield(`Unmount B [${props.count}]`);1011 };1012 });1013 return <Text text={'Count: ' + props.count} />;1014 }1015 ReactNoop.render(<Counter count={0} />);1016 expect(ReactNoop.flush()).toEqual(['Count: 0']);1017 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);1018 flushPassiveEffects();1019 expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']);1020 ReactNoop.render(<Counter count={1} />);1021 expect(ReactNoop.flush()).toEqual(['Count: 1']);1022 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);1023 flushPassiveEffects();1024 expect(ReactNoop.clearYields()).toEqual([1025 'Unmount A [0]',1026 'Unmount B [0]',1027 'Mount A [1]',1028 'Mount B [1]',1029 ]);1030 });1031 it('handles errors on mount', () => {1032 function Counter(props) {1033 useEffect(() => {1034 ReactNoop.yield(`Mount A [${props.count}]`);1035 return () => {1036 ReactNoop.yield(`Unmount A [${props.count}]`);1037 };1038 });1039 useEffect(() => {1040 ReactNoop.yield('Oops!');1041 throw new Error('Oops!');1042 // eslint-disable-next-line no-unreachable1043 ReactNoop.yield(`Mount B [${props.count}]`);1044 return () => {1045 ReactNoop.yield(`Unmount B [${props.count}]`);1046 };1047 });1048 return <Text text={'Count: ' + props.count} />;1049 }1050 ReactNoop.render(<Counter count={0} />);1051 expect(ReactNoop.flush()).toEqual(['Count: 0']);1052 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);1053 expect(() => flushPassiveEffects()).toThrow('Oops');1054 expect(ReactNoop.clearYields()).toEqual([1055 'Mount A [0]',1056 'Oops!',1057 // Clean up effect A. There's no effect B to clean-up, because it1058 // never mounted.1059 'Unmount A [0]',1060 ]);1061 expect(ReactNoop.getChildren()).toEqual([]);1062 });1063 it('handles errors on update', () => {1064 function Counter(props) {1065 useEffect(() => {1066 ReactNoop.yield(`Mount A [${props.count}]`);1067 return () => {1068 ReactNoop.yield(`Unmount A [${props.count}]`);1069 };1070 });1071 useEffect(() => {1072 if (props.count === 1) {1073 ReactNoop.yield('Oops!');1074 throw new Error('Oops!');1075 }1076 ReactNoop.yield(`Mount B [${props.count}]`);1077 return () => {1078 ReactNoop.yield(`Unmount B [${props.count}]`);1079 };1080 });1081 return <Text text={'Count: ' + props.count} />;1082 }1083 ReactNoop.render(<Counter count={0} />);1084 expect(ReactNoop.flush()).toEqual(['Count: 0']);1085 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);1086 flushPassiveEffects();1087 expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']);1088 // This update will trigger an errror1089 ReactNoop.render(<Counter count={1} />);1090 expect(ReactNoop.flush()).toEqual(['Count: 1']);1091 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);1092 expect(() => flushPassiveEffects()).toThrow('Oops');1093 expect(ReactNoop.clearYields()).toEqual([1094 'Unmount A [0]',1095 'Unmount B [0]',1096 'Mount A [1]',1097 'Oops!',1098 // Clean up effect A. There's no effect B to clean-up, because it1099 // never mounted.1100 'Unmount A [1]',1101 ]);1102 expect(ReactNoop.getChildren()).toEqual([]);1103 });1104 it('handles errors on unmount', () => {1105 function Counter(props) {1106 useEffect(() => {1107 ReactNoop.yield(`Mount A [${props.count}]`);1108 return () => {1109 ReactNoop.yield('Oops!');1110 throw new Error('Oops!');1111 // eslint-disable-next-line no-unreachable1112 ReactNoop.yield(`Unmount A [${props.count}]`);1113 };1114 });1115 useEffect(() => {1116 ReactNoop.yield(`Mount B [${props.count}]`);1117 return () => {1118 ReactNoop.yield(`Unmount B [${props.count}]`);1119 };1120 });1121 return <Text text={'Count: ' + props.count} />;1122 }1123 ReactNoop.render(<Counter count={0} />);1124 expect(ReactNoop.flush()).toEqual(['Count: 0']);1125 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);1126 flushPassiveEffects();1127 expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']);1128 // This update will trigger an errror1129 ReactNoop.render(<Counter count={1} />);1130 expect(ReactNoop.flush()).toEqual(['Count: 1']);1131 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);1132 expect(() => flushPassiveEffects()).toThrow('Oops');1133 expect(ReactNoop.clearYields()).toEqual([1134 'Oops!',1135 // B unmounts even though an error was thrown in the previous effect1136 'Unmount B [0]',1137 ]);1138 expect(ReactNoop.getChildren()).toEqual([]);1139 });1140 it('works with memo', () => {1141 function Counter({count}) {1142 useLayoutEffect(() => {1143 ReactNoop.yield('Mount: ' + count);1144 return () => ReactNoop.yield('Unmount: ' + count);1145 });1146 return <Text text={'Count: ' + count} />;1147 }1148 Counter = memo(Counter);1149 ReactNoop.render(<Counter count={0} />);1150 expect(ReactNoop.flush()).toEqual(['Count: 0', 'Mount: 0']);1151 expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);1152 ReactNoop.render(<Counter count={1} />);1153 expect(ReactNoop.flush()).toEqual(['Count: 1', 'Unmount: 0', 'Mount: 1']);1154 expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);1155 ReactNoop.render(null);1156 expect(ReactNoop.flush()).toEqual(['Unmount: 1']);1157 expect(ReactNoop.getChildren()).toEqual([]);1158 });1159 });1160 describe('useLayoutEffect', () => {1161 it('fires layout effects after the host has been mutated', () => {1162 function getCommittedText() {1163 const children = ReactNoop.getChildren();1164 if (children === null) {1165 return null;1166 }1167 return children[0].prop;1168 }1169 function Counter(props) {1170 useLayoutEffect(() => {1171 ReactNoop.yield(`Current: ${getCommittedText()}`);1172 });1173 return <Text text={props.count} />;1174 }1175 ReactNoop.render(<Counter count={0} />);1176 expect(ReactNoop.flush()).toEqual([0, 'Current: 0']);1177 expect(ReactNoop.getChildren()).toEqual([span(0)]);1178 ReactNoop.render(<Counter count={1} />);1179 expect(ReactNoop.flush()).toEqual([1, 'Current: 1']);1180 expect(ReactNoop.getChildren()).toEqual([span(1)]);1181 });1182 it('force flushes passive effects before firing new layout effects', () => {1183 let committedText = '(empty)';1184 function Counter(props) {1185 useLayoutEffect(() => {1186 // Normally this would go in a mutation effect, but this test1187 // intentionally omits a mutation effect.1188 committedText = props.count + '';1189 ReactNoop.yield(`Mount layout [current: ${committedText}]`);1190 return () => {1191 ReactNoop.yield(`Unmount layout [current: ${committedText}]`);1192 };1193 });1194 useEffect(() => {1195 ReactNoop.yield(`Mount normal [current: ${committedText}]`);1196 return () => {1197 ReactNoop.yield(`Unmount normal [current: ${committedText}]`);1198 };1199 });1200 return null;1201 }1202 ReactNoop.render(<Counter count={0} />);1203 expect(ReactNoop.flush()).toEqual(['Mount layout [current: 0]']);1204 expect(committedText).toEqual('0');1205 ReactNoop.render(<Counter count={1} />);1206 expect(ReactNoop.flush()).toEqual([1207 'Mount normal [current: 0]',1208 'Unmount layout [current: 0]',1209 'Mount layout [current: 1]',1210 ]);1211 expect(committedText).toEqual('1');1212 flushPassiveEffects();1213 expect(ReactNoop.clearYields()).toEqual([1214 'Unmount normal [current: 1]',1215 'Mount normal [current: 1]',1216 ]);1217 });1218 });1219 describe('useCallback', () => {1220 it('memoizes callback by comparing inputs', () => {1221 class IncrementButton extends React.PureComponent {1222 increment = () => {1223 this.props.increment();1224 };1225 render() {1226 return <Text text="Increment" />;1227 }1228 }1229 function Counter({incrementBy}) {1230 const [count, updateCount] = useState(0);1231 const increment = useCallback(() => updateCount(c => c + incrementBy), [1232 incrementBy,1233 ]);1234 return (1235 <React.Fragment>1236 <IncrementButton increment={increment} ref={button} />1237 <Text text={'Count: ' + count} />1238 </React.Fragment>1239 );1240 }1241 const button = React.createRef(null);1242 ReactNoop.render(<Counter incrementBy={1} />);1243 expect(ReactNoop.flush()).toEqual(['Increment', 'Count: 0']);1244 expect(ReactNoop.getChildren()).toEqual([1245 span('Increment'),1246 span('Count: 0'),1247 ]);1248 button.current.increment();1249 expect(ReactNoop.flush()).toEqual([1250 // Button should not re-render, because its props haven't changed1251 // 'Increment',1252 'Count: 1',1253 ]);1254 expect(ReactNoop.getChildren()).toEqual([1255 span('Increment'),1256 span('Count: 1'),1257 ]);1258 // Increase the increment amount1259 ReactNoop.render(<Counter incrementBy={10} />);1260 expect(ReactNoop.flush()).toEqual([1261 // Inputs did change this time1262 'Increment',1263 'Count: 1',1264 ]);1265 expect(ReactNoop.getChildren()).toEqual([1266 span('Increment'),1267 span('Count: 1'),1268 ]);1269 // Callback should have updated1270 button.current.increment();1271 expect(ReactNoop.flush()).toEqual(['Count: 11']);1272 expect(ReactNoop.getChildren()).toEqual([1273 span('Increment'),1274 span('Count: 11'),1275 ]);1276 });1277 });1278 describe('useMemo', () => {1279 it('memoizes value by comparing to previous inputs', () => {1280 function CapitalizedText(props) {1281 const text = props.text;1282 const capitalizedText = useMemo(1283 () => {1284 ReactNoop.yield(`Capitalize '${text}'`);1285 return text.toUpperCase();1286 },1287 [text],1288 );1289 return <Text text={capitalizedText} />;1290 }1291 ReactNoop.render(<CapitalizedText text="hello" />);1292 expect(ReactNoop.flush()).toEqual(["Capitalize 'hello'", 'HELLO']);1293 expect(ReactNoop.getChildren()).toEqual([span('HELLO')]);1294 ReactNoop.render(<CapitalizedText text="hi" />);1295 expect(ReactNoop.flush()).toEqual(["Capitalize 'hi'", 'HI']);1296 expect(ReactNoop.getChildren()).toEqual([span('HI')]);1297 ReactNoop.render(<CapitalizedText text="hi" />);1298 expect(ReactNoop.flush()).toEqual(['HI']);1299 expect(ReactNoop.getChildren()).toEqual([span('HI')]);1300 ReactNoop.render(<CapitalizedText text="goodbye" />);1301 expect(ReactNoop.flush()).toEqual(["Capitalize 'goodbye'", 'GOODBYE']);1302 expect(ReactNoop.getChildren()).toEqual([span('GOODBYE')]);1303 });1304 it('compares function if no inputs are provided', () => {1305 function LazyCompute(props) {1306 const computed = useMemo(props.compute);1307 return <Text text={computed} />;1308 }1309 function computeA() {1310 ReactNoop.yield('compute A');1311 return 'A';1312 }1313 function computeB() {1314 ReactNoop.yield('compute B');1315 return 'B';1316 }1317 ReactNoop.render(<LazyCompute compute={computeA} />);1318 expect(ReactNoop.flush()).toEqual(['compute A', 'A']);1319 ReactNoop.render(<LazyCompute compute={computeA} />);1320 expect(ReactNoop.flush()).toEqual(['A']);1321 ReactNoop.render(<LazyCompute compute={computeA} />);1322 expect(ReactNoop.flush()).toEqual(['A']);1323 ReactNoop.render(<LazyCompute compute={computeB} />);1324 expect(ReactNoop.flush()).toEqual(['compute B', 'B']);1325 });1326 it('should not invoke memoized function during re-renders unless inputs change', () => {1327 function LazyCompute(props) {1328 const computed = useMemo(() => props.compute(props.input), [1329 props.input,1330 ]);1331 const [count, setCount] = useState(0);1332 if (count < 3) {1333 setCount(count + 1);1334 }1335 return <Text text={computed} />;1336 }1337 function compute(val) {1338 ReactNoop.yield('compute ' + val);1339 return val;1340 }1341 ReactNoop.render(<LazyCompute compute={compute} input="A" />);1342 expect(ReactNoop.flush()).toEqual(['compute A', 'A']);1343 ReactNoop.render(<LazyCompute compute={compute} input="A" />);1344 expect(ReactNoop.flush()).toEqual(['A']);1345 ReactNoop.render(<LazyCompute compute={compute} input="B" />);1346 expect(ReactNoop.flush()).toEqual(['compute B', 'B']);1347 });1348 });1349 describe('useRef', () => {1350 it('creates a ref object initialized with the provided value', () => {1351 jest.useFakeTimers();1352 function useDebouncedCallback(callback, ms, inputs) {1353 const timeoutID = useRef(-1);1354 useEffect(() => {1355 return function unmount() {1356 clearTimeout(timeoutID.current);1357 };1358 }, []);1359 const debouncedCallback = useCallback(1360 (...args) => {1361 clearTimeout(timeoutID.current);1362 timeoutID.current = setTimeout(callback, ms, ...args);1363 },1364 [callback, ms],1365 );1366 return useCallback(debouncedCallback, inputs);1367 }1368 let ping;1369 function App() {1370 ping = useDebouncedCallback(1371 value => {1372 ReactNoop.yield('ping: ' + value);1373 },1374 100,1375 [],1376 );1377 return null;1378 }1379 ReactNoop.render(<App />);1380 expect(ReactNoop.flush()).toEqual([]);1381 ping(1);1382 ping(2);1383 ping(3);1384 expect(ReactNoop.flush()).toEqual([]);1385 jest.advanceTimersByTime(100);1386 expect(ReactNoop.flush()).toEqual(['ping: 3']);1387 ping(4);1388 jest.advanceTimersByTime(20);1389 ping(5);1390 ping(6);1391 jest.advanceTimersByTime(80);1392 expect(ReactNoop.flush()).toEqual([]);1393 jest.advanceTimersByTime(20);1394 expect(ReactNoop.flush()).toEqual(['ping: 6']);1395 });1396 it('should return the same ref during re-renders', () => {1397 function Counter() {1398 const ref = useRef('val');1399 const [count, setCount] = useState(0);1400 const [firstRef] = useState(ref);1401 if (firstRef !== ref) {1402 throw new Error('should never change');1403 }1404 if (count < 3) {1405 setCount(count + 1);1406 }1407 return <Text text={ref.current} />;1408 }1409 ReactNoop.render(<Counter />);1410 expect(ReactNoop.flush()).toEqual(['val']);1411 ReactNoop.render(<Counter />);1412 expect(ReactNoop.flush()).toEqual(['val']);1413 });1414 });1415 describe('progressive enhancement', () => {1416 it('mount additional state', () => {1417 let updateA;1418 let updateB;1419 let updateC;1420 function App(props) {1421 const [A, _updateA] = useState(0);1422 const [B, _updateB] = useState(0);1423 updateA = _updateA;1424 updateB = _updateB;1425 let C;1426 if (props.loadC) {1427 const [_C, _updateC] = useState(0);1428 C = _C;1429 updateC = _updateC;1430 } else {1431 C = '[not loaded]';1432 }1433 return <Text text={`A: ${A}, B: ${B}, C: ${C}`} />;1434 }1435 ReactNoop.render(<App loadC={false} />);1436 expect(ReactNoop.flush()).toEqual(['A: 0, B: 0, C: [not loaded]']);1437 expect(ReactNoop.getChildren()).toEqual([1438 span('A: 0, B: 0, C: [not loaded]'),1439 ]);1440 updateA(2);1441 updateB(3);1442 expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: [not loaded]']);1443 expect(ReactNoop.getChildren()).toEqual([1444 span('A: 2, B: 3, C: [not loaded]'),1445 ]);1446 ReactNoop.render(<App loadC={true} />);1447 expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 0']);1448 expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 0')]);1449 updateC(4);1450 expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 4']);1451 expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]);1452 });1453 it('unmount state', () => {1454 let updateA;1455 let updateB;1456 let updateC;1457 function App(props) {1458 const [A, _updateA] = useState(0);1459 const [B, _updateB] = useState(0);1460 updateA = _updateA;1461 updateB = _updateB;1462 let C;1463 if (props.loadC) {1464 const [_C, _updateC] = useState(0);1465 C = _C;1466 updateC = _updateC;1467 } else {1468 C = '[not loaded]';1469 }1470 return <Text text={`A: ${A}, B: ${B}, C: ${C}`} />;1471 }1472 ReactNoop.render(<App loadC={true} />);1473 expect(ReactNoop.flush()).toEqual(['A: 0, B: 0, C: 0']);1474 expect(ReactNoop.getChildren()).toEqual([span('A: 0, B: 0, C: 0')]);1475 updateA(2);1476 updateB(3);1477 updateC(4);1478 expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 4']);1479 expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]);1480 ReactNoop.render(<App loadC={false} />);1481 expect(() => ReactNoop.flush()).toThrow(1482 'Rendered fewer hooks than expected. This may be caused by an ' +1483 'accidental early return statement.',1484 );1485 });1486 it('unmount effects', () => {1487 function App(props) {1488 useEffect(() => {1489 ReactNoop.yield('Mount A');1490 return () => {1491 ReactNoop.yield('Unmount A');1492 };1493 }, []);1494 if (props.showMore) {1495 useEffect(() => {1496 ReactNoop.yield('Mount B');1497 return () => {1498 ReactNoop.yield('Unmount B');1499 };1500 }, []);1501 }1502 return null;1503 }1504 ReactNoop.render(<App showMore={false} />);1505 expect(ReactNoop.flush()).toEqual([]);1506 flushPassiveEffects();1507 expect(ReactNoop.clearYields()).toEqual(['Mount A']);1508 ReactNoop.render(<App showMore={true} />);1509 expect(ReactNoop.flush()).toEqual([]);1510 flushPassiveEffects();1511 expect(ReactNoop.clearYields()).toEqual(['Mount B']);1512 ReactNoop.render(<App showMore={false} />);1513 expect(() => ReactNoop.flush()).toThrow(1514 'Rendered fewer hooks than expected. This may be caused by an ' +1515 'accidental early return statement.',1516 );1517 });1518 });...
ReactFiberClassComponent.js
Source:ReactFiberClassComponent.js
...17 }18 update.callback = callback;19 }20 if (revertPassiveEffectsChange) {21 flushPassiveEffects();22 }23 enqueueUpdate(fiber, update);24 scheduleWork(fiber, expirationTime);25 },26 enqueueReplaceState(inst, payload, callback) {27 const fiber = getInstance(inst);28 const currentTime = requestCurrentTime();29 const suspenseConfig = requestCurrentSuspenseConfig();30 const expirationTime = computeExpirationForFiber(31 currentTime,32 fiber,33 suspenseConfig,34 );35 const update = createUpdate(expirationTime, suspenseConfig);36 update.tag = ReplaceState;37 update.payload = payload;38 if (callback !== undefined && callback !== null) {39 if (__DEV__) {40 warnOnInvalidCallback(callback, 'replaceState');41 }42 update.callback = callback;43 }44 if (revertPassiveEffectsChange) {45 flushPassiveEffects();46 }47 enqueueUpdate(fiber, update);48 scheduleWork(fiber, expirationTime);49 },50 enqueueForceUpdate(inst, callback) {51 const fiber = getInstance(inst);52 const currentTime = requestCurrentTime();53 const suspenseConfig = requestCurrentSuspenseConfig();54 const expirationTime = computeExpirationForFiber(55 currentTime,56 fiber,57 suspenseConfig,58 );59 const update = createUpdate(expirationTime, suspenseConfig);60 update.tag = ForceUpdate;61 if (callback !== undefined && callback !== null) {62 if (__DEV__) {63 warnOnInvalidCallback(callback, 'forceUpdate');64 }65 update.callback = callback;66 }67 if (revertPassiveEffectsChange) {68 flushPassiveEffects();69 }70 enqueueUpdate(fiber, update);71 scheduleWork(fiber, expirationTime);72 },73};74function mapSingleChildIntoContext(bookKeeping, child, childKey) {75 const { result, keyPrefix, func, context } = bookKeeping;76 let mappedChild = func.call(context, child, bookKeeping.count++);77 if (Array.isArray(mappedChild)) {78 mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);79 } else if (mappedChild != null) {80 if (isValidElement(mappedChild)) {81 mappedChild = cloneAndReplaceKey(82 mappedChild,...
Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 await page.click('text=Get Started');7 await page.waitForSelector('text=Quick Start');8 await page.flushPassiveEffects();9 await page.screenshot({ path: 'example.png' });10 await browser.close();11})();
Using AI Code Generation
1const playwright = require('playwright');2(async () => {3 const browser = await playwright['chromium'].launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 await page.evaluate(() => {7 document.querySelector('input[name="q"]').value = 'Playwright';8 document.querySelector('input[name="q"]').dispatchEvent(new Event('input', { bubbles: true }));9 document.querySelector('input[name="q"]').dispatchEvent(new Event('keydown', { bubbles: true }));10 });11 await page.flushPassiveEffects();12 await page.screenshot({ path: 'page.png' });13 await browser.close();14})();15flushPassiveEffects()16flushMicrotasks()17flushPromises()18frame()19goBack(options?: NavigationOptions): Promise<Response | null>20goForward(options?: NavigationOptions): Promise<Response | null>21goto(url: string, options?: NavigationOptions): Promise<Response>22isClosed()23isJavaScriptEnabled()24keyboard()25mainFrame()26mouse()27off(event: string
Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 await page.waitForLoadState('networkidle');7 await page.evaluate(() => {8 window.flushPassiveEffects();9 });10 await browser.close();11})();12 at CDPSession.send (C:\Users\user\playwright\node_modules\playwright\lib\cdp\cdpSession.js:111:25)13 at ExecutionContext._evaluateInternal (C:\Users\user\playwright\node_modules\playwright\lib\client\executionContext.js:91:51)14 at ExecutionContext.evaluate (C:\Users\user\playwright\node_modules\playwright\lib\client\executionContext.js:38:17)15 at processTicksAndRejections (internal/process/task_queues.js:97:5)
Using AI Code Generation
1const playwright = require('playwright');2(async () => {3 const browser = await playwright.chromium.launch();4 const page = await browser.newPage();5 await page.evaluate(() => {6 window.flushPassiveEffects();7 });8 await browser.close();9})();
Using AI Code Generation
1const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');2const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');3const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');4const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');5const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');6const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');7const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');8const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');9const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');10const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');11const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');12const { flushPassiveEffects } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');13const { flushPassiveEffects } = require('playwright/lib/server/supplements
Using AI Code Generation
1const { flushPassiveEffects } = require('@playwright/test/lib/server/frames');2const { chromium, webkit } = require('playwright');3async function main() {4 const browser = await chromium.launch({ headless: false });5 const context = await browser.newContext();6 const page = await context.newPage();7 await flushPassiveEffects(page);8 await page.screenshot({ path: 'example.png' });9 await browser.close();10}11main();12const { test } = require('@playwright/test');13test('should wait for passive effects to flush', async ({ page }) => {14 await page.waitForPassiveEffects();15 await page.screenshot({ path: 'example.png' });16});
LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!