Repository: yesmeck/react-with-hooks Branch: master Commit: e1ff16b19016 Files: 33 Total size: 179.8 KB Directory structure: gitextract_5sudl4xz/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── babel.config.js ├── index.d.ts ├── package.json ├── polyfill.js ├── scripts/ │ ├── alertFlag.sh │ └── jest/ │ ├── matchers/ │ │ └── toWarnDev.js │ ├── setupTests.js │ └── shouldIgnoreConsoleError.js ├── src/ │ ├── ReactCurrentDispatcher.js │ ├── ReactHookEffectTags.js │ ├── ReactHooks.js │ ├── ReactSideEffectTags.js │ ├── __mocks__/ │ │ └── scheduleCallback.js │ ├── index.js │ ├── objectIs.js │ ├── polyfill.js │ ├── scheduleCallback.js │ └── withHooks.js ├── tests/ │ ├── ReactHooksWithNoopRenderer.test.js │ └── ReactHooksWithReactDOM.test.js └── vendors/ └── react-noop-renderer/ ├── LICENSE ├── README.md ├── cjs/ │ ├── react-noop-renderer-persistent.development.js │ ├── react-noop-renderer-server.development.js │ └── react-noop-renderer.development.js ├── index.js ├── package.json ├── persistent.js └── server.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /node_modules /lib /coverage /yarn.lock /yarn-error.log ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - stable script: - npm test -- --coverage - bash <(curl -s https://codecov.io/bash) ================================================ FILE: LICENSE ================================================ The MIT License Copyright (c) 2018-present Wei Zhu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # react-with-hooks [![Build Status](https://img.shields.io/travis/yesmeck/react-with-hooks.svg?style=flat-square)](https://travis-ci.org/yesmeck/react-with-hooks) ![codecov](https://img.shields.io/codecov/c/github/yesmeck/react-with-hooks.svg?style=flat-square) Polyfill and ponyfill for the [React Hooks API](https://reactjs.org/docs/hooks-intro.html). Works on React Native! ⚠️The code on master branch is still WIP. ## Install ```bash $ npm i react-with-hooks --save ``` ## Usage You can use `react-with-hooks` as a polyfill; in this case, when you later transition to native React Hooks you will only need to remove the `import 'react-with-hooks/polyfill'` statement: ```javascript import 'react-with-hooks/polyfill'; // import the polyfill in the entry of your application import React, { useState, useEffect } from 'react'; const Counter = () => { const [ count, setCount ] = useState(0); useEffect(() => { document.title = "count is " + count; }) return (
{count}
); }; ``` Alternatively, you can use this library as a ponyfill with the `withHooks` helper. In this case, you will have to refactor your code later when you transition to use native React Hooks. ```javascript import React from 'react'; import withHooks, { useState, useEffect } from 'react-with-hooks'; const Counter = withHooks(() => { const [ count, setCount ] = useState(0); useEffect(() => { document.title = "count is " + count; }) return (
{count}
); }); ``` [Live Demo](https://codesandbox.io/s/olx6zp44n6) ## API Reference - Basic Hooks - [useState](https://reactjs.org/docs/hooks-reference.html#usestate) - [useEffect](https://reactjs.org/docs/hooks-reference.html#useeffect) - [useContext](https://reactjs.org/docs/hooks-reference.html#usecontext) - Additional Hooks - [useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer) - [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback) - [useMemo](https://reactjs.org/docs/hooks-reference.html#usememo) - [useRef](https://reactjs.org/docs/hooks-reference.html#useref) - [useImperativeHandle](https://reactjs.org/docs/hooks-reference.html#useimperativehandle) - [useLayoutEffect](https://reactjs.org/docs/hooks-reference.html#uselayouteffect) ## License [MIT](./LICENSE) ================================================ FILE: babel.config.js ================================================ module.exports = { presets: ['@babel/preset-env', '@babel/preset-react'], plugins: [ '@babel/plugin-proposal-class-properties' ] }; ================================================ FILE: index.d.ts ================================================ import * as React from 'react'; type WithHooks = (component: React.SFC) => React.SFC; declare function useContext(context: React.Context): T; declare function useState(initialState: S | (() => S)): [S, React.Dispatch>]; declare function useReducer( reducer: React.Reducer, initialState: S, initialAction?: A | null, ): [S, React.Dispatch]; declare function useRef(initialValue: T): React.MutableRefObject; declare function useRef(initialValue: T | null): React.RefObject; declare function useMutationEffect(effect: React.EffectCallback, inputs?: React.InputIdentityList): void; declare function useLayoutEffect(effect: React.EffectCallback, inputs?: React.InputIdentityList): void; declare function useEffect(effect: React.EffectCallback, inputs?: React.InputIdentityList): void; declare function useImperativeHandle( ref: React.Ref | undefined, init: () => R, inputs?: React.InputIdentityList, ): void; declare function useCallback any>(callback: T, inputs: React.InputIdentityList): T; declare function useMemo(factory: () => T, inputs: React.InputIdentityList): T; declare var withHooks: WithHooks; export { useContext, useState, useReducer, useRef, useMutationEffect, useLayoutEffect, useEffect, useImperativeHandle, useCallback, useMemo, } export default withHooks; ================================================ FILE: package.json ================================================ { "name": "react-with-hooks", "version": "1.1.6", "description": "react hooks polyfill", "main": "lib/index.js", "files": [ "lib", "index.d.ts", "polyfill.js" ], "repository": { "type": "git", "url": "https://github.com/yesmeck/react-with-hooks" }, "bugs": { "url": "https://github.com/yesmeck/react-with-hooks/issues" }, "keywords": [ "react", "hooks" ], "author": "yesmeck ", "license": "MIT", "scripts": { "test": "NODE_ENV=development jest", "build": "babel src -d lib", "prepack": "npm run build", "postinstall": "./scripts/alertFlag.sh" }, "jest": { "setupFilesAfterEnv": [ "scripts/jest/setupTests.js" ], "collectCoverageFrom": [ "src/**/*.js" ] }, "typings": "index.d.ts", "devDependencies": { "@babel/cli": "^7.1.2", "@babel/core": "^7.1.2", "@babel/plugin-proposal-class-properties": "^7.1.0", "@babel/preset-env": "^7.1.0", "@babel/preset-react": "^7.0.0", "babel-jest": "^24.0.0", "chalk": "^2.4.2", "jest": "^24.0.0", "jest-diff": "^24.0.0", "jest-dom": "^3.1.1", "react": "^16.8.1", "react-dom": "^16.8.1", "react-reconciler": "^0.19.1", "react-testing-library": "^5.8.0", "regenerator-runtime": "^0.12.1" }, "dependencies": { "invariant": "^2.2.4", "react-is": "^16.8.1" } } ================================================ FILE: polyfill.js ================================================ module.exports = require('./lib/polyfill'); ================================================ FILE: scripts/alertFlag.sh ================================================ #!/bin/sh perl -i -pe's/var debugRenderPhaseSideEffectsForStrictMode = true;/var debugRenderPhaseSideEffectsForStrictMode = false;/' node_modules/react-reconciler/cjs/react-reconciler.development.js ================================================ FILE: scripts/jest/matchers/toWarnDev.js ================================================ 'use strict'; const jestDiff = require('jest-diff'); const util = require('util'); const shouldIgnoreConsoleError = require('../shouldIgnoreConsoleError'); function normalizeCodeLocInfo(str) { return str && str.replace(/at .+?:\d+/g, 'at **'); } const createMatcherFor = consoleMethod => function matcher(callback, expectedMessages, options = {}) { if (__DEV__) { // Warn about incorrect usage of matcher. if (typeof expectedMessages === 'string') { expectedMessages = [expectedMessages]; } else if (!Array.isArray(expectedMessages)) { throw Error( `toWarnDev() requires a parameter of type string or an array of strings ` + `but was given ${typeof expectedMessages}.` ); } if ( options != null && (typeof options !== 'object' || Array.isArray(options)) ) { throw new Error( 'toWarnDev() second argument, when present, should be an object. ' + 'Did you forget to wrap the messages into an array?' ); } if (arguments.length > 3) { // `matcher` comes from Jest, so it's more than 2 in practice throw new Error( 'toWarnDev() received more than two arguments. ' + 'Did you forget to wrap the messages into an array?' ); } const withoutStack = options.withoutStack; const warningsWithoutComponentStack = []; const warningsWithComponentStack = []; const unexpectedWarnings = []; let lastWarningWithMismatchingFormat = null; let lastWarningWithExtraComponentStack = null; // Catch errors thrown by the callback, // But only rethrow them if all test expectations have been satisfied. // Otherwise an Error in the callback can mask a failed expectation, // and result in a test that passes when it shouldn't. let caughtError; const isLikelyAComponentStack = message => typeof message === 'string' && message.includes('\n in '); const consoleSpy = (format, ...args) => { // Ignore uncaught errors reported by jsdom // and React addendums because they're too noisy. if ( consoleMethod === 'error' && shouldIgnoreConsoleError(format, args) ) { return; } const message = util.format(format, ...args); const normalizedMessage = normalizeCodeLocInfo(message); // Remember if the number of %s interpolations // doesn't match the number of arguments. // We'll fail the test if it happens. let argIndex = 0; format.replace(/%s/g, () => argIndex++); if (argIndex !== args.length) { lastWarningWithMismatchingFormat = { format, args, expectedArgCount: argIndex, }; } // Protect against accidentally passing a component stack // to warning() which already injects the component stack. if ( args.length >= 2 && isLikelyAComponentStack(args[args.length - 1]) && isLikelyAComponentStack(args[args.length - 2]) ) { lastWarningWithExtraComponentStack = { format, }; } for (let index = 0; index < expectedMessages.length; index++) { const expectedMessage = expectedMessages[index]; if ( normalizedMessage === expectedMessage || normalizedMessage.includes(expectedMessage) ) { if (isLikelyAComponentStack(normalizedMessage)) { warningsWithComponentStack.push(normalizedMessage); } else { warningsWithoutComponentStack.push(normalizedMessage); } expectedMessages.splice(index, 1); return; } } let errorMessage; if (expectedMessages.length === 0) { errorMessage = 'Unexpected warning recorded: ' + this.utils.printReceived(normalizedMessage); } else if (expectedMessages.length === 1) { errorMessage = 'Unexpected warning recorded: ' + jestDiff(expectedMessages[0], normalizedMessage); } else { errorMessage = 'Unexpected warning recorded: ' + jestDiff(expectedMessages, [normalizedMessage]); } // Record the call stack for unexpected warnings. // We don't throw an Error here though, // Because it might be suppressed by ReactFiberScheduler. unexpectedWarnings.push(new Error(errorMessage)); }; // TODO Decide whether we need to support nested toWarn* expectations. // If we don't need it, add a check here to see if this is already our spy, // And throw an error. const originalMethod = console[consoleMethod]; // Avoid using Jest's built-in spy since it can't be removed. console[consoleMethod] = consoleSpy; try { callback(); } catch (error) { caughtError = error; } finally { // Restore the unspied method so that unexpected errors fail tests. console[consoleMethod] = originalMethod; // Any unexpected Errors thrown by the callback should fail the test. // This should take precedence since unexpected errors could block warnings. if (caughtError) { throw caughtError; } // Any unexpected warnings should be treated as a failure. if (unexpectedWarnings.length > 0) { return { message: () => unexpectedWarnings[0].stack, pass: false, }; } // Any remaining messages indicate a failed expectations. if (expectedMessages.length > 0) { return { message: () => `Expected warning was not recorded:\n ${this.utils.printReceived( expectedMessages[0] )}`, pass: false, }; } if (typeof withoutStack === 'number') { // We're expecting a particular number of warnings without stacks. if (withoutStack !== warningsWithoutComponentStack.length) { return { message: () => `Expected ${withoutStack} warnings without a component stack but received ${ warningsWithoutComponentStack.length }:\n` + warningsWithoutComponentStack.map(warning => this.utils.printReceived(warning) ), pass: false, }; } } else if (withoutStack === true) { // We're expecting that all warnings won't have the stack. // If some warnings have it, it's an error. if (warningsWithComponentStack.length > 0) { return { message: () => `Received warning unexpectedly includes a component stack:\n ${this.utils.printReceived( warningsWithComponentStack[0] )}\nIf this warning intentionally includes the component stack, remove ` + `{withoutStack: true} from the toWarnDev() call. If you have a mix of ` + `warnings with and without stack in one toWarnDev() call, pass ` + `{withoutStack: N} where N is the number of warnings without stacks.`, pass: false, }; } } else if (withoutStack === false || withoutStack === undefined) { // We're expecting that all warnings *do* have the stack (default). // If some warnings don't have it, it's an error. if (warningsWithoutComponentStack.length > 0) { return { message: () => `Received warning unexpectedly does not include a component stack:\n ${this.utils.printReceived( warningsWithoutComponentStack[0] )}\nIf this warning intentionally omits the component stack, add ` + `{withoutStack: true} to the toWarnDev() call.`, pass: false, }; } } else { throw Error( `The second argument for toWarnDev(), when specified, must be an object. It may have a ` + `property called "withoutStack" whose value may be undefined, boolean, or a number. ` + `Instead received ${typeof withoutStack}.` ); } if (lastWarningWithMismatchingFormat !== null) { return { message: () => `Received ${ lastWarningWithMismatchingFormat.args.length } arguments for a message with ${ lastWarningWithMismatchingFormat.expectedArgCount } placeholders:\n ${this.utils.printReceived( lastWarningWithMismatchingFormat.format )}`, pass: false, }; } if (lastWarningWithExtraComponentStack !== null) { return { message: () => `Received more than one component stack for a warning:\n ${this.utils.printReceived( lastWarningWithExtraComponentStack.format )}\nDid you accidentally pass a stack to warning() as the last argument? ` + `Don't forget warning() already injects the component stack automatically.`, pass: false, }; } return {pass: true}; } } else { // Any uncaught errors or warnings should fail tests in production mode. callback(); return {pass: true}; } }; module.exports = { toLowPriorityWarnDev: createMatcherFor('warn'), toWarnDev: createMatcherFor('error'), }; ================================================ FILE: scripts/jest/setupTests.js ================================================ 'use strict'; const chalk = require('chalk'); const util = require('util'); const shouldIgnoreConsoleError = require('./shouldIgnoreConsoleError'); const env = jasmine.getEnv(); const NODE_ENV = process.env.NODE_ENV; if (NODE_ENV !== 'development' && NODE_ENV !== 'production') { throw new Error('NODE_ENV must either be set to development or production.'); } global.__DEV__ = NODE_ENV === 'development'; const expect = global.expect; expect.extend({ ...require('./matchers/toWarnDev'), }); ['error', 'warn'].forEach(methodName => { const unexpectedConsoleCallStacks = []; const newMethod = function(format, ...args) { // Ignore uncaught errors reported by jsdom // and React addendums because they're too noisy. if (methodName === 'error' && shouldIgnoreConsoleError(format, args)) { return; } // Capture the call stack now so we can warn about it later. // The call stack has helpful information for the test author. // Don't throw yet though b'c it might be accidentally caught and suppressed. const stack = new Error().stack; unexpectedConsoleCallStacks.push([stack.substr(stack.indexOf('\n') + 1), util.format(format, ...args)]); }; console[methodName] = newMethod; env.beforeEach(() => { unexpectedConsoleCallStacks.length = 0; }); env.afterEach(() => { if (console[methodName] !== newMethod && !isSpy(console[methodName])) { throw new Error(`Test did not tear down console.${methodName} mock properly.`); } if (unexpectedConsoleCallStacks.length > 0) { const messages = unexpectedConsoleCallStacks.map( ([stack, message]) => `${chalk.red(message)}\n` + `${stack .split('\n') .map(line => chalk.gray(line)) .join('\n')}`, ); const message = `Expected test not to call ${chalk.bold(`console.${methodName}()`)}.\n\n` + 'If the warning is expected, test for it explicitly by:\n' + `1. Using the ${chalk.bold('.toWarnDev()')} / ${chalk.bold('.toLowPriorityWarnDev()')} matchers, or...\n` + `2. Mock it out using ${chalk.bold('spyOnDev')}(console, '${methodName}') or ${chalk.bold( 'spyOnProd', )}(console, '${methodName}'), and test that the warning occurs.`; throw new Error(`${message}\n\n${messages.join('\n\n')}`); } }); }); ================================================ FILE: scripts/jest/shouldIgnoreConsoleError.js ================================================ 'use strict'; module.exports = function shouldIgnoreConsoleError(format, args) { if (__DEV__) { if (typeof format === 'string') { if (format.indexOf('Error: Uncaught [') === 0) { // This looks like an uncaught error from invokeGuardedCallback() wrapper // in development that is reported by jsdom. Ignore because it's noisy. return true; } if (format.indexOf('The above error occurred') === 0) { // This looks like an error addendum from ReactFiberErrorLogger. // Ignore it too. return true; } } } else { if ( format != null && typeof format.message === 'string' && typeof format.stack === 'string' && args.length === 0 ) { // In production, ReactFiberErrorLogger logs error objects directly. // They are noisy too so we'll try to ignore them. return true; } } // Looks legit return false; }; ================================================ FILE: src/ReactCurrentDispatcher.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ /** * Keeps track of the current dispatcher. */ const ReactCurrentDispatcher = { /** * @internal * @type {ReactComponent} */ current: null, }; export default ReactCurrentDispatcher; ================================================ FILE: src/ReactHookEffectTags.js ================================================ export const NoEffect = /* */ 0b00000000; export const UnmountSnapshot = /* */ 0b00000010; export const UnmountMutation = /* */ 0b00000100; export const MountMutation = /* */ 0b00001000; export const UnmountLayout = /* */ 0b00010000; export const MountLayout = /* */ 0b00100000; export const MountPassive = /* */ 0b01000000; export const UnmountPassive = /* */ 0b10000000; ================================================ FILE: src/ReactHooks.js ================================================ /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ import invariant from 'invariant'; import ReactCurrentDispatcher from './ReactCurrentDispatcher'; function resolveDispatcher() { const dispatcher = ReactCurrentDispatcher.current; invariant( dispatcher !== null, 'Hooks can only be called inside the body of a function component. ' + '(https://fb.me/react-invalid-hook-call)', ); return dispatcher; } export function useContext( context, ) { const dispatcher = resolveDispatcher(); return dispatcher.useContext(context); } export function useState(initialState) { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); } export function useReducer( reducer, initialArg, init, ) { const dispatcher = resolveDispatcher(); return dispatcher.useReducer(reducer, initialArg, init); } export function useRef(initialValue) { const dispatcher = resolveDispatcher(); return dispatcher.useRef(initialValue); } export function useEffect( create, inputs, ) { const dispatcher = resolveDispatcher(); return dispatcher.useEffect(create, inputs); } export function useLayoutEffect( create, inputs, ) { const dispatcher = resolveDispatcher(); return dispatcher.useLayoutEffect(create, inputs); } export function useCallback( callback, inputs, ) { const dispatcher = resolveDispatcher(); return dispatcher.useCallback(callback, inputs); } export function useMemo( create, inputs, ) { const dispatcher = resolveDispatcher(); return dispatcher.useMemo(create, inputs); } export function useImperativeHandle( ref, create, inputs, ) { const dispatcher = resolveDispatcher(); return dispatcher.useImperativeHandle(ref, create, inputs); } ================================================ FILE: src/ReactSideEffectTags.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ // Don't change these two values. They're used by React Dev Tools. export const NoEffect = /* */ 0b000000000000; export const PerformedWork = /* */ 0b000000000001; // You can change the rest (and add more). export const Placement = /* */ 0b000000000010; export const Update = /* */ 0b000000000100; export const PlacementAndUpdate = /* */ 0b000000000110; export const Deletion = /* */ 0b000000001000; export const ContentReset = /* */ 0b000000010000; export const Callback = /* */ 0b000000100000; export const DidCapture = /* */ 0b000001000000; export const Ref = /* */ 0b000010000000; export const Snapshot = /* */ 0b000100000000; export const Passive = /* */ 0b001000000000; // Passive & Update & Callback & Ref & Snapshot export const LifecycleEffectMask = /* */ 0b001110100100; // Union of all host effects export const HostEffectMask = /* */ 0b001111111111; export const Incomplete = /* */ 0b010000000000; export const ShouldCapture = /* */ 0b100000000000; ================================================ FILE: src/__mocks__/scheduleCallback.js ================================================ let pendingWorks = []; const scheduleCallback = callback => { pendingWorks.push(callback); return () => { pendingWorks = pendingWorks.filter(cb => cb === callback); }; }; scheduleCallback.flush = () => { let work = pendingWorks.shift(); while (work !== undefined) { work(); work = pendingWorks.shift(); } }; export default scheduleCallback; ================================================ FILE: src/index.js ================================================ import React from 'react'; import withHooks from './withHooks'; import * as hooks from './ReactHooks'; const useNative = !!React.useState; export const useState = useNative ? React.useState : hooks.useState; export const useEffect = useNative ? React.useEffect : hooks.useEffect; export const useContext = useNative ? React.useContext : hooks.useContext; export const useReducer = useNative ? React.useReducer : hooks.useReducer; export const useCallback = useNative ? React.useCallback : hooks.useCallback; export const useMemo = useNative ? React.useMemo : hooks.useMemo; export const useRef = useNative ? React.useRef : hooks.useRef; export const useImperativeHandle = useNative ? React.useImperativeHandle : hooks.useImperativeHandle; export const useMutationEffect = useNative ? React.useMutationEffect : hooks.useMutationEffect; export const useLayoutEffect = useNative ? React.useLayoutEffect : hooks.useLayoutEffect; export default useNative ? (fn) => fn : withHooks; ================================================ FILE: src/objectIs.js ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ /** * inlined Object.is polyfill to avoid requiring consumers ship their own * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is */ function is(x, y) { return ( (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare ); } export default is; ================================================ FILE: src/polyfill.js ================================================ import React from 'react'; import * as ReactIs from 'react-is'; import withHooks from './withHooks'; import * as hooks from './ReactHooks'; const useNative = __DEV__ ? false : !!React.useState; const nativeCreateElement = React.createElement; function shouldConstruct(Component) { const prototype = Component.prototype; return !!(prototype && prototype.isReactComponent); } function isSimpleFunctionComponent(type) { return typeof type === 'function' && !shouldConstruct(type) && type.defaultProps === undefined; } function hasHooks(fn) { return /\buse[A-Z0-9]/.test(fn.toString()); } const createElementWithHooks = (() => { const componentMap = new Map(); return (component, props, ...children) => { if (componentMap.has(component)) { return nativeCreateElement(componentMap.get(component), props, ...children); } const element = nativeCreateElement(component, props, ...children); let wrappedComponent = component; let render; if (ReactIs.isForwardRef(element)) { render = component.render; wrappedComponent.render = withHooks(render); componentMap.set(component, wrappedComponent); } if (ReactIs.isMemo(component)) { render = component.type; wrappedComponent.type = withHooks(render); componentMap.set(component, wrappedComponent); } if (isSimpleFunctionComponent(component) && component.__react_with_hooks !== true) { render = component; wrappedComponent = withHooks(render); componentMap.set(component, wrappedComponent); } if (!render || !hasHooks(render)) { return element; } return nativeCreateElement(wrappedComponent, props, ...children); }; })(); React.createElement = useNative ? React.createElement : createElementWithHooks; if (!useNative) { Object.keys(hooks).forEach(hook => { React[hook] = hooks[hook]; }); } ================================================ FILE: src/scheduleCallback.js ================================================ export default function scheduleCallback(callback) { const timer = requestAnimationFrame(callback); return () => { cancelAnimationFrame(timer); }; } ================================================ FILE: src/withHooks.js ================================================ import React, { createElement } from 'react'; import invariant from 'invariant'; import ReactCurrentDispatcher from './ReactCurrentDispatcher'; import { Update as UpdateEffect, Passive as PassiveEffect } from './ReactSideEffectTags'; import { NoEffect as NoHookEffect, UnmountMutation, MountLayout, UnmountPassive, MountPassive, MountMutation, UnmountLayout, } from './ReactHookEffectTags'; import is from './objectIs'; import scheduleCallback from './scheduleCallback'; const RE_RENDER_LIMIT = 25; let firstCurrentHook = null; let firstWorkInProgressHook = null; let currentHook = null; let nextCurrentHook = null; let workInProgressHook = null; let nextWorkInProgressHook = null; let didScheduleRenderPhaseUpdate = false; let currentInstance = null; let renderPhaseUpdates = null; let numberOfReRenders = 0; let componentUpdateQueue = null; let sideEffectTag = 0; let componentContext = null; let isRenderPhase = false; let didReceiveUpdate = false; let passiveHookEffects = []; function markWorkInProgressReceivedUpdate() { didReceiveUpdate = true; } function basicStateReducer(state, action) { return typeof action === 'function' ? action(state) : action; } function prepareToUseHooks(current) { currentInstance = current; firstCurrentHook = nextCurrentHook = current !== null ? current.memoizedState : null; } function resetHooks() { currentInstance = null; firstCurrentHook = null; currentHook = null; firstWorkInProgressHook = null; workInProgressHook = null; componentUpdateQueue = null; componentContext = null; didScheduleRenderPhaseUpdate = false; renderPhaseUpdates = null; numberOfReRenders = 0; isRenderPhase = false; } function mountWorkInProgressHook() { const hook = { memoizedState: null, baseState: null, queue: null, baseUpdate: null, next: null, }; if (workInProgressHook === null) { // This is the first hook in the list firstWorkInProgressHook = workInProgressHook = hook; } else { // Append to the end of the list workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; } function updateWorkInProgressHook() { // This function is used both for updates and for re-renders triggered by a // render phase update. It assumes there is either a current hook we can // clone, or a work-in-progress hook from a previous render pass that we can // use as a base. When we reach the end of the base list, we must switch to // the dispatcher used for mounts. if (nextWorkInProgressHook !== null) { // There's already a work-in-progress. Reuse it. workInProgressHook = nextWorkInProgressHook; nextWorkInProgressHook = workInProgressHook.next; currentHook = nextCurrentHook; nextCurrentHook = currentHook !== null ? currentHook.next : null; } else { // Clone from the current hook. invariant(nextCurrentHook !== null, 'Rendered more hooks than during the previous render.'); currentHook = nextCurrentHook; const newHook = { memoizedState: currentHook.memoizedState, baseState: currentHook.baseState, queue: currentHook.queue, baseUpdate: currentHook.baseUpdate, next: null, }; if (workInProgressHook === null) { // This is the first hook in the list. workInProgressHook = firstWorkInProgressHook = newHook; } else { // Append to the end of the list. workInProgressHook = workInProgressHook.next = newHook; } nextCurrentHook = currentHook.next; } return workInProgressHook; } function createFunctionComponentUpdateQueue() { return { lastEffect: null, }; } function areHookInputsEqual(nextDeps, prevDeps) { if (prevDeps === null) { return false; } for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { if (is(nextDeps[i], prevDeps[i])) { continue; } return false; } return true; } function mountState(initialState) { const hook = mountWorkInProgressHook(); if (typeof initialState === 'function') { initialState = initialState(); } hook.memoizedState = hook.baseState = initialState; const queue = (hook.queue = { last: null, dispatch: null, eagerReducer: basicStateReducer, eagerState: initialState, }); const dispatch = (queue.dispatch = dispatchAction.bind(null, currentInstance, queue)); return [hook.memoizedState, dispatch]; } function updateState(initialState) { return updateReducer(basicStateReducer, initialState); } function pushEffect(tag, create, destroy, deps) { const effect = { tag, create, destroy, deps, // Circular next: null, }; if (componentUpdateQueue === null) { componentUpdateQueue = createFunctionComponentUpdateQueue(); componentUpdateQueue.lastEffect = effect.next = effect; } else { const lastEffect = componentUpdateQueue.lastEffect; if (lastEffect === null) { componentUpdateQueue.lastEffect = effect.next = effect; } else { const firstEffect = lastEffect.next; lastEffect.next = effect; effect.next = firstEffect; componentUpdateQueue.lastEffect = effect; } } return effect; } function pushContext(context) { const _context = { Consumer: context.Consumer, next: null, }; if (componentContext === null) { componentContext = _context; } else { componentContext.next = _context; } return _context; } function mountRef(initialValue) { const hook = mountWorkInProgressHook(); const ref = { current: initialValue }; hook.memoizedState = ref; return ref; } function updateRef() { const hook = updateWorkInProgressHook(); return hook.memoizedState; } function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; sideEffectTag |= fiberEffectTag; hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps); } function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; let destroy = undefined; if (currentHook !== null) { const prevEffect = currentHook.memoizedState; destroy = prevEffect.destroy; if (nextDeps !== null) { const prevDeps = prevEffect.deps; if (areHookInputsEqual(nextDeps, prevDeps)) { pushEffect(NoHookEffect, create, destroy, nextDeps); return; } } } sideEffectTag |= fiberEffectTag; hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps); } function mountEffect(create, deps) { return mountEffectImpl(UpdateEffect | PassiveEffect, UnmountPassive | MountPassive, create, deps); } function updateEffect(create, deps) { return updateEffectImpl(UpdateEffect | PassiveEffect, UnmountPassive | MountPassive, create, deps); } function mountLayoutEffect(create, deps) { return mountEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, deps); } function updateLayoutEffect(create, deps) { return updateEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, deps); } function imperativeHandleEffect(create, ref) { if (typeof ref === 'function') { const refCallback = ref; const inst = create(); refCallback(inst); return () => { refCallback(null); }; } else if (ref !== null && ref !== undefined) { const refObject = ref; const inst = create(); refObject.current = inst; return () => { refObject.current = null; }; } } function mountImperativeHandle(ref, create, deps) { // TODO: If deps are provided, should we skip comparing the ref itself? const effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : [ref]; return mountEffectImpl( UpdateEffect, UnmountMutation | MountLayout, imperativeHandleEffect.bind(null, create, ref), effectDeps, ); } function updateImperativeHandle(ref, create, deps) { // TODO: If deps are provided, should we skip comparing the ref itself? const effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : [ref]; return updateEffectImpl( UpdateEffect, UnmountMutation | MountLayout, imperativeHandleEffect.bind(null, create, ref), effectDeps, ); } function mountContext(Context) { pushContext(Context); return Context._currentValue; } function mountReducer(reducer, initialArg, init) { const hook = mountWorkInProgressHook(); let initialState; if (init !== undefined) { initialState = init(initialArg); } else { initialState = initialArg; } hook.memoizedState = hook.baseState = initialState; const queue = (hook.queue = { last: null, dispatch: null, eagerReducer: reducer, eagerState: initialState, }); const dispatch = (queue.dispatch = dispatchAction.bind( null, // Flow doesn't know this is non-null, but we do. currentInstance, queue, )); return [hook.memoizedState, dispatch]; } function updateReducer(reducer, initialArg, init) { const hook = updateWorkInProgressHook(); const queue = hook.queue; invariant(queue !== null, 'Should have a queue. This is likely a bug in React. Please file an issue.'); if (numberOfReRenders > 0) { // This is a re-render. Apply the new render phase updates to the previous // work-in-progress hook. const dispatch = queue.dispatch; if (renderPhaseUpdates !== null) { // Render phase updates are stored in a map of queue -> linked list const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); if (firstRenderPhaseUpdate !== undefined) { renderPhaseUpdates.delete(queue); let newState = hook.memoizedState; let update = firstRenderPhaseUpdate; do { // Process this render phase update. We don't have to check the // priority because it will always be the same as the current // render's. const action = update.action; newState = reducer(newState, action); update = update.next; } while (update !== null); // Mark that the fiber performed work, but only if the new state is // different from the current state. if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); } hook.memoizedState = newState; // Don't persist the state accumlated from the render phase updates to // the base state unless the queue is empty. // TODO: Not sure if this is the desired semantics, but it's what we // do for gDSFP. I can't remember why. if (hook.baseUpdate === queue.last) { hook.baseState = newState; } return [newState, dispatch]; } } return [hook.memoizedState, dispatch]; } // The last update in the entire queue const last = queue.last; // The last update that is part of the base state. const baseUpdate = hook.baseUpdate; const baseState = hook.baseState; // Find the first unprocessed update. let first; if (baseUpdate !== null) { if (last !== null) { // For the first update, the queue is a circular linked list where // `queue.last.next = queue.first`. Once the first update commits, and // the `baseUpdate` is no longer empty, we can unravel the list. last.next = null; } first = baseUpdate.next; } else { first = last !== null ? last.next : null; } if (first !== null) { let newState = baseState; let newBaseState = null; let newBaseUpdate = null; let prevUpdate = baseUpdate; let update = first; let didSkip = false; do { // Process this update. if (update.eagerReducer === reducer) { // If this update was processed eagerly, and its reducer matches the // current reducer, we can use the eagerly computed state. newState = update.eagerState; } else { const action = update.action; newState = reducer(newState, action); } prevUpdate = update; update = update.next; } while (update !== null && update !== first); if (!didSkip) { newBaseUpdate = prevUpdate; newBaseState = newState; } // Mark that the fiber performed work, but only if the new state is // different from the current state. if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); } hook.memoizedState = newState; hook.baseUpdate = newBaseUpdate; hook.baseState = newBaseState; queue.eagerReducer = reducer; queue.eagerState = newState; } const dispatch = queue.dispatch; return [hook.memoizedState, dispatch]; } function mountCallback(callback, deps) { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; hook.memoizedState = [callback, nextDeps]; return callback; } function updateCallback(callback, deps) { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; if (prevState !== null) { if (nextDeps !== null) { const prevDeps = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } } hook.memoizedState = [callback, nextDeps]; return callback; } function mountMemo(nextCreate, deps) { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; return nextValue; } function updateMemo(nextCreate, deps) { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; if (prevState !== null) { // Assume these are defined. If they're not, areHookInputsEqual will warn. if (nextDeps !== null) { const prevDeps = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } } const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; return nextValue; } function flushPassiveEffects() { passiveHookEffects.forEach(effect => { effect.cancel(); effect.callback(); }); passiveHookEffects = []; } function dispatchAction(instance, queue, action) { invariant( numberOfReRenders < RE_RENDER_LIMIT, 'Too many re-renders. React limits the number of renders to prevent ' + 'an infinite loop.', ); if (isRenderPhase) { // This is a render phase update. Stash it in a lazily-created map of // queue -> linked list of updates. After this render pass, we'll restart // and apply the stashed updates on top of the work-in-progress hook. didScheduleRenderPhaseUpdate = true; const update = { action, next: null, }; if (renderPhaseUpdates === null) { renderPhaseUpdates = new Map(); } const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); if (firstRenderPhaseUpdate === undefined) { renderPhaseUpdates.set(queue, update); } else { // Append the update to the end of the list. let lastRenderPhaseUpdate = firstRenderPhaseUpdate; while (lastRenderPhaseUpdate.next !== null) { lastRenderPhaseUpdate = lastRenderPhaseUpdate.next; } lastRenderPhaseUpdate.next = update; } } else { flushPassiveEffects(); const update = { action, next: null, }; // Append the update to the end of the list. const last = queue.last; if (last === null) { // This is the first update. Create a circular list. update.next = update; } else { const first = last.next; if (first !== null) { // Still circular. update.next = first; } last.next = update; } queue.last = update; // The queue is currently empty, which means we can eagerly compute the // next state before entering the render phase. If the new state is the // same as the current state, we may be able to bail out entirely. // const eagerReducer = queue.eagerReducer; // if (eagerReducer !== null) { // let prevDispatcher; // try { // const currentState = queue.eagerState; // const eagerState = eagerReducer(currentState, action); // // Stash the eagerly computed state, and the reducer used to compute // // it, on the update object. If the reducer hasn't changed by the // // time we enter the render phase, then the eager state can be used // // without calling the reducer again. // update.eagerReducer = eagerReducer; // update.eagerState = eagerState; // if (is(eagerState, currentState)) { // // Fast path. We can bail out without scheduling React to re-render. // // It's still possible that we'll need to rebase this update later, // // if the component re-renders for a different reason and by that // // time the reducer has changed. // return; // } // } catch (error) { // // Suppress the error. It will throw again in the render phase. // } // } instance.setState({}); } } const HooksDispatcherOnMount = { useCallback: mountCallback, useContext: mountContext, useEffect: mountEffect, useImperativeHandle: mountImperativeHandle, useLayoutEffect: mountLayoutEffect, useMemo: mountMemo, useReducer: mountReducer, useRef: mountRef, useState: mountState, }; const HooksDispatcherOnUpdate = { useCallback: updateCallback, useContext: mountContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: updateReducer, useRef: updateRef, useState: updateState, }; export default function withHooks(render) { class WithHooks extends React.Component { memoizedState = null; passiveHookEffect = null; componentDidMount() { // useLayoutEffect this.commitHookEffectList(UnmountMutation, MountMutation); this.commitHookEffectList(UnmountLayout, MountLayout); // useEffect this.createPassiveHookEffect(); this.mounted = true; } componentDidUpdate() { // useLayoutEffect this.commitHookEffectList(UnmountMutation, MountMutation); this.commitHookEffectList(UnmountLayout, MountLayout); // useEffect this.createPassiveHookEffect(); } componentWillUnmount() { this.callDestroy(); } createPassiveHookEffect() { const callback = this.commitPassiveHookEffects.bind(this); const cancel = scheduleCallback(callback); this.passiveHookEffect = { callback, cancel, }; passiveHookEffects.push(this.passiveHookEffect); } commitPassiveHookEffects() { if (this.passiveHookEffect === null) { return; } passiveHookEffects = passiveHookEffects.filter(effect => effect !== this.passiveHookEffect); this.passiveHookEffect = null; this.commitHookEffectList(UnmountPassive, NoHookEffect); this.commitHookEffectList(NoHookEffect, MountPassive); } commitHookEffectList(unmountTag, mountTag) { let lastEffect = this.updateQueue !== null ? this.updateQueue.lastEffect : null; if (lastEffect !== null) { const firstEffect = lastEffect.next; let effect = firstEffect; do { if ((effect.tag & unmountTag) !== NoHookEffect) { // Unmount const destroy = effect.destroy; effect.destroy = undefined; if (destroy !== undefined) { destroy(); } } effect = effect.next; } while (effect !== firstEffect); effect = firstEffect; do { if ((effect.tag & mountTag) !== NoHookEffect) { // Mount const create = effect.create; const destroy = create(); effect.destroy = typeof destroy === 'function' ? destroy : undefined; } effect = effect.next; } while (effect !== firstEffect); } } callDestroy() { const updateQueue = this.updateQueue; if (updateQueue !== null) { var lastEffect = updateQueue.lastEffect; if (lastEffect !== null) { var firstEffect = lastEffect.next; var effect = firstEffect; do { var destroy = effect.destroy; if (destroy !== undefined) { destroy(); } effect = effect.next; } while (effect !== firstEffect); } } } applyContext(render, context, children) { if (!children) { children = render(); } context = context || componentContext; if (context !== null) { return createElement(context.Consumer, {}, () => { if (this.mounted) { children = render(); } return context.next === null ? children : this.applyContext(render, context.next, children); }); } return children; } render() { resetHooks(); prepareToUseHooks(this); ReactCurrentDispatcher.current = nextCurrentHook === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate; const { _forwardedRef, ...rest } = this.props; isRenderPhase = true; let children = this.applyContext(() => render(rest, _forwardedRef)); if (didScheduleRenderPhaseUpdate) { do { didScheduleRenderPhaseUpdate = false; numberOfReRenders += 1; // Start over from the beginning of the list firstCurrentHook = nextCurrentHook = this.memoizedState; nextWorkInProgressHook = firstWorkInProgressHook; currentHook = null; workInProgressHook = null; componentUpdateQueue = null; ReactCurrentDispatcher.current = HooksDispatcherOnUpdate; children = render(this.props, _forwardedRef); } while (didScheduleRenderPhaseUpdate); renderPhaseUpdates = null; numberOfReRenders = 0; } this.memoizedState = firstWorkInProgressHook; this.updateQueue = componentUpdateQueue; this.effectTag |= sideEffectTag; const didRenderTooFewHooks = currentHook !== null && currentHook.next !== null; currentInstance = null; firstCurrentHook = null; currentHook = null; nextCurrentHook = null; firstWorkInProgressHook = null; workInProgressHook = null; nextWorkInProgressHook = null; componentUpdateQueue = null; sideEffectTag = 0; isRenderPhase = false; // These were reset above // didScheduleRenderPhaseUpdate = false; // renderPhaseUpdates = null; // numberOfReRenders = 0; invariant( !didRenderTooFewHooks, 'Rendered fewer hooks than expected. This may be caused by an accidental ' + 'early return statement.', ); return children; } } WithHooks.displayName = render.displayName || render.name; const wrap = (props, ref) => ; wrap.__react_with_hooks = true; wrap.displayName = `WithHooks(${WithHooks.displayName})`; return wrap; } ================================================ FILE: tests/ReactHooksWithNoopRenderer.test.js ================================================ jest.mock('../src/scheduleCallback'); let React; let ReactNoop; let scheduleCallback; let useState; let useReducer; let useEffect; let useLayoutEffect; let useCallback; let useMemo; let useRef; let useContext; let useImperativeHandle; let forwardRef; let createContext; let memo; let act; describe('hooks', () => { function span(prop) { return { type: 'span', hidden: false, children: [], prop }; } function Text(props) { ReactNoop.yield(props.text); return ; } beforeEach(() => { jest.resetModules(); require('../src/polyfill'); React = require('react'); ReactNoop = require('../vendors/react-noop-renderer'); scheduleCallback = require('../src/scheduleCallback').default; const nativeFlushPassiveEffects = ReactNoop.flushPassiveEffects; ReactNoop.flushPassiveEffects = () => { scheduleCallback.flush(); return nativeFlushPassiveEffects(); }; useState = React.useState; useReducer = React.useReducer; useEffect = React.useEffect; useLayoutEffect = React.useLayoutEffect; useCallback = React.useCallback; useMemo = React.useMemo; useRef = React.useRef; useContext = React.useContext; useImperativeHandle = React.useImperativeHandle; forwardRef = React.forwardRef; createContext = React.createContext; memo = React.memo; act = ReactNoop.act; }); describe('useState', () => { it('simple mount and update', () => { function Counter(props, ref) { const [count, updateCount] = useState(0); useImperativeHandle(ref, () => ({ updateCount })); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); act(() => counter.current.updateCount(1)); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); act(() => counter.current.updateCount(count => count + 10)); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]); }); it('lazy state initializer', () => { function Counter(props, ref) { const [count, updateCount] = useState(() => { ReactNoop.yield('getInitialState'); return props.initialState; }); useImperativeHandle(ref, () => ({ updateCount })); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['getInitialState', 'Count: 42']); expect(ReactNoop.getChildren()).toEqual([span('Count: 42')]); act(() => counter.current.updateCount(7)); expect(ReactNoop.flush()).toEqual(['Count: 7']); expect(ReactNoop.getChildren()).toEqual([span('Count: 7')]); }); it('multiple states', () => { function Counter(props, ref) { const [count, updateCount] = useState(0); const [label, updateLabel] = useState('Count'); useImperativeHandle(ref, () => ({ updateCount, updateLabel })); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); act(() => counter.current.updateCount(7)); expect(ReactNoop.flush()).toEqual(['Count: 7']); act(() => counter.current.updateLabel('Total')); expect(ReactNoop.flush()).toEqual(['Total: 7']); }); it('returns the same updater function every time', () => { let updaters = []; function Counter() { const [count, updateCount] = useState(0); updaters.push(updateCount); return ; } ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); act(() => updaters[0](1)); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); act(() => updaters[0](count => count + 10)); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]); expect(updaters).toEqual([updaters[0], updaters[0], updaters[0]]); }); it('warns on set after unmount', () => { let _updateCount; function Counter(props, ref) { const [, updateCount] = useState(0); _updateCount = updateCount; return null; } ReactNoop.render(); ReactNoop.flush(); ReactNoop.render(null); ReactNoop.flush(); expect(() => act(() => _updateCount(1))).toWarnDev( "Warning: Can't perform a React state update on an unmounted " + 'component. This is a no-op, but it indicates a memory leak in your ' + 'application. To fix, cancel all subscriptions and asynchronous ' + 'tasks in the componentWillUnmount method.\n' + ' in Counter (created by WithHooks(Counter))\n' + ' in WithHooks(Counter)', ); }); it('works with memo', () => { let _updateCount; function Counter(props) { const [count, updateCount] = useState(0); _updateCount = updateCount; return ; } Counter = memo(Counter); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.render(); expect(ReactNoop.flush()).toEqual([]); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); act(() => _updateCount(1)); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); }); }); describe('updates during the render phase', () => { it('restarts the render function and applies the new updates on top', () => { function ScrollView({ row: newRow }) { let [isScrollingDown, setIsScrollingDown] = useState(false); let [row, setRow] = useState(null); if (row !== newRow) { // Row changed since last render. Update isScrollingDown. setIsScrollingDown(row !== null && newRow > row); setRow(newRow); } return ; } ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: false')]); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: true')]); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: true')]); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: true')]); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: false')]); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Scrolling down: false')]); }); it('keeps restarting until there are no more new updates', () => { function Counter({ row: newRow }) { let [count, setCount] = useState(0); if (count < 3) { setCount(count + 1); } ReactNoop.yield('Render: ' + count); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Render: 0', 'Render: 1', 'Render: 2', 'Render: 3', 3]); expect(ReactNoop.getChildren()).toEqual([span(3)]); }); it('updates multiple times within same render function', () => { function Counter({ row: newRow }) { let [count, setCount] = useState(0); if (count < 12) { setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); } ReactNoop.yield('Render: ' + count); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual([ // Should increase by three each time 'Render: 0', 'Render: 3', 'Render: 6', 'Render: 9', 'Render: 12', 12, ]); expect(ReactNoop.getChildren()).toEqual([span(12)]); }); it('throws after too many iterations', () => { function Counter({ row: newRow }) { let [count, setCount] = useState(0); setCount(count + 1); ReactNoop.yield('Render: ' + count); return ; } ReactNoop.render(); expect(() => ReactNoop.flush()).toThrow( 'Too many re-renders. React limits the number of renders to prevent ' + 'an infinite loop.', ); }); it('works with useReducer', () => { function reducer(state, action) { return action === 'increment' ? state + 1 : state; } function Counter({ row: newRow }) { let [count, dispatch] = useReducer(reducer, 0); if (count < 3) { dispatch('increment'); } ReactNoop.yield('Render: ' + count); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Render: 0', 'Render: 1', 'Render: 2', 'Render: 3', 3]); expect(ReactNoop.getChildren()).toEqual([span(3)]); }); it('uses reducer passed at time of render, not time of dispatch', () => { // This test is a bit contrived but it demonstrates a subtle edge case. // Reducer A increments by 1. Reducer B increments by 10. function reducerA(state, action) { switch (action) { case 'increment': return state + 1; case 'reset': return 0; } } function reducerB(state, action) { switch (action) { case 'increment': return state + 10; case 'reset': return 0; } } function Counter({ row: newRow }, ref) { let [reducer, setReducer] = useState(() => reducerA); let [count, dispatch] = useReducer(reducer, 0); useImperativeHandle(ref, () => ({ dispatch })); if (count < 20) { dispatch('increment'); // Swap reducers each time we increment if (reducer === reducerA) { setReducer(() => reducerB); } else { setReducer(() => reducerA); } } ReactNoop.yield('Render: ' + count); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); expect(ReactNoop.flush()).toEqual([ // The count should increase by alternating amounts of 10 and 1 // until we reach 21. 'Render: 0', 'Render: 10', 'Render: 11', 'Render: 21', 21, ]); expect(ReactNoop.getChildren()).toEqual([span(21)]); // Test that it works on update, too. This time the log is a bit different // because we started with reducerB instead of reducerA. counter.current.dispatch('reset'); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Render: 0', 'Render: 1', 'Render: 11', 'Render: 12', 'Render: 22', 22]); expect(ReactNoop.getChildren()).toEqual([span(22)]); }); }); describe('useReducer', () => { it('simple mount and update', () => { const INCREMENT = 'INCREMENT'; const DECREMENT = 'DECREMENT'; function reducer(state, action) { switch (action) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } function Counter(props, ref) { const [count, dispatch] = useReducer(reducer, 0); useImperativeHandle(ref, () => ({dispatch})); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); act(() => counter.current.dispatch(INCREMENT)); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); act(() => { counter.current.dispatch(DECREMENT); counter.current.dispatch(DECREMENT); counter.current.dispatch(DECREMENT); }); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: -2')]); }); it('lazy init', () => { const INCREMENT = 'INCREMENT'; const DECREMENT = 'DECREMENT'; function reducer(state, action) { switch (action) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } function Counter(props, ref) { const [count, dispatch] = useReducer(reducer, props, p => { ReactNoop.yield('Init'); return p.initialCount; }); useImperativeHandle(ref, () => ({dispatch})); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Init', 'Count: 10']); expect(ReactNoop.getChildren()).toEqual([span('Count: 10')]); act(() => counter.current.dispatch(INCREMENT)); expect(ReactNoop.flush()).toEqual(['Count: 11']); expect(ReactNoop.getChildren()).toEqual([span('Count: 11')]); act(() => { counter.current.dispatch(DECREMENT); counter.current.dispatch(DECREMENT); counter.current.dispatch(DECREMENT); }); expect(ReactNoop.flush()).toEqual(['Count: 8']); expect(ReactNoop.getChildren()).toEqual([span('Count: 8')]); }); // Regression test for https://github.com/facebook/react/issues/14360 it.skip('handles dispatches with mixed priorities', () => { const INCREMENT = 'INCREMENT'; function reducer(state, action) { return action === INCREMENT ? state + 1 : state; } function Counter(props, ref) { const [count, dispatch] = useReducer(reducer, 0); useImperativeHandle(ref, () => ({dispatch})); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); act(() => { counter.current.dispatch(INCREMENT); counter.current.dispatch(INCREMENT); counter.current.dispatch(INCREMENT); }); ReactNoop.flushSync(() => { counter.current.dispatch(INCREMENT); }); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]); }); }); describe('useEffect', () => { it('simple mount and update', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Did commit [${props.count}]`); }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did commit [0]']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); // Effects are deferred until after the commit ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did commit [1]']); }); // tested in react-dom it.skip('flushes passive effects even with sibling deletions', () => { function LayoutEffect(props) { useLayoutEffect(() => { ReactNoop.yield(`Layout effect`); }); return ; } function PassiveEffect(props) { useEffect(() => { ReactNoop.yield(`Passive effect`); }, []); return ; } let passive = ; ReactNoop.render([, passive]); expect(ReactNoop.flush()).toEqual(['Layout', 'Passive', 'Layout effect']); expect(ReactNoop.getChildren()).toEqual([span('Layout'), span('Passive')]); // Destroying the first child shouldn't prevent the passive effect from // being executed ReactNoop.render([passive]); expect(ReactNoop.flush()).toEqual(['Passive effect']); expect(ReactNoop.getChildren()).toEqual([span('Passive')]); // (No effects are left to flush.) ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(null); }); it.skip('flushes passive effects even if siblings schedule an update', () => { function PassiveEffect(props) { useEffect(() => { ReactNoop.yield('Passive effect'); }); return ; } function LayoutEffect(props) { let [count, setCount] = useState(0); useLayoutEffect(() => { // Scheduling work shouldn't interfere with the queued passive effect if (count === 0) { setCount(1); } ReactNoop.yield('Layout effect ' + count); }); return ; } ReactNoop.render([, ]); act(() => { expect(ReactNoop.flush()).toEqual([ 'Passive', 'Layout', 'Layout effect 0', 'Passive effect', 'Layout', 'Layout effect 1', ]); }); expect(ReactNoop.getChildren()).toEqual([span('Passive'), span('Layout')]); }); it.skip('flushes passive effects even if siblings schedule a new root', () => { function PassiveEffect(props) { useEffect(() => { ReactNoop.yield('Passive effect'); }, []); return ; } function LayoutEffect(props) { useLayoutEffect(() => { ReactNoop.yield('Layout effect'); // Scheduling work shouldn't interfere with the queued passive effect ReactNoop.renderToRootWithID(, 'root2'); }); return ; } ReactNoop.render([, ]); expect(ReactNoop.flush()).toEqual(['Passive', 'Layout', 'Layout effect', 'Passive effect', 'New Root']); expect(ReactNoop.getChildren()).toEqual([span('Passive'), span('Layout')]); }); it( 'flushes effects serially by flushing old effects before flushing ' + "new ones, if they haven't already fired", () => { function getCommittedText() { const children = ReactNoop.getChildren(); if (children === null) { return null; } return children[0].prop; } function Counter(props) { useEffect(() => { ReactNoop.yield(`Committed state when effect was fired: ${getCommittedText()}`); }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual([0]); expect(ReactNoop.getChildren()).toEqual([span(0)]); // Before the effects have a chance to flush, schedule another update ReactNoop.render(); expect(ReactNoop.flush()).toEqual([ // The previous effect flushes before the reconciliation 'Committed state when effect was fired: 0', 1, ]); expect(ReactNoop.getChildren()).toEqual([span(1)]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Committed state when effect was fired: 1']); }, ); it('updates have async priority', () => { function Counter(props) { const [count, updateCount] = useState('(empty)'); useEffect(() => { ReactNoop.yield(`Schedule update [${props.count}]`); updateCount(props.count); }, [props.count]); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: (empty)']); expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Schedule update [0]']); expect(ReactNoop.flush()).toEqual(['Count: 0']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Schedule update [1]']); expect(ReactNoop.flush()).toEqual(['Count: 1']); }); it.skip('updates have async priority even if effects are flushed early', () => { function Counter(props) { const [count, updateCount] = useState('(empty)'); useEffect(() => { ReactNoop.yield(`Schedule update [${props.count}]`); updateCount(props.count); }, [props.count]); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: (empty)']); expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); // Rendering again should flush the previous commit's effects ReactNoop.render(); ReactNoop.flushThrough(['Schedule update [0]', 'Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); ReactNoop.batchedUpdates(() => { expect(ReactNoop.flush()).toEqual([]); }); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.flush()).toEqual(['Schedule update [1]', 'Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); }); it.skip('flushes serial effects before enqueueing work', () => { let _updateCount; function Counter(props) { const [count, updateCount] = useState(0); _updateCount = updateCount; useEffect(() => { ReactNoop.yield(`Will set count to 1`); updateCount(1); }, []); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); // Enqueuing this update forces the passive effect to be flushed -- // updateCount(1) happens first, so 2 wins. act(() => _updateCount(2)); expect(ReactNoop.flush()).toEqual(['Will set count to 1', 'Count: 2']); expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]); }); it.skip('flushes serial effects before enqueueing work (with tracing)', () => { const onInteractionScheduledWorkCompleted = jest.fn(); const onWorkCanceled = jest.fn(); SchedulerTracing.unstable_subscribe({ onInteractionScheduledWorkCompleted, onInteractionTraced: jest.fn(), onWorkCanceled, onWorkScheduled: jest.fn(), onWorkStarted: jest.fn(), onWorkStopped: jest.fn(), }); let _updateCount; function Counter(props) { const [count, updateCount] = useState(0); _updateCount = updateCount; useEffect(() => { expect(SchedulerTracing.unstable_getCurrent()).toMatchInteractions([tracingEvent]); ReactNoop.yield(`Will set count to 1`); updateCount(1); }, []); return ; } const tracingEvent = { id: 0, name: 'hello', timestamp: 0 }; SchedulerTracing.unstable_trace(tracingEvent.name, tracingEvent.timestamp, () => { ReactNoop.render(); }); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0); // Enqueuing this update forces the passive effect to be flushed -- // updateCount(1) happens first, so 2 wins. act(() => _updateCount(2)); expect(ReactNoop.flush()).toEqual(['Will set count to 1', 'Count: 2']); expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); expect(onWorkCanceled).toHaveBeenCalledTimes(0); }); it.skip('in sync mode, useEffect is deferred and updates finish synchronously ' + '(in a single batch)', () => { function Counter(props) { const [count, updateCount] = useState('(empty)'); useEffect(() => { // Update multiple times. These should all be batched together in // a single render. updateCount(props.count); updateCount(props.count); updateCount(props.count); updateCount(props.count); updateCount(props.count); updateCount(props.count); }, [props.count]); return ; } ReactNoop.renderLegacySyncRoot(); // Even in sync mode, effects are deferred until after paint expect(ReactNoop.flush()).toEqual(['Count: (empty)']); expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); // Now fire the effects ReactNoop.flushPassiveEffects(); // There were multiple updates, but there should only be a // single render expect(ReactNoop.clearYields()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); }); it.skip('flushSync is not allowed', () => { function Counter(props) { const [count, updateCount] = useState('(empty)'); useEffect(() => { ReactNoop.yield(`Schedule update [${props.count}]`); ReactNoop.flushSync(() => { updateCount(props.count); }); }, [props.count]); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: (empty)']); expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); expect(() => { ReactNoop.flushPassiveEffects(); }).toThrow('flushSync was called from inside a lifecycle method'); }); it('unmounts previous effect', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Did create [${props.count}]`); return () => { ReactNoop.yield(`Did destroy [${props.count}]`); }; }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did create [0]']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did destroy [0]', 'Did create [1]']); }); it('unmounts on deletion', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Did create [${props.count}]`); return () => { ReactNoop.yield(`Did destroy [${props.count}]`); }; }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did create [0]']); ReactNoop.render(null); expect(ReactNoop.flush()).toEqual(['Did destroy [0]']); expect(ReactNoop.getChildren()).toEqual([]); }); it('unmounts on deletion after skipped effect', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Did create [${props.count}]`); return () => { ReactNoop.yield(`Did destroy [${props.count}]`); }; }, []); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did create [0]']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(null); ReactNoop.render(null); expect(ReactNoop.flush()).toEqual(['Did destroy [0]']); expect(ReactNoop.getChildren()).toEqual([]); }); it('always fires effects if no dependencies are provided', () => { function effect() { ReactNoop.yield(`Did create`); return () => { ReactNoop.yield(`Did destroy`); }; } function Counter(props) { useEffect(effect); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did create']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did destroy', 'Did create']); ReactNoop.render(null); expect(ReactNoop.flush()).toEqual(['Did destroy']); expect(ReactNoop.getChildren()).toEqual([]); }); it('skips effect if inputs have not changed', () => { function Counter(props) { const text = `${props.label}: ${props.count}`; useEffect(() => { ReactNoop.yield(`Did create [${text}]`); return () => { ReactNoop.yield(`Did destroy [${text}]`); }; }, [props.label, props.count]); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did create [Count: 0]']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.render(); // Count changed expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did destroy [Count: 0]', 'Did create [Count: 1]']); ReactNoop.render(); // Nothing changed, so no effect should have fired expect(ReactNoop.flush()).toEqual(['Count: 1']); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(null); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.render(); // Label changed expect(ReactNoop.flush()).toEqual(['Total: 1']); expect(ReactNoop.getChildren()).toEqual([span('Total: 1')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did destroy [Count: 1]', 'Did create [Total: 1]']); }); it('multiple effects', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Did commit 1 [${props.count}]`); }); useEffect(() => { ReactNoop.yield(`Did commit 2 [${props.count}]`); }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did commit 1 [0]', 'Did commit 2 [0]']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Did commit 1 [1]', 'Did commit 2 [1]']); }); it('unmounts all previous effects before creating any new ones', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Mount A [${props.count}]`); return () => { ReactNoop.yield(`Unmount A [${props.count}]`); }; }); useEffect(() => { ReactNoop.yield(`Mount B [${props.count}]`); return () => { ReactNoop.yield(`Unmount B [${props.count}]`); }; }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Unmount A [0]', 'Unmount B [0]', 'Mount A [1]', 'Mount B [1]']); }); it.skip('handles errors on mount', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Mount A [${props.count}]`); return () => { ReactNoop.yield(`Unmount A [${props.count}]`); }; }); useEffect(() => { ReactNoop.yield('Oops!'); throw new Error('Oops!'); // eslint-disable-next-line no-unreachable ReactNoop.yield(`Mount B [${props.count}]`); return () => { ReactNoop.yield(`Unmount B [${props.count}]`); }; }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); expect(ReactNoop.clearYields()).toEqual([ 'Mount A [0]', 'Oops!', // Clean up effect A. There's no effect B to clean-up, because it // never mounted. 'Unmount A [0]', ]); expect(ReactNoop.getChildren()).toEqual([]); }); it.skip('handles errors on update', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Mount A [${props.count}]`); return () => { ReactNoop.yield(`Unmount A [${props.count}]`); }; }); useEffect(() => { if (props.count === 1) { ReactNoop.yield('Oops!'); throw new Error('Oops!'); } ReactNoop.yield(`Mount B [${props.count}]`); return () => { ReactNoop.yield(`Unmount B [${props.count}]`); }; }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']); // This update will trigger an errror ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); expect(ReactNoop.clearYields()).toEqual([ 'Unmount A [0]', 'Unmount B [0]', 'Mount A [1]', 'Oops!', // Clean up effect A. There's no effect B to clean-up, because it // never mounted. 'Unmount A [1]', ]); expect(ReactNoop.getChildren()).toEqual([]); }); it.skip('handles errors on unmount', () => { function Counter(props) { useEffect(() => { ReactNoop.yield(`Mount A [${props.count}]`); return () => { ReactNoop.yield('Oops!'); throw new Error('Oops!'); // eslint-disable-next-line no-unreachable ReactNoop.yield(`Unmount A [${props.count}]`); }; }); useEffect(() => { ReactNoop.yield(`Mount B [${props.count}]`); return () => { ReactNoop.yield(`Unmount B [${props.count}]`); }; }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Mount A [0]', 'Mount B [0]']); // This update will trigger an errror ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); expect(ReactNoop.clearYields()).toEqual([ 'Oops!', // B unmounts even though an error was thrown in the previous effect 'Unmount B [0]', ]); expect(ReactNoop.getChildren()).toEqual([]); }); it('works with memo', () => { function Counter({ count }) { useLayoutEffect(() => { ReactNoop.yield('Mount: ' + count); return () => ReactNoop.yield('Unmount: ' + count); }); return ; } Counter = memo(Counter); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 0', 'Mount: 0']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Count: 1', 'Unmount: 0', 'Mount: 1']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.render(null); expect(ReactNoop.flush()).toEqual(['Unmount: 1']); expect(ReactNoop.getChildren()).toEqual([]); }); }); describe('useLayoutEffect', () => { it('fires layout effects after the host has been mutated', () => { function getCommittedText() { const children = ReactNoop.getChildren(); if (children === null) { return null; } return children[0].prop; } function Counter(props) { useLayoutEffect(() => { ReactNoop.yield(`Current: ${getCommittedText()}`); }); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual([0, 'Current: 0']); expect(ReactNoop.getChildren()).toEqual([span(0)]); ReactNoop.render(); expect(ReactNoop.flush()).toEqual([1, 'Current: 1']); expect(ReactNoop.getChildren()).toEqual([span(1)]); }); it.skip('force flushes passive effects before firing new layout effects', () => { let committedText = '(empty)'; function Counter(props) { useLayoutEffect(() => { // Normally this would go in a mutation effect, but this test // intentionally omits a mutation effect. committedText = props.count + ''; ReactNoop.yield(`Mount layout [current: ${committedText}]`); return () => { ReactNoop.yield(`Unmount layout [current: ${committedText}]`); }; }); useEffect(() => { ReactNoop.yield(`Mount normal [current: ${committedText}]`); return () => { ReactNoop.yield(`Unmount normal [current: ${committedText}]`); }; }); return null; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Mount layout [current: 0]']); expect(committedText).toEqual('0'); ReactNoop.render(); expect(ReactNoop.flush()).toEqual([ 'Mount normal [current: 0]', 'Unmount layout [current: 0]', 'Mount layout [current: 1]', ]); expect(committedText).toEqual('1'); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Unmount normal [current: 1]', 'Mount normal [current: 1]']); }); }); describe('useCallback', () => { it('memoizes callback by comparing inputs', () => { class IncrementButton extends React.PureComponent { increment = () => { this.props.increment(); }; render() { return ; } } function Counter({ incrementBy }) { const [count, updateCount] = useState(0); const increment = useCallback(() => updateCount(c => c + incrementBy), [incrementBy]); return ( ); } const button = React.createRef(null); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['Increment', 'Count: 0']); expect(ReactNoop.getChildren()).toEqual([span('Increment'), span('Count: 0')]); act(button.current.increment); expect(ReactNoop.flush()).toEqual([ // Button should not re-render, because its props haven't changed // 'Increment', 'Count: 1', ]); expect(ReactNoop.getChildren()).toEqual([span('Increment'), span('Count: 1')]); // Increase the increment amount ReactNoop.render(); expect(ReactNoop.flush()).toEqual([ // Inputs did change this time 'Increment', 'Count: 1', ]); expect(ReactNoop.getChildren()).toEqual([span('Increment'), span('Count: 1')]); // Callback should have updated act(button.current.increment); expect(ReactNoop.flush()).toEqual(['Count: 11']); expect(ReactNoop.getChildren()).toEqual([span('Increment'), span('Count: 11')]); }); }); describe('useMemo', () => { it('memoizes value by comparing to previous inputs', () => { function CapitalizedText(props) { const text = props.text; const capitalizedText = useMemo(() => { ReactNoop.yield(`Capitalize '${text}'`); return text.toUpperCase(); }, [text]); return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(["Capitalize 'hello'", 'HELLO']); expect(ReactNoop.getChildren()).toEqual([span('HELLO')]); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(["Capitalize 'hi'", 'HI']); expect(ReactNoop.getChildren()).toEqual([span('HI')]); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['HI']); expect(ReactNoop.getChildren()).toEqual([span('HI')]); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(["Capitalize 'goodbye'", 'GOODBYE']); expect(ReactNoop.getChildren()).toEqual([span('GOODBYE')]); }); it('always re-computes if no inputs are provided', () => { function LazyCompute(props) { const computed = useMemo(props.compute); return ; } function computeA() { ReactNoop.yield('compute A'); return 'A'; } function computeB() { ReactNoop.yield('compute B'); return 'B'; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['compute A', 'A']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['compute A', 'A']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['compute A', 'A']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['compute B', 'B']); }); it('should not invoke memoized function during re-renders unless inputs change', () => { function LazyCompute(props) { const computed = useMemo(() => props.compute(props.input), [props.input]); const [count, setCount] = useState(0); if (count < 3) { setCount(count + 1); } return ; } function compute(val) { ReactNoop.yield('compute ' + val); return val; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['compute A', 'A']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['A']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['compute B', 'B']); }); }); describe('useRef', () => { it('creates a ref object initialized with the provided value', () => { jest.useFakeTimers(); function useDebouncedCallback(callback, ms, inputs) { const timeoutID = useRef(-1); useEffect(() => { return function unmount() { clearTimeout(timeoutID.current); }; }, []); const debouncedCallback = useCallback( (...args) => { clearTimeout(timeoutID.current); timeoutID.current = setTimeout(callback, ms, ...args); }, [callback, ms], ); return useCallback(debouncedCallback, inputs); } let ping; function App() { ping = useDebouncedCallback( value => { ReactNoop.yield('ping: ' + value); }, 100, [], ); return null; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual([]); ping(1); ping(2); ping(3); expect(ReactNoop.flush()).toEqual([]); jest.advanceTimersByTime(100); expect(ReactNoop.flush()).toEqual(['ping: 3']); ping(4); jest.advanceTimersByTime(20); ping(5); ping(6); jest.advanceTimersByTime(80); expect(ReactNoop.flush()).toEqual([]); jest.advanceTimersByTime(20); expect(ReactNoop.flush()).toEqual(['ping: 6']); }); it('should return the same ref during re-renders', () => { function Counter() { const ref = useRef('val'); const [count, setCount] = useState(0); const [firstRef] = useState(ref); if (firstRef !== ref) { throw new Error('should never change'); } if (count < 3) { setCount(count + 1); } return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['val']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['val']); }); }); describe('useImperativeHandle', () => { it('does not update when deps are the same', () => { const INCREMENT = 'INCREMENT'; function reducer(state, action) { return action === INCREMENT ? state + 1 : state; } function Counter(props, ref) { const [count, dispatch] = useReducer(reducer, 0); useImperativeHandle(ref, () => ({ count, dispatch }), []); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); expect(counter.current.count).toBe(0); act(() => { counter.current.dispatch(INCREMENT); }); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); // Intentionally not updated because of [] deps: expect(counter.current.count).toBe(0); }); // Regression test for https://github.com/facebook/react/issues/14782 it.skip('automatically updates when deps are not specified', () => { const INCREMENT = 'INCREMENT'; function reducer(state, action) { return action === INCREMENT ? state + 1 : state; } function Counter(props, ref) { const [count, dispatch] = useReducer(reducer, 0); useImperativeHandle(ref, () => ({ count, dispatch })); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); expect(counter.current.count).toBe(0); act(() => { counter.current.dispatch(INCREMENT); }); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); expect(counter.current.count).toBe(1); }); it('updates when deps are different', () => { const INCREMENT = 'INCREMENT'; function reducer(state, action) { return action === INCREMENT ? state + 1 : state; } let totalRefUpdates = 0; function Counter(props, ref) { const [count, dispatch] = useReducer(reducer, 0); useImperativeHandle( ref, () => { totalRefUpdates++; return { count, dispatch }; }, [count], ); return ; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); expect(counter.current.count).toBe(0); expect(totalRefUpdates).toBe(1); act(() => { counter.current.dispatch(INCREMENT); }); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); expect(counter.current.count).toBe(1); expect(totalRefUpdates).toBe(2); // Update that doesn't change the ref dependencies ReactNoop.render(); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); expect(counter.current.count).toBe(1); expect(totalRefUpdates).toBe(2); // Should not increase since last time }); }); describe('progressive enhancement (not supported)', () => { it('mount additional state', () => { let updateA; let updateB; // let updateC; function App(props) { const [A, _updateA] = useState(0); const [B, _updateB] = useState(0); updateA = _updateA; updateB = _updateB; let C; if (props.loadC) { useState(0); } else { C = '[not loaded]'; } return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['A: 0, B: 0, C: [not loaded]']); expect(ReactNoop.getChildren()).toEqual([span('A: 0, B: 0, C: [not loaded]')]); act(() => { updateA(2); updateB(3); }); expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: [not loaded]']); expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: [not loaded]')]); ReactNoop.render(); expect(() => { expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 0']); }).toThrow('Rendered more hooks than during the previous render'); // Uncomment if/when we support this again // expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 0')]); // updateC(4); // expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 4']); // expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]); }); it('unmount state', () => { let updateA; let updateB; let updateC; function App(props) { const [A, _updateA] = useState(0); const [B, _updateB] = useState(0); updateA = _updateA; updateB = _updateB; let C; if (props.loadC) { const [_C, _updateC] = useState(0); C = _C; updateC = _updateC; } else { C = '[not loaded]'; } return ; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['A: 0, B: 0, C: 0']); expect(ReactNoop.getChildren()).toEqual([span('A: 0, B: 0, C: 0')]); act(() => { updateA(2); updateB(3); updateC(4); }); expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 4']); expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 4')]); // Skip // ReactNoop.render(); // expect(() => ReactNoop.flush()).toThrow( // 'Rendered fewer hooks than expected. This may be caused by an ' + 'accidental early return statement.', // ); }); it.skip('unmount effects', () => { function App(props) { useEffect(() => { ReactNoop.yield('Mount A'); return () => { ReactNoop.yield('Unmount A'); }; }, []); if (props.showMore) { useEffect(() => { ReactNoop.yield('Mount B'); return () => { ReactNoop.yield('Unmount B'); }; }, []); } return null; } ReactNoop.render(); expect(ReactNoop.flush()).toEqual([]); ReactNoop.flushPassiveEffects(); expect(ReactNoop.clearYields()).toEqual(['Mount A']); ReactNoop.render(); expect(() => { expect(ReactNoop.flush()).toEqual([]); }).toThrow('Rendered more hooks than during the previous render'); // Uncomment if/when we support this again // ReactNoop.flushPassiveEffects(); // expect(ReactNoop.clearYields()).toEqual(['Mount B']); // ReactNoop.render(); // expect(() => ReactNoop.flush()).toThrow( // 'Rendered fewer hooks than expected. This may be caused by an ' + // 'accidental early return statement.', // ); }); }); describe('useContext', () => { it('simple use', () => { const CounterContext = createContext(); const Counter = () => { const count = useContext(CounterContext); return ; }; class Blank extends React.PureComponent { render() { return this.props.children; } } const App = props => { return ( ); }; ReactNoop.render(); expect(ReactNoop.flush()).toEqual([0]); ReactNoop.render(); expect(ReactNoop.flush()).toEqual([1]); }); it('works with shouldComponentUpdate', () => { const CounterContext = createContext(0); const Counter = () => { const count = useContext(CounterContext); return }; class Blank extends React.PureComponent { render() { return ; } } let updateCount; const App = () => { const [count, _updateCount] = useState(0); updateCount = _updateCount; return ( ); }; ReactNoop.render(); expect(ReactNoop.flush()).toEqual([0]); updateCount(1); expect(ReactNoop.flush()).toEqual([1]); }); it('multiple contexts', () => { const CounterContext1 = createContext(); const CounterContext2 = createContext(); const Counter = () => { const count1 = useContext(CounterContext1); const count2 = useContext(CounterContext2); return ( ); }; class Blank extends React.PureComponent { render() { return ; } } const App = props => { return ( ); }; ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['00']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['10']); ReactNoop.render(); expect(ReactNoop.flush()).toEqual(['11']); }); }); }); ================================================ FILE: tests/ReactHooksWithReactDOM.test.js ================================================ import '../src/polyfill'; import React, { forwardRef, useState, useImperativeHandle, useLayoutEffect, useEffect } from 'react'; import { render, cleanup } from 'react-testing-library'; import 'jest-dom/extend-expect'; class Logger { yieldedValues = null; yield(value) { if (this.yieldedValues === null) { this.yieldedValues = [value]; } else { this.yieldedValues.push(value); } } flush() { const values = this.yieldedValues || []; this.yieldedValues = null; return values; } clearYields() { const values = this.yieldedValues; this.yieldedValues = null; return values; } } let logger; describe('hooks', () => { function Text(props) { logger.yield(props.text); return {props.text}; } beforeEach(() => { logger = new Logger(); jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => setTimeout(cb)); }); afterEach(() => { cleanup(); window.requestAnimationFrame.mockRestore(); jest.clearAllTimers(); }); describe('useEffect', () => { it('flushes passive effects even with sibling deletions', () => { jest.useFakeTimers(); function LayoutEffect(props) { useLayoutEffect(() => { logger.yield(`Layout effect`); }); return ; } function PassiveEffect(props) { useEffect(() => { logger.yield(`Passive effect`); }, []); return ; } let passive = ; const { container, rerender } = render([, passive]); expect(logger.flush()).toEqual(['Layout', 'Passive', 'Layout effect']); expect(container).toHaveTextContent('LayoutPassive'); // Destroying the first child shouldn't prevent the passive effect from // being executed rerender([passive]); jest.runAllTimers(); expect(logger.flush()).toEqual(['Passive effect']); expect(container).toHaveTextContent('Passive'); // (No effects are left to flush.) jest.runAllTimers(); expect(logger.clearYields()).toEqual(null); }); it('flushes passive effects even if siblings schedule an update', () => { jest.useFakeTimers(); function PassiveEffect(props) { useEffect(() => { debugger logger.yield('Passive effect'); }); return ; } function LayoutEffect(props) { let [count, setCount] = useState(0); useLayoutEffect(() => { // Scheduling work shouldn't interfere with the queued passive effect if (count === 0) { setCount(1); } logger.yield('Layout effect ' + count); }); return ; } render([, ]); jest.runAllTimers(); expect(logger.flush()).toEqual([ 'Passive', 'Layout', 'Layout effect 0', 'Passive effect', 'Layout', 'Layout effect 1', ]); expect(container).toHaveTextContent('PassiveLayout'); }); }); }); ================================================ FILE: vendors/react-noop-renderer/LICENSE ================================================ MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendors/react-noop-renderer/README.md ================================================ # `react-noop-renderer` This package is the renderer we use for debugging [Fiber](https://github.com/facebook/react/issues/6170). It is not intended to be used directly. ================================================ FILE: vendors/react-noop-renderer/cjs/react-noop-renderer-persistent.development.js ================================================ /** @license React v16.6.1 * react-noop-renderer-persistent.development.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const regeneratorRuntime = require("regenerator-runtime"); if (process.env.NODE_ENV !== "production") { (function() { 'use strict'; var ReactFiberPersistentReconciler = require('react-reconciler/persistent'); var _assign = require('object-assign'); var expect = require('expect'); // The Symbol used to tag the ReactElement-like types. If there is no native Symbol // nor polyfill, then a plain number is used for performance. var hasSymbol = typeof Symbol === 'function' && Symbol.for; var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7; var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca; var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb; function createPortal(children, containerInfo, // TODO: figure out the API for cross-renderer implementation. implementation) { var key = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; return { // This tag allow us to uniquely identify this as a React Portal $$typeof: REACT_PORTAL_TYPE, key: key == null ? null : '' + key, children: children, containerInfo: containerInfo, implementation: implementation }; } /** * Similar to invariant but only logs a warning if the condition is not met. * This can be used to log issues in development environments in critical * paths. Removing the logging code for production environments will keep the * same logic and follow the same code paths. */ var warningWithoutStack = function () {}; { warningWithoutStack = function (condition, format) { for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } if (format === undefined) { throw new Error('`warningWithoutStack(condition, format, ...args)` requires a warning ' + 'message argument'); } if (args.length > 8) { // Check before the condition to catch violations early. throw new Error('warningWithoutStack() currently supports at most 8 arguments.'); } if (condition) { return; } if (typeof console !== 'undefined') { var argsWithFormat = args.map(function (item) { return '' + item; }); argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it // breaks IE9: https://github.com/facebook/react/issues/13610 Function.prototype.apply.call(console.error, console, argsWithFormat); } try { // --- Welcome to debugging React --- // This error was thrown as a convenience so that you can use this stack // to find the callsite that caused this warning to fire. var argIndex = 0; var message = 'Warning: ' + format.replace(/%s/g, function () { return args[argIndex++]; }); throw new Error(message); } catch (x) {} }; } var warningWithoutStack$1 = warningWithoutStack; // for .act's return value var NO_CONTEXT = {}; var UPDATE_SIGNAL = {}; { Object.freeze(NO_CONTEXT); Object.freeze(UPDATE_SIGNAL); } function createReactNoop(reconciler, useMutation) { var _marked = /*#__PURE__*/regeneratorRuntime.mark(flushUnitsOfWork); var scheduledCallback = null; var scheduledCallbackTimeout = -1; var scheduledPassiveCallback = null; var instanceCounter = 0; var hostDiffCounter = 0; var hostUpdateCounter = 0; var hostCloneCounter = 0; function appendChildToContainerOrInstance(parentInstance, child) { var index = parentInstance.children.indexOf(child); if (index !== -1) { parentInstance.children.splice(index, 1); } parentInstance.children.push(child); } function appendChildToContainer(parentInstance, child) { if (typeof parentInstance.rootID !== 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('appendChildToContainer() first argument is not a container.'); } appendChildToContainerOrInstance(parentInstance, child); } function appendChild(parentInstance, child) { if (typeof parentInstance.rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('appendChild() first argument is not an instance.'); } appendChildToContainerOrInstance(parentInstance, child); } function insertInContainerOrInstanceBefore(parentInstance, child, beforeChild) { var index = parentInstance.children.indexOf(child); if (index !== -1) { parentInstance.children.splice(index, 1); } var beforeIndex = parentInstance.children.indexOf(beforeChild); if (beforeIndex === -1) { throw new Error('This child does not exist.'); } parentInstance.children.splice(beforeIndex, 0, child); } function insertInContainerBefore(parentInstance, child, beforeChild) { if (typeof parentInstance.rootID !== 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('insertInContainerBefore() first argument is not a container.'); } insertInContainerOrInstanceBefore(parentInstance, child, beforeChild); } function insertBefore(parentInstance, child, beforeChild) { if (typeof parentInstance.rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('insertBefore() first argument is not an instance.'); } insertInContainerOrInstanceBefore(parentInstance, child, beforeChild); } function removeChildFromContainerOrInstance(parentInstance, child) { var index = parentInstance.children.indexOf(child); if (index === -1) { throw new Error('This child does not exist.'); } parentInstance.children.splice(index, 1); } function removeChildFromContainer(parentInstance, child) { if (typeof parentInstance.rootID !== 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('removeChildFromContainer() first argument is not a container.'); } removeChildFromContainerOrInstance(parentInstance, child); } function removeChild(parentInstance, child) { if (typeof parentInstance.rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('removeChild() first argument is not an instance.'); } removeChildFromContainerOrInstance(parentInstance, child); } function cloneInstance(instance, updatePayload, type, oldProps, newProps, internalInstanceHandle, keepChildren, recyclableInstance) { var clone = { id: instance.id, type: type, children: keepChildren ? instance.children : [], text: shouldSetTextContent(type, newProps) ? newProps.children + '' : null, prop: newProps.prop, hidden: newProps.hidden === true }; Object.defineProperty(clone, 'id', { value: clone.id, enumerable: false }); Object.defineProperty(clone, 'text', { value: clone.text, enumerable: false }); hostCloneCounter++; return clone; } function shouldSetTextContent(type, props) { if (type === 'errorInBeginPhase') { throw new Error('Error in host config.'); } return typeof props.children === 'string' || typeof props.children === 'number'; } var elapsedTimeInMs = 0; var sharedHostConfig = { getRootHostContext: function () { return NO_CONTEXT; }, getChildHostContext: function () { return NO_CONTEXT; }, getPublicInstance: function (instance) { return instance; }, createInstance: function (type, props) { if (type === 'errorInCompletePhase') { throw new Error('Error in host config.'); } var inst = { id: instanceCounter++, type: type, children: [], text: shouldSetTextContent(type, props) ? props.children + '' : null, prop: props.prop, hidden: props.hidden === true }; // Hide from unit tests Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false }); Object.defineProperty(inst, 'text', { value: inst.text, enumerable: false }); return inst; }, appendInitialChild: function (parentInstance, child) { parentInstance.children.push(child); }, finalizeInitialChildren: function (domElement, type, props) { return false; }, prepareUpdate: function (instance, type, oldProps, newProps) { if (type === 'errorInCompletePhase') { throw new Error('Error in host config.'); } if (oldProps === null) { throw new Error('Should have old props'); } if (newProps === null) { throw new Error('Should have new props'); } hostDiffCounter++; return UPDATE_SIGNAL; }, shouldSetTextContent: shouldSetTextContent, shouldDeprioritizeSubtree: function (type, props) { return !!props.hidden; }, createTextInstance: function (text, rootContainerInstance, hostContext, internalInstanceHandle) { var inst = { text: text, id: instanceCounter++, hidden: false }; // Hide from unit tests Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false }); return inst; }, scheduleDeferredCallback: function (callback, options) { if (scheduledCallback) { throw new Error('Scheduling a callback twice is excessive. Instead, keep track of ' + 'whether the callback has already been scheduled.'); } scheduledCallback = callback; if (typeof options === 'object' && options !== null && typeof options.timeout === 'number') { var newTimeout = options.timeout; if (scheduledCallbackTimeout === -1 || scheduledCallbackTimeout > newTimeout) { scheduledCallbackTimeout = elapsedTimeInMs + newTimeout; } } return 0; }, cancelDeferredCallback: function () { if (scheduledCallback === null) { throw new Error('No callback is scheduled.'); } scheduledCallback = null; scheduledCallbackTimeout = -1; }, shouldYield: shouldYield, scheduleTimeout: setTimeout, cancelTimeout: clearTimeout, noTimeout: -1, schedulePassiveEffects: function (callback) { if (scheduledCallback) { throw new Error('Scheduling a callback twice is excessive. Instead, keep track of ' + 'whether the callback has already been scheduled.'); } scheduledPassiveCallback = callback; }, cancelPassiveEffects: function () { if (scheduledPassiveCallback === null) { throw new Error('No passive effects callback is scheduled.'); } scheduledPassiveCallback = null; }, prepareForCommit: function () {}, resetAfterCommit: function () {}, now: function () { return elapsedTimeInMs; }, isPrimaryRenderer: true, supportsHydration: false }; var hostConfig = useMutation ? _assign({}, sharedHostConfig, { supportsMutation: true, supportsPersistence: false, commitMount: function (instance, type, newProps) { // Noop }, commitUpdate: function (instance, updatePayload, type, oldProps, newProps) { if (oldProps === null) { throw new Error('Should have old props'); } hostUpdateCounter++; instance.prop = newProps.prop; instance.hidden = newProps.hidden === true; if (shouldSetTextContent(type, newProps)) { instance.text = newProps.children + ''; } }, commitTextUpdate: function (textInstance, oldText, newText) { hostUpdateCounter++; textInstance.text = newText; }, appendChild: appendChild, appendChildToContainer: appendChildToContainer, insertBefore: insertBefore, insertInContainerBefore: insertInContainerBefore, removeChild: removeChild, removeChildFromContainer: removeChildFromContainer, hideInstance: function (instance) { instance.hidden = true; }, hideTextInstance: function (textInstance) { textInstance.hidden = true; }, unhideInstance: function (instance, props) { if (!props.hidden) { instance.hidden = false; } }, unhideTextInstance: function (textInstance, text) { textInstance.hidden = false; }, resetTextContent: function (instance) { instance.text = null; } }) : _assign({}, sharedHostConfig, { supportsMutation: false, supportsPersistence: true, cloneInstance: cloneInstance, createContainerChildSet: function (container) { return []; }, appendChildToContainerChildSet: function (childSet, child) { childSet.push(child); }, finalizeContainerChildren: function (container, newChildren) {}, replaceContainerChildren: function (container, newChildren) { container.children = newChildren; }, cloneHiddenInstance: function (instance, type, props, internalInstanceHandle) { var clone = cloneInstance(instance, null, type, props, props, internalInstanceHandle, true, null); clone.hidden = true; return clone; }, cloneUnhiddenInstance: function (instance, type, props, internalInstanceHandle) { var clone = cloneInstance(instance, null, type, props, props, internalInstanceHandle, true, null); clone.hidden = props.hidden; return clone; }, createHiddenTextInstance: function (text, rootContainerInstance, hostContext, internalInstanceHandle) { var inst = { text: text, id: instanceCounter++, hidden: true }; // Hide from unit tests Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false }); return inst; } }); var NoopRenderer = reconciler(hostConfig); var rootContainers = new Map(); var roots = new Map(); var DEFAULT_ROOT_ID = ''; var yieldedValues = null; var didYield = void 0; var unitsRemaining = void 0; function shouldYield() { if (scheduledCallbackTimeout === -1 || elapsedTimeInMs > scheduledCallbackTimeout) { return false; } else { if (didYield || yieldedValues !== null) { return true; } if (unitsRemaining-- > 0) { return false; } didYield = true; return true; } } function flushUnitsOfWork(n) { var cb, values; return regeneratorRuntime.wrap(function flushUnitsOfWork$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: unitsRemaining = n + 1; didYield = false; _context.prev = 2; case 3: if (!(!didYield && scheduledCallback !== null)) { _context.next = 14; break; } cb = scheduledCallback; scheduledCallback = null; cb(); if (!(yieldedValues !== null)) { _context.next = 12; break; } values = yieldedValues; yieldedValues = null; _context.next = 12; return values; case 12: _context.next = 3; break; case 14: _context.prev = 14; unitsRemaining = -1; didYield = false; return _context.finish(14); case 18: case 'end': return _context.stop(); } } }, _marked, this, [[2,, 14, 18]]); } function childToJSX(child, text) { if (text !== null) { return text; } if (child === null) { return null; } if (typeof child === 'string') { return child; } if (Array.isArray(child)) { if (child.length === 0) { return null; } if (child.length === 1) { return childToJSX(child[0], null); } // $FlowFixMe var _children = child.map(function (c) { return childToJSX(c, null); }); if (_children.every(function (c) { return typeof c === 'string' || typeof c === 'number'; })) { return _children.join(''); } return _children; } if (Array.isArray(child.children)) { // This is an instance. var instance = child; var _children2 = childToJSX(instance.children, instance.text); var props = { prop: instance.prop }; if (instance.hidden) { props.hidden = true; } if (_children2 !== null) { props.children = _children2; } return { $$typeof: REACT_ELEMENT_TYPE, type: instance.type, key: null, ref: null, props: props, _owner: null, _store: {} }; } // This is a text instance var textInstance = child; if (textInstance.hidden) { return ''; } return textInstance.text; } var ReactNoop = { getChildren: function () { var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; var container = rootContainers.get(rootID); if (container) { return container.children; } else { return null; } }, getOrCreateRootContainer: function () { var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; var isConcurrent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var root = roots.get(rootID); if (!root) { var container = { rootID: rootID, children: [] }; rootContainers.set(rootID, container); root = NoopRenderer.createContainer(container, isConcurrent, false); roots.set(rootID, root); } return root.current.stateNode.containerInfo; }, getChildrenAsJSX: function () { var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; var children = childToJSX(ReactNoop.getChildren(rootID), null); if (children === null) { return null; } if (Array.isArray(children)) { return { $$typeof: REACT_ELEMENT_TYPE, type: REACT_FRAGMENT_TYPE, key: null, ref: null, props: { children: children }, _owner: null, _store: {} }; } return children; }, createPortal: function (children, container) { var key = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; return createPortal(children, container, null, key); }, // Shortcut for testing a single root render: function (element, callback) { ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback); }, renderLegacySyncRoot: function (element, callback) { var rootID = DEFAULT_ROOT_ID; var isConcurrent = false; var container = ReactNoop.getOrCreateRootContainer(rootID, isConcurrent); var root = roots.get(container.rootID); NoopRenderer.updateContainer(element, root, null, callback); }, renderToRootWithID: function (element, rootID, callback) { var isConcurrent = true; var container = ReactNoop.getOrCreateRootContainer(rootID, isConcurrent); var root = roots.get(container.rootID); NoopRenderer.updateContainer(element, root, null, callback); }, unmountRootWithID: function (rootID) { var root = roots.get(rootID); if (root) { NoopRenderer.updateContainer(null, root, null, function () { roots.delete(rootID); rootContainers.delete(rootID); }); } }, findInstance: function (componentOrElement) { if (componentOrElement == null) { return null; } // Unsound duck typing. var component = componentOrElement; if (typeof component.id === 'number') { return component; } { return NoopRenderer.findHostInstanceWithWarning(component, 'findInstance'); } return NoopRenderer.findHostInstance(component); }, flushDeferredPri: function () { var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Infinity; // The legacy version of this function decremented the timeout before // returning the new time. // TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead. var n = timeout / 5 - 1; var values = []; // eslint-disable-next-line no-for-of-loops/no-for-of-loops var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = flushUnitsOfWork(n)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var value = _step.value; values.push.apply(values, value); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return values; }, flush: function () { return ReactNoop.flushUnitsOfWork(Infinity); }, flushAndYield: function () { var unitsOfWork = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Infinity; return flushUnitsOfWork(unitsOfWork); }, flushUnitsOfWork: function (n) { var values = yieldedValues || []; yieldedValues = null; // eslint-disable-next-line no-for-of-loops/no-for-of-loops var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = flushUnitsOfWork(n)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var value = _step2.value; values.push.apply(values, value); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return values; }, flushThrough: function (expected) { var actual = []; if (expected.length !== 0) { // eslint-disable-next-line no-for-of-loops/no-for-of-loops var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = flushUnitsOfWork(Infinity)[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var value = _step3.value; actual.push.apply(actual, value); if (actual.length >= expected.length) { break; } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } expect(actual).toEqual(expected); }, flushNextYield: function () { var actual = null; // eslint-disable-next-line no-for-of-loops/no-for-of-loops var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = flushUnitsOfWork(Infinity)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var value = _step4.value; actual = value; break; } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } return actual !== null ? actual : []; }, flushWithHostCounters: function (fn) { hostDiffCounter = 0; hostUpdateCounter = 0; hostCloneCounter = 0; try { ReactNoop.flush(); return useMutation ? { hostDiffCounter: hostDiffCounter, hostUpdateCounter: hostUpdateCounter } : { hostDiffCounter: hostDiffCounter, hostCloneCounter: hostCloneCounter }; } finally { hostDiffCounter = 0; hostUpdateCounter = 0; hostCloneCounter = 0; } }, expire: function (ms) { ReactNoop.advanceTime(ms); return ReactNoop.flushExpired(); }, advanceTime: function (ms) { elapsedTimeInMs += ms; }, flushExpired: function () { return ReactNoop.flushUnitsOfWork(0); }, yield: function (value) { if (yieldedValues === null) { yieldedValues = [value]; } else { yieldedValues.push(value); } }, clearYields: function () { var values = yieldedValues; yieldedValues = null; return values; }, hasScheduledCallback: function () { return !!scheduledCallback; }, batchedUpdates: NoopRenderer.batchedUpdates, deferredUpdates: NoopRenderer.deferredUpdates, unbatchedUpdates: NoopRenderer.unbatchedUpdates, interactiveUpdates: NoopRenderer.interactiveUpdates, // maybe this should exist only in the test file act: function (callback) { // note: keep these warning messages in sync with // ReactTestRenderer.js and ReactTestUtils.js var result = NoopRenderer.batchedUpdates(callback); { if (result !== undefined) { var addendum = void 0; if (result !== null && typeof result.then === 'function') { addendum = "\n\nIt looks like you wrote ReactNoop.act(async () => ...) or returned a Promise from it's callback. " + 'Putting asynchronous logic inside ReactNoop.act(...) is not supported.\n'; } else { addendum = ' You returned: ' + result; } warningWithoutStack$1(false, 'The callback passed to ReactNoop.act(...) function must not return anything.%s', addendum); } } ReactNoop.flushPassiveEffects(); // we want the user to not expect a return, // but we want to warn if they use it like they can await on it. return { then: function () { { warningWithoutStack$1(false, 'Do not await the result of calling ReactNoop.act(...), it is not a Promise.'); } } }; }, flushSync: function (fn) { yieldedValues = []; NoopRenderer.flushSync(fn); return yieldedValues; }, flushPassiveEffects: function () { // Trick to flush passive effects without exposing an internal API: // Create a throwaway root and schedule a dummy update on it. var rootID = 'bloopandthenmoreletterstoavoidaconflict'; var container = { rootID: rootID, children: [] }; rootContainers.set(rootID, container); var root = NoopRenderer.createContainer(container, true, false); NoopRenderer.updateContainer(null, root, null, null); }, // Logs the current state of the tree. dumpTree: function () { var _console; var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; var root = roots.get(rootID); var rootContainer = rootContainers.get(rootID); if (!root || !rootContainer) { console.log('Nothing rendered yet.'); return; } var bufferedLog = []; function log() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } bufferedLog.push.apply(bufferedLog, args.concat(['\n'])); } function logHostInstances(children, depth) { for (var i = 0; i < children.length; i++) { var child = children[i]; var indent = ' '.repeat(depth); if (typeof child.text === 'string') { log(indent + '- ' + child.text); } else { // $FlowFixMe - The child should've been refined now. log(indent + '- ' + child.type + '#' + child.id); // $FlowFixMe - The child should've been refined now. logHostInstances(child.children, depth + 1); } } } function logContainer(container, depth) { log(' '.repeat(depth) + '- [root#' + container.rootID + ']'); logHostInstances(container.children, depth + 1); } function logUpdateQueue(updateQueue, depth) { log(' '.repeat(depth + 1) + 'QUEUED UPDATES'); var firstUpdate = updateQueue.firstUpdate; if (!firstUpdate) { return; } log(' '.repeat(depth + 1) + '~', '[' + firstUpdate.expirationTime + ']'); while (firstUpdate.next) { log(' '.repeat(depth + 1) + '~', '[' + firstUpdate.expirationTime + ']'); } } function logFiber(fiber, depth) { log(' '.repeat(depth) + '- ' + ( // need to explicitly coerce Symbol to a string fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'), '[' + fiber.childExpirationTime + (fiber.pendingProps ? '*' : '') + ']'); if (fiber.updateQueue) { logUpdateQueue(fiber.updateQueue, depth); } // const childInProgress = fiber.progressedChild; // if (childInProgress && childInProgress !== fiber.child) { // log( // ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority, // ); // logFiber(childInProgress, depth + 1); // if (fiber.child) { // log(' '.repeat(depth + 1) + 'CURRENT'); // } // } else if (fiber.child && fiber.updateQueue) { // log(' '.repeat(depth + 1) + 'CHILDREN'); // } if (fiber.child) { logFiber(fiber.child, depth + 1); } if (fiber.sibling) { logFiber(fiber.sibling, depth); } } log('HOST INSTANCES:'); logContainer(rootContainer, 0); log('FIBERS:'); logFiber(root.current, 0); (_console = console).log.apply(_console, bufferedLog); }, flushWithoutCommitting: function (expectedFlush) { var rootID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_ROOT_ID; var root = roots.get(rootID); var expiration = NoopRenderer.computeUniqueAsyncExpiration(); var batch = { _defer: true, _expirationTime: expiration, _onComplete: function () { root.firstBatch = null; }, _next: null }; root.firstBatch = batch; var actual = ReactNoop.flush(); expect(actual).toEqual(expectedFlush); return function (expectedCommit) { batch._defer = false; NoopRenderer.flushRoot(root, expiration); expect(yieldedValues).toEqual(expectedCommit); }; }, getRoot: function () { var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; return roots.get(rootID); } }; return ReactNoop; } /** * This is a renderer of React that doesn't have a render target output. * It is useful to demonstrate the internals of the reconciler in isolation * and for testing semantics of reconciliation separate from the host * environment. */ var ReactNoopPersistent = createReactNoop(ReactFiberPersistentReconciler, // reconciler false // useMutation ); var ReactNoopPersistent$2 = Object.freeze({ default: ReactNoopPersistent }); var ReactNoopPersistent$3 = ( ReactNoopPersistent$2 && ReactNoopPersistent ) || ReactNoopPersistent$2; // TODO: decide on the top-level export form. // This is hacky but makes it work with both Rollup and Jest. var persistent = ReactNoopPersistent$3.default || ReactNoopPersistent$3; module.exports = persistent; })(); } ================================================ FILE: vendors/react-noop-renderer/cjs/react-noop-renderer-server.development.js ================================================ /** @license React v16.6.1 * react-noop-renderer-server.development.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; if (process.env.NODE_ENV !== "production") { (function() { 'use strict'; var ReactFizzStreamer = require('react-stream'); /** * This is a renderer of React that doesn't have a render target output. * It is useful to demonstrate the internals of the reconciler in isolation * and for testing semantics of reconciliation separate from the host * environment. */ var ReactNoopServer = ReactFizzStreamer({ scheduleWork: function (callback) { callback(); }, beginWriting: function (destination) {}, writeChunk: function (destination, buffer) { destination.push(JSON.parse(Buffer.from(buffer).toString('utf8'))); }, completeWriting: function (destination) {}, close: function (destination) {}, flushBuffered: function (destination) {}, convertStringToBuffer: function (content) { return Buffer.from(content, 'utf8'); }, formatChunk: function (type, props) { return Buffer.from(JSON.stringify({ type: type, props: props }), 'utf8'); } }); function render(children) { var destination = []; var request = ReactNoopServer.createRequest(children, destination); ReactNoopServer.startWork(request); return destination; } var ReactNoopServer$1 = { render: render }; var ReactNoopServer$2 = Object.freeze({ default: ReactNoopServer$1 }); var ReactNoopServer$3 = ( ReactNoopServer$2 && ReactNoopServer$1 ) || ReactNoopServer$2; // TODO: decide on the top-level export form. // This is hacky but makes it work with both Rollup and Jest. var server = ReactNoopServer$3.default || ReactNoopServer$3; module.exports = server; })(); } ================================================ FILE: vendors/react-noop-renderer/cjs/react-noop-renderer.development.js ================================================ /** @license React v16.6.1 * react-noop-renderer.development.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; const regeneratorRuntime = require("regenerator-runtime"); if (process.env.NODE_ENV !== "production") { (function() { 'use strict'; var ReactFiberReconciler = require('react-reconciler'); var _assign = require('object-assign'); var expect = require('expect'); // The Symbol used to tag the ReactElement-like types. If there is no native Symbol // nor polyfill, then a plain number is used for performance. var hasSymbol = typeof Symbol === 'function' && Symbol.for; var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7; var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca; var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb; function createPortal(children, containerInfo, // TODO: figure out the API for cross-renderer implementation. implementation) { var key = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; return { // This tag allow us to uniquely identify this as a React Portal $$typeof: REACT_PORTAL_TYPE, key: key == null ? null : '' + key, children: children, containerInfo: containerInfo, implementation: implementation }; } /** * Similar to invariant but only logs a warning if the condition is not met. * This can be used to log issues in development environments in critical * paths. Removing the logging code for production environments will keep the * same logic and follow the same code paths. */ var warningWithoutStack = function () {}; { warningWithoutStack = function (condition, format) { for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } if (format === undefined) { throw new Error('`warningWithoutStack(condition, format, ...args)` requires a warning ' + 'message argument'); } if (args.length > 8) { // Check before the condition to catch violations early. throw new Error('warningWithoutStack() currently supports at most 8 arguments.'); } if (condition) { return; } if (typeof console !== 'undefined') { var argsWithFormat = args.map(function (item) { return '' + item; }); argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it // breaks IE9: https://github.com/facebook/react/issues/13610 Function.prototype.apply.call(console.error, console, argsWithFormat); } try { // --- Welcome to debugging React --- // This error was thrown as a convenience so that you can use this stack // to find the callsite that caused this warning to fire. var argIndex = 0; var message = 'Warning: ' + format.replace(/%s/g, function () { return args[argIndex++]; }); throw new Error(message); } catch (x) {} }; } var warningWithoutStack$1 = warningWithoutStack; // for .act's return value var NO_CONTEXT = {}; var UPDATE_SIGNAL = {}; { Object.freeze(NO_CONTEXT); Object.freeze(UPDATE_SIGNAL); } function createReactNoop(reconciler, useMutation) { var _marked = /*#__PURE__*/regeneratorRuntime.mark(flushUnitsOfWork); var scheduledCallback = null; var scheduledCallbackTimeout = -1; var scheduledPassiveCallback = null; var instanceCounter = 0; var hostDiffCounter = 0; var hostUpdateCounter = 0; var hostCloneCounter = 0; function appendChildToContainerOrInstance(parentInstance, child) { var index = parentInstance.children.indexOf(child); if (index !== -1) { parentInstance.children.splice(index, 1); } parentInstance.children.push(child); } function appendChildToContainer(parentInstance, child) { if (typeof parentInstance.rootID !== 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('appendChildToContainer() first argument is not a container.'); } appendChildToContainerOrInstance(parentInstance, child); } function appendChild(parentInstance, child) { if (typeof parentInstance.rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('appendChild() first argument is not an instance.'); } appendChildToContainerOrInstance(parentInstance, child); } function insertInContainerOrInstanceBefore(parentInstance, child, beforeChild) { var index = parentInstance.children.indexOf(child); if (index !== -1) { parentInstance.children.splice(index, 1); } var beforeIndex = parentInstance.children.indexOf(beforeChild); if (beforeIndex === -1) { throw new Error('This child does not exist.'); } parentInstance.children.splice(beforeIndex, 0, child); } function insertInContainerBefore(parentInstance, child, beforeChild) { if (typeof parentInstance.rootID !== 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('insertInContainerBefore() first argument is not a container.'); } insertInContainerOrInstanceBefore(parentInstance, child, beforeChild); } function insertBefore(parentInstance, child, beforeChild) { if (typeof parentInstance.rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('insertBefore() first argument is not an instance.'); } insertInContainerOrInstanceBefore(parentInstance, child, beforeChild); } function removeChildFromContainerOrInstance(parentInstance, child) { var index = parentInstance.children.indexOf(child); if (index === -1) { throw new Error('This child does not exist.'); } parentInstance.children.splice(index, 1); } function removeChildFromContainer(parentInstance, child) { if (typeof parentInstance.rootID !== 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('removeChildFromContainer() first argument is not a container.'); } removeChildFromContainerOrInstance(parentInstance, child); } function removeChild(parentInstance, child) { if (typeof parentInstance.rootID === 'string') { // Some calls to this aren't typesafe. // This helps surface mistakes in tests. throw new Error('removeChild() first argument is not an instance.'); } removeChildFromContainerOrInstance(parentInstance, child); } function cloneInstance(instance, updatePayload, type, oldProps, newProps, internalInstanceHandle, keepChildren, recyclableInstance) { var clone = { id: instance.id, type: type, children: keepChildren ? instance.children : [], text: shouldSetTextContent(type, newProps) ? newProps.children + '' : null, prop: newProps.prop, hidden: newProps.hidden === true }; Object.defineProperty(clone, 'id', { value: clone.id, enumerable: false }); Object.defineProperty(clone, 'text', { value: clone.text, enumerable: false }); hostCloneCounter++; return clone; } function shouldSetTextContent(type, props) { if (type === 'errorInBeginPhase') { throw new Error('Error in host config.'); } return typeof props.children === 'string' || typeof props.children === 'number'; } var elapsedTimeInMs = 0; var sharedHostConfig = { getRootHostContext: function () { return NO_CONTEXT; }, getChildHostContext: function () { return NO_CONTEXT; }, getPublicInstance: function (instance) { return instance; }, createInstance: function (type, props) { if (type === 'errorInCompletePhase') { throw new Error('Error in host config.'); } var inst = { id: instanceCounter++, type: type, children: [], text: shouldSetTextContent(type, props) ? props.children + '' : null, prop: props.prop, hidden: props.hidden === true }; // Hide from unit tests Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false }); Object.defineProperty(inst, 'text', { value: inst.text, enumerable: false }); return inst; }, appendInitialChild: function (parentInstance, child) { parentInstance.children.push(child); }, finalizeInitialChildren: function (domElement, type, props) { return false; }, prepareUpdate: function (instance, type, oldProps, newProps) { if (type === 'errorInCompletePhase') { throw new Error('Error in host config.'); } if (oldProps === null) { throw new Error('Should have old props'); } if (newProps === null) { throw new Error('Should have new props'); } hostDiffCounter++; return UPDATE_SIGNAL; }, shouldSetTextContent: shouldSetTextContent, shouldDeprioritizeSubtree: function (type, props) { return !!props.hidden; }, createTextInstance: function (text, rootContainerInstance, hostContext, internalInstanceHandle) { var inst = { text: text, id: instanceCounter++, hidden: false }; // Hide from unit tests Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false }); return inst; }, scheduleDeferredCallback: function (callback, options) { if (scheduledCallback) { throw new Error('Scheduling a callback twice is excessive. Instead, keep track of ' + 'whether the callback has already been scheduled.'); } scheduledCallback = callback; if (typeof options === 'object' && options !== null && typeof options.timeout === 'number') { var newTimeout = options.timeout; if (scheduledCallbackTimeout === -1 || scheduledCallbackTimeout > newTimeout) { scheduledCallbackTimeout = elapsedTimeInMs + newTimeout; } } return 0; }, cancelDeferredCallback: function () { if (scheduledCallback === null) { throw new Error('No callback is scheduled.'); } scheduledCallback = null; scheduledCallbackTimeout = -1; }, shouldYield: shouldYield, scheduleTimeout: setTimeout, cancelTimeout: clearTimeout, noTimeout: -1, schedulePassiveEffects: function (callback) { if (scheduledCallback) { throw new Error('Scheduling a callback twice is excessive. Instead, keep track of ' + 'whether the callback has already been scheduled.'); } scheduledPassiveCallback = callback; }, cancelPassiveEffects: function () { if (scheduledPassiveCallback === null) { throw new Error('No passive effects callback is scheduled.'); } scheduledPassiveCallback = null; }, prepareForCommit: function () {}, resetAfterCommit: function () {}, now: function () { return elapsedTimeInMs; }, isPrimaryRenderer: true, supportsHydration: false }; var hostConfig = useMutation ? _assign({}, sharedHostConfig, { supportsMutation: true, supportsPersistence: false, commitMount: function (instance, type, newProps) { // Noop }, commitUpdate: function (instance, updatePayload, type, oldProps, newProps) { if (oldProps === null) { throw new Error('Should have old props'); } hostUpdateCounter++; instance.prop = newProps.prop; instance.hidden = newProps.hidden === true; if (shouldSetTextContent(type, newProps)) { instance.text = newProps.children + ''; } }, commitTextUpdate: function (textInstance, oldText, newText) { hostUpdateCounter++; textInstance.text = newText; }, appendChild: appendChild, appendChildToContainer: appendChildToContainer, insertBefore: insertBefore, insertInContainerBefore: insertInContainerBefore, removeChild: removeChild, removeChildFromContainer: removeChildFromContainer, hideInstance: function (instance) { instance.hidden = true; }, hideTextInstance: function (textInstance) { textInstance.hidden = true; }, unhideInstance: function (instance, props) { if (!props.hidden) { instance.hidden = false; } }, unhideTextInstance: function (textInstance, text) { textInstance.hidden = false; }, resetTextContent: function (instance) { instance.text = null; } }) : _assign({}, sharedHostConfig, { supportsMutation: false, supportsPersistence: true, cloneInstance: cloneInstance, createContainerChildSet: function (container) { return []; }, appendChildToContainerChildSet: function (childSet, child) { childSet.push(child); }, finalizeContainerChildren: function (container, newChildren) {}, replaceContainerChildren: function (container, newChildren) { container.children = newChildren; }, cloneHiddenInstance: function (instance, type, props, internalInstanceHandle) { var clone = cloneInstance(instance, null, type, props, props, internalInstanceHandle, true, null); clone.hidden = true; return clone; }, cloneUnhiddenInstance: function (instance, type, props, internalInstanceHandle) { var clone = cloneInstance(instance, null, type, props, props, internalInstanceHandle, true, null); clone.hidden = props.hidden; return clone; }, createHiddenTextInstance: function (text, rootContainerInstance, hostContext, internalInstanceHandle) { var inst = { text: text, id: instanceCounter++, hidden: true }; // Hide from unit tests Object.defineProperty(inst, 'id', { value: inst.id, enumerable: false }); return inst; } }); var NoopRenderer = reconciler(hostConfig); var rootContainers = new Map(); var roots = new Map(); var DEFAULT_ROOT_ID = ''; var yieldedValues = null; var didYield = void 0; var unitsRemaining = void 0; function shouldYield() { if (scheduledCallbackTimeout === -1 || elapsedTimeInMs > scheduledCallbackTimeout) { return false; } else { if (didYield || yieldedValues !== null) { return true; } if (unitsRemaining-- > 0) { return false; } didYield = true; return true; } } function flushUnitsOfWork(n) { var cb, values; return regeneratorRuntime.wrap(function flushUnitsOfWork$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: unitsRemaining = n + 1; didYield = false; _context.prev = 2; case 3: if (!(!didYield && scheduledCallback !== null)) { _context.next = 14; break; } cb = scheduledCallback; scheduledCallback = null; cb(); if (!(yieldedValues !== null)) { _context.next = 12; break; } values = yieldedValues; yieldedValues = null; _context.next = 12; return values; case 12: _context.next = 3; break; case 14: _context.prev = 14; unitsRemaining = -1; didYield = false; return _context.finish(14); case 18: case 'end': return _context.stop(); } } }, _marked, this, [[2,, 14, 18]]); } function childToJSX(child, text) { if (text !== null) { return text; } if (child === null) { return null; } if (typeof child === 'string') { return child; } if (Array.isArray(child)) { if (child.length === 0) { return null; } if (child.length === 1) { return childToJSX(child[0], null); } // $FlowFixMe var _children = child.map(function (c) { return childToJSX(c, null); }); if (_children.every(function (c) { return typeof c === 'string' || typeof c === 'number'; })) { return _children.join(''); } return _children; } if (Array.isArray(child.children)) { // This is an instance. var instance = child; var _children2 = childToJSX(instance.children, instance.text); var props = { prop: instance.prop }; if (instance.hidden) { props.hidden = true; } if (_children2 !== null) { props.children = _children2; } return { $$typeof: REACT_ELEMENT_TYPE, type: instance.type, key: null, ref: null, props: props, _owner: null, _store: {} }; } // This is a text instance var textInstance = child; if (textInstance.hidden) { return ''; } return textInstance.text; } var ReactNoop = { getChildren: function () { var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; var container = rootContainers.get(rootID); if (container) { return container.children; } else { return null; } }, getOrCreateRootContainer: function () { var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; var isConcurrent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var root = roots.get(rootID); if (!root) { var container = { rootID: rootID, children: [] }; rootContainers.set(rootID, container); root = NoopRenderer.createContainer(container, isConcurrent, false); roots.set(rootID, root); } return root.current.stateNode.containerInfo; }, getChildrenAsJSX: function () { var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; var children = childToJSX(ReactNoop.getChildren(rootID), null); if (children === null) { return null; } if (Array.isArray(children)) { return { $$typeof: REACT_ELEMENT_TYPE, type: REACT_FRAGMENT_TYPE, key: null, ref: null, props: { children: children }, _owner: null, _store: {} }; } return children; }, createPortal: function (children, container) { var key = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; return createPortal(children, container, null, key); }, // Shortcut for testing a single root render: function (element, callback) { ReactNoop.renderToRootWithID(element, DEFAULT_ROOT_ID, callback); }, renderLegacySyncRoot: function (element, callback) { var rootID = DEFAULT_ROOT_ID; var isConcurrent = false; var container = ReactNoop.getOrCreateRootContainer(rootID, isConcurrent); var root = roots.get(container.rootID); NoopRenderer.updateContainer(element, root, null, callback); }, renderToRootWithID: function (element, rootID, callback) { var isConcurrent = true; var container = ReactNoop.getOrCreateRootContainer(rootID, isConcurrent); var root = roots.get(container.rootID); NoopRenderer.updateContainer(element, root, null, callback); }, unmountRootWithID: function (rootID) { var root = roots.get(rootID); if (root) { NoopRenderer.updateContainer(null, root, null, function () { roots.delete(rootID); rootContainers.delete(rootID); }); } }, findInstance: function (componentOrElement) { if (componentOrElement == null) { return null; } // Unsound duck typing. var component = componentOrElement; if (typeof component.id === 'number') { return component; } { return NoopRenderer.findHostInstanceWithWarning(component, 'findInstance'); } return NoopRenderer.findHostInstance(component); }, flushDeferredPri: function () { var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Infinity; // The legacy version of this function decremented the timeout before // returning the new time. // TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead. var n = timeout / 5 - 1; var values = []; // eslint-disable-next-line no-for-of-loops/no-for-of-loops var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = flushUnitsOfWork(n)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var value = _step.value; values.push.apply(values, value); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return values; }, flush: function () { return ReactNoop.flushUnitsOfWork(Infinity); }, flushAndYield: function () { var unitsOfWork = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Infinity; return flushUnitsOfWork(unitsOfWork); }, flushUnitsOfWork: function (n) { var values = yieldedValues || []; yieldedValues = null; // eslint-disable-next-line no-for-of-loops/no-for-of-loops var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = flushUnitsOfWork(n)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var value = _step2.value; values.push.apply(values, value); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return values; }, flushThrough: function (expected) { var actual = []; if (expected.length !== 0) { // eslint-disable-next-line no-for-of-loops/no-for-of-loops var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = flushUnitsOfWork(Infinity)[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var value = _step3.value; actual.push.apply(actual, value); if (actual.length >= expected.length) { break; } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } expect(actual).toEqual(expected); }, flushNextYield: function () { var actual = null; // eslint-disable-next-line no-for-of-loops/no-for-of-loops var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = flushUnitsOfWork(Infinity)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var value = _step4.value; actual = value; break; } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } return actual !== null ? actual : []; }, flushWithHostCounters: function (fn) { hostDiffCounter = 0; hostUpdateCounter = 0; hostCloneCounter = 0; try { ReactNoop.flush(); return useMutation ? { hostDiffCounter: hostDiffCounter, hostUpdateCounter: hostUpdateCounter } : { hostDiffCounter: hostDiffCounter, hostCloneCounter: hostCloneCounter }; } finally { hostDiffCounter = 0; hostUpdateCounter = 0; hostCloneCounter = 0; } }, expire: function (ms) { ReactNoop.advanceTime(ms); return ReactNoop.flushExpired(); }, advanceTime: function (ms) { elapsedTimeInMs += ms; }, flushExpired: function () { return ReactNoop.flushUnitsOfWork(0); }, yield: function (value) { if (yieldedValues === null) { yieldedValues = [value]; } else { yieldedValues.push(value); } }, clearYields: function () { var values = yieldedValues; yieldedValues = null; return values; }, hasScheduledCallback: function () { return !!scheduledCallback; }, batchedUpdates: NoopRenderer.batchedUpdates, deferredUpdates: NoopRenderer.deferredUpdates, unbatchedUpdates: NoopRenderer.unbatchedUpdates, interactiveUpdates: NoopRenderer.interactiveUpdates, // maybe this should exist only in the test file act: function (callback) { // note: keep these warning messages in sync with // ReactTestRenderer.js and ReactTestUtils.js var result = NoopRenderer.batchedUpdates(callback); { if (result !== undefined) { var addendum = void 0; if (result !== null && typeof result.then === 'function') { addendum = "\n\nIt looks like you wrote ReactNoop.act(async () => ...) or returned a Promise from it's callback. " + 'Putting asynchronous logic inside ReactNoop.act(...) is not supported.\n'; } else { addendum = ' You returned: ' + result; } warningWithoutStack$1(false, 'The callback passed to ReactNoop.act(...) function must not return anything.%s', addendum); } } ReactNoop.flushPassiveEffects(); // we want the user to not expect a return, // but we want to warn if they use it like they can await on it. return { then: function () { { warningWithoutStack$1(false, 'Do not await the result of calling ReactNoop.act(...), it is not a Promise.'); } } }; }, flushSync: function (fn) { yieldedValues = []; NoopRenderer.flushSync(fn); return yieldedValues; }, flushPassiveEffects: function () { // Trick to flush passive effects without exposing an internal API: // Create a throwaway root and schedule a dummy update on it. var rootID = 'bloopandthenmoreletterstoavoidaconflict'; var container = { rootID: rootID, children: [] }; rootContainers.set(rootID, container); var root = NoopRenderer.createContainer(container, true, false); NoopRenderer.updateContainer(null, root, null, null); }, // Logs the current state of the tree. dumpTree: function () { var _console; var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; var root = roots.get(rootID); var rootContainer = rootContainers.get(rootID); if (!root || !rootContainer) { console.log('Nothing rendered yet.'); return; } var bufferedLog = []; function log() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } bufferedLog.push.apply(bufferedLog, args.concat(['\n'])); } function logHostInstances(children, depth) { for (var i = 0; i < children.length; i++) { var child = children[i]; var indent = ' '.repeat(depth); if (typeof child.text === 'string') { log(indent + '- ' + child.text); } else { // $FlowFixMe - The child should've been refined now. log(indent + '- ' + child.type + '#' + child.id); // $FlowFixMe - The child should've been refined now. logHostInstances(child.children, depth + 1); } } } function logContainer(container, depth) { log(' '.repeat(depth) + '- [root#' + container.rootID + ']'); logHostInstances(container.children, depth + 1); } function logUpdateQueue(updateQueue, depth) { log(' '.repeat(depth + 1) + 'QUEUED UPDATES'); var firstUpdate = updateQueue.firstUpdate; if (!firstUpdate) { return; } log(' '.repeat(depth + 1) + '~', '[' + firstUpdate.expirationTime + ']'); while (firstUpdate.next) { log(' '.repeat(depth + 1) + '~', '[' + firstUpdate.expirationTime + ']'); } } function logFiber(fiber, depth) { log(' '.repeat(depth) + '- ' + ( // need to explicitly coerce Symbol to a string fiber.type ? fiber.type.name || fiber.type.toString() : '[root]'), '[' + fiber.childExpirationTime + (fiber.pendingProps ? '*' : '') + ']'); if (fiber.updateQueue) { logUpdateQueue(fiber.updateQueue, depth); } // const childInProgress = fiber.progressedChild; // if (childInProgress && childInProgress !== fiber.child) { // log( // ' '.repeat(depth + 1) + 'IN PROGRESS: ' + fiber.pendingWorkPriority, // ); // logFiber(childInProgress, depth + 1); // if (fiber.child) { // log(' '.repeat(depth + 1) + 'CURRENT'); // } // } else if (fiber.child && fiber.updateQueue) { // log(' '.repeat(depth + 1) + 'CHILDREN'); // } if (fiber.child) { logFiber(fiber.child, depth + 1); } if (fiber.sibling) { logFiber(fiber.sibling, depth); } } log('HOST INSTANCES:'); logContainer(rootContainer, 0); log('FIBERS:'); logFiber(root.current, 0); (_console = console).log.apply(_console, bufferedLog); }, flushWithoutCommitting: function (expectedFlush) { var rootID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_ROOT_ID; var root = roots.get(rootID); var expiration = NoopRenderer.computeUniqueAsyncExpiration(); var batch = { _defer: true, _expirationTime: expiration, _onComplete: function () { root.firstBatch = null; }, _next: null }; root.firstBatch = batch; var actual = ReactNoop.flush(); expect(actual).toEqual(expectedFlush); return function (expectedCommit) { batch._defer = false; NoopRenderer.flushRoot(root, expiration); expect(yieldedValues).toEqual(expectedCommit); }; }, getRoot: function () { var rootID = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_ROOT_ID; return roots.get(rootID); } }; return ReactNoop; } /** * This is a renderer of React that doesn't have a render target output. * It is useful to demonstrate the internals of the reconciler in isolation * and for testing semantics of reconciliation separate from the host * environment. */ var ReactNoop = createReactNoop(ReactFiberReconciler, // reconciler true // useMutation ); var ReactNoop$2 = Object.freeze({ default: ReactNoop }); var ReactNoop$3 = ( ReactNoop$2 && ReactNoop ) || ReactNoop$2; // TODO: decide on the top-level export form. // This is hacky but makes it work with both Rollup and Jest. var reactNoopRenderer = ReactNoop$3.default || ReactNoop$3; module.exports = reactNoopRenderer; })(); } ================================================ FILE: vendors/react-noop-renderer/index.js ================================================ 'use strict'; if (process.env.NODE_ENV === 'production') { module.exports = require('./cjs/react-noop-renderer.production.min.js'); } else { module.exports = require('./cjs/react-noop-renderer.development.js'); } ================================================ FILE: vendors/react-noop-renderer/package.json ================================================ { "name": "react-noop-renderer", "version": "16.0.0", "private": true, "description": "React package for testing the Fiber reconciler.", "main": "index.js", "repository": { "type" : "git", "url" : "https://github.com/facebook/react.git", "directory": "packages/react-noop-renderer" }, "license": "MIT", "dependencies": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", "regenerator-runtime": "^0.11.0", "react-reconciler": "*", "react-stream": "*" }, "peerDependencies": { "react": "^16.0.0" }, "files": [ "LICENSE", "README.md", "build-info.json", "index.js", "persistent.js", "server.js", "cjs/" ] } ================================================ FILE: vendors/react-noop-renderer/persistent.js ================================================ 'use strict'; if (process.env.NODE_ENV === 'production') { module.exports = require('./cjs/react-noop-renderer-persistent.production.min.js'); } else { module.exports = require('./cjs/react-noop-renderer-persistent.development.js'); } ================================================ FILE: vendors/react-noop-renderer/server.js ================================================ 'use strict'; if (process.env.NODE_ENV === 'production') { module.exports = require('./cjs/react-noop-renderer-server.production.min.js'); } else { module.exports = require('./cjs/react-noop-renderer-server.development.js'); }