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: stringUsing 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/supplementsUsing 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!!
