diff --git a/.eslintignore b/.eslintignore index 4b9cfb7dd7..564cfdfb14 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,5 @@ node_modules -flow-typed shells/browser/chrome/build shells/browser/firefox/build shells/browser/shared/build diff --git a/.gitignore b/.gitignore index ef86dd7006..e2524be341 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,4 @@ yarn-error.log yarn-error.log .vscode .idea -.watchmanconfig -key.pem - +.watchmanconfig \ No newline at end of file diff --git a/fixtures/regression/14.9.html b/fixtures/regression/14.9.html new file mode 100644 index 0000000000..efa28f49cd --- /dev/null +++ b/fixtures/regression/14.9.html @@ -0,0 +1,38 @@ + + + + + React 14.9 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/14.9.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/15.0.html b/fixtures/regression/15.0.html new file mode 100644 index 0000000000..464ea1f548 --- /dev/null +++ b/fixtures/regression/15.0.html @@ -0,0 +1,38 @@ + + + + + React 15.0 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/15.0.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/15.1.html b/fixtures/regression/15.1.html new file mode 100644 index 0000000000..7e02e81bf9 --- /dev/null +++ b/fixtures/regression/15.1.html @@ -0,0 +1,38 @@ + + + + + React 15.1 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/15.1.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/15.2.html b/fixtures/regression/15.2.html new file mode 100644 index 0000000000..ff1e6b8746 --- /dev/null +++ b/fixtures/regression/15.2.html @@ -0,0 +1,38 @@ + + + + + React 15.2 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/15.2.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/15.3.html b/fixtures/regression/15.3.html new file mode 100644 index 0000000000..8d1795962e --- /dev/null +++ b/fixtures/regression/15.3.html @@ -0,0 +1,38 @@ + + + + + React 15.3 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/15.3.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/15.4.html b/fixtures/regression/15.4.html new file mode 100644 index 0000000000..ee1bfc0723 --- /dev/null +++ b/fixtures/regression/15.4.html @@ -0,0 +1,38 @@ + + + + + React 15.4 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/15.4.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/15.5.html b/fixtures/regression/15.5.html new file mode 100644 index 0000000000..4097bdf708 --- /dev/null +++ b/fixtures/regression/15.5.html @@ -0,0 +1,38 @@ + + + + + React 15.5 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/15.5.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/15.6.html b/fixtures/regression/15.6.html new file mode 100644 index 0000000000..ab10ba8716 --- /dev/null +++ b/fixtures/regression/15.6.html @@ -0,0 +1,38 @@ + + + + + React 15.6 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/15.6.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/16.0.html b/fixtures/regression/16.0.html new file mode 100644 index 0000000000..7c55d75114 --- /dev/null +++ b/fixtures/regression/16.0.html @@ -0,0 +1,38 @@ + + + + + React 16.0 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/16.0.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/16.1.html b/fixtures/regression/16.1.html new file mode 100644 index 0000000000..d93addf46e --- /dev/null +++ b/fixtures/regression/16.1.html @@ -0,0 +1,38 @@ + + + + + React 16.1 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/16.1.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/16.2.html b/fixtures/regression/16.2.html new file mode 100644 index 0000000000..10d8e150ed --- /dev/null +++ b/fixtures/regression/16.2.html @@ -0,0 +1,38 @@ + + + + + React 16.2 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/16.2.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/16.3.html b/fixtures/regression/16.3.html new file mode 100644 index 0000000000..2e42e2813a --- /dev/null +++ b/fixtures/regression/16.3.html @@ -0,0 +1,38 @@ + + + + + React 16.3 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/16.3.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/16.4.html b/fixtures/regression/16.4.html new file mode 100644 index 0000000000..f59f4e9e6f --- /dev/null +++ b/fixtures/regression/16.4.html @@ -0,0 +1,38 @@ + + + + + React 16.4 + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/16.4.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/16.5.html b/fixtures/regression/16.5.html new file mode 100644 index 0000000000..455e06f219 --- /dev/null +++ b/fixtures/regression/16.5.html @@ -0,0 +1,40 @@ + + + + + React 16.5 + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/16.5.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/16.6.html b/fixtures/regression/16.6.html new file mode 100644 index 0000000000..1f61c47f85 --- /dev/null +++ b/fixtures/regression/16.6.html @@ -0,0 +1,41 @@ + + + + + React 16.6 + + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/16.6.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/16.7.html b/fixtures/regression/16.7.html new file mode 100644 index 0000000000..d839d7e030 --- /dev/null +++ b/fixtures/regression/16.7.html @@ -0,0 +1,41 @@ + + + + + React 16.7 + + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/16.7.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/canary.html b/fixtures/regression/canary.html new file mode 100644 index 0000000000..ab59d7029e --- /dev/null +++ b/fixtures/regression/canary.html @@ -0,0 +1,41 @@ + + + + + React canary + + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/canary.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/index.html b/fixtures/regression/index.html new file mode 100644 index 0000000000..125a8e8baf --- /dev/null +++ b/fixtures/regression/index.html @@ -0,0 +1,28 @@ + + + + + React DevTools regression test + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fixtures/regression/next.html b/fixtures/regression/next.html new file mode 100644 index 0000000000..77bb9bbed2 --- /dev/null +++ b/fixtures/regression/next.html @@ -0,0 +1,41 @@ + + + + + React next + + + + + + + + + + + + + + +
+ If you are seeing this message, you are likely viewing this file using the file protocol which does not support cross origin requests. +

+ Use the server script instead: +

+ node ./fixtures/regression/server.js
+ open http://localhost:3000/fixtures/regression/next.html +
+ + + + + + \ No newline at end of file diff --git a/fixtures/regression/server.js b/fixtures/regression/server.js new file mode 100755 index 0000000000..f7e03f075d --- /dev/null +++ b/fixtures/regression/server.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +const finalhandler = require('finalhandler'); +const http = require('http'); +const serveStatic = require('serve-static'); + +// Serve fixtures folder +const serve = serveStatic(__dirname, { index: 'index.html' }); + +// Create server +const server = http.createServer(function onRequest(req, res) { + serve(req, res, finalhandler(req, res)); +}); + +// Listen +server.listen(3000); diff --git a/fixtures/regression/shared.js b/fixtures/regression/shared.js new file mode 100644 index 0000000000..2afd063b6b --- /dev/null +++ b/fixtures/regression/shared.js @@ -0,0 +1,330 @@ +/* eslint-disable no-fallthrough, react/react-in-jsx-scope, react/jsx-no-undef */ +/* global React ReactCache ReactDOM SchedulerTracing ScheduleTracing */ + +const apps = []; + +const pieces = React.version.split('.'); +const major = + pieces[0] === '0' ? parseInt(pieces[1], 10) : parseInt(pieces[0], 10); +const minor = + pieces[0] === '0' ? parseInt(pieces[2], 10) : parseInt(pieces[1], 10); + +// Convenience wrapper to organize API features in DevTools. +function Feature({ children, label, version }) { + return ( +
+
+ {label} + {version} +
+ {children} +
+ ); +} + +// Simplify interaction tracing for tests below. +let trace = null; +if (typeof SchedulerTracing !== 'undefined') { + trace = SchedulerTracing.unstable_trace; +} else if (typeof ScheduleTracing !== 'undefined') { + trace = ScheduleTracing.unstable_trace; +} else { + trace = (_, __, callback) => callback(); +} + +// https://github.com/facebook/react/blob/master/CHANGELOG.md +switch (major) { + case 16: + switch (minor) { + case 7: + if (typeof React.useState === 'function') { + // Hooks + function Hooks() { + const [count, setCount] = React.useState(0); + const incrementCount = React.useCallback( + () => setCount(count + 1), + [count] + ); + return ( +
+ count: {count}{' '} + +
+ ); + } + apps.push( + + + + ); + } + case 6: + // memo + function LabelComponent({ label }) { + return ; + } + const AnonymousMemoized = React.memo(({ label }) => ( + + )); + const Memoized = React.memo(LabelComponent); + const CustomMemoized = React.memo(LabelComponent); + CustomMemoized.displayName = 'MemoizedLabelFunction'; + apps.push( + + + + + + ); + + // Suspense + const loadResource = ([text, ms]) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(text); + }, ms); + }); + }; + const getResourceKey = ([text, ms]) => text; + const Resource = ReactCache.unstable_createResource( + loadResource, + getResourceKey + ); + class Suspending extends React.Component { + state = { useSuspense: false }; + useSuspense = () => this.setState({ useSuspense: true }); + render() { + if (this.state.useSuspense) { + const text = Resource.read(['loaded', 2000]); + return text; + } else { + return ; + } + } + } + apps.push( + + loading...}> + + + + ); + + // lazy + const LazyWithDefaultProps = React.lazy( + () => + new Promise(resolve => { + function FooWithDefaultProps(props) { + return ( +

+ {props.greeting}, {props.name} +

+ ); + } + FooWithDefaultProps.defaultProps = { + name: 'World', + greeting: 'Bonjour', + }; + resolve({ + default: FooWithDefaultProps, + }); + }) + ); + apps.push( + + loading...}> + + + + ); + case 5: + case 4: + // unstable_Profiler + class ProfilerChild extends React.Component { + state = { count: 0 }; + incrementCount = () => + this.setState(prevState => ({ count: prevState.count + 1 })); + render() { + return ( +
+ count: {this.state.count}{' '} + +
+ ); + } + } + const onRender = (...args) => {}; + const Profiler = React.unstable_Profiler || React.Profiler; + apps.push( + + +
+ +
+
+
+ ); + case 3: + // createContext() + const LocaleContext = React.createContext(); + LocaleContext.displayName = 'LocaleContext'; + const ThemeContext = React.createContext(); + apps.push( + + + + {theme =>
theme: {theme}
} +
+
+ + + {locale =>
locale: {locale}
} +
+
+
+ ); + + // forwardRef() + const AnonymousFunction = React.forwardRef((props, ref) => ( +
{props.children}
+ )); + const NamedFunction = React.forwardRef(function named(props, ref) { + return
{props.children}
; + }); + const CustomName = React.forwardRef((props, ref) => ( +
{props.children}
+ )); + CustomName.displayName = 'CustomNameForwardRef'; + apps.push( + + AnonymousFunction + NamedFunction + CustomName + + ); + + // StrictMode + class StrictModeChild extends React.Component { + render() { + return 'StrictModeChild'; + } + } + apps.push( + + + + + + ); + + // unstable_AsyncMode (later renamed to unstable_ConcurrentMode, then ConcurrentMode) + const ConcurrentMode = + React.ConcurrentMode || + React.unstable_ConcurrentMode || + React.unstable_AsyncMode; + apps.push( + + +
+ unstable_AsyncMode was added in 16.3, renamed to + unstable_ConcurrentMode in 16.5, and then renamed to + ConcurrentMode in 16.7 +
+
+
+ ); + case 2: + // Fragment + apps.push( + + +
one
+
two
+
+
+ ); + case 1: + case 0: + default: + break; + } + break; + case 15: + break; + case 14: + break; + default: + break; +} + +function Even() { + return (even); +} + +// Simple stateful app shared by all React versions +class SimpleApp extends React.Component { + state = { count: 0 }; + incrementCount = () => { + const updaterFn = prevState => ({ count: prevState.count + 1 }); + trace('Updating count', performance.now(), () => this.setState(updaterFn)); + }; + render() { + const { count } = this.state; + return ( +
+ {count % 2 === 0 ? ( + + count: {count} + + ) : ( + count: {count} + )}{' '} + +
+ ); + } +} +apps.push( + + + +); + +// This component, with the version prop, helps organize DevTools at a glance. +function TopLevelWrapperForDevTools({ version }) { + let header =

React {version}

; + if (version.includes('canary')) { + const commitSha = version.match(/.+canary-(.+)/)[1]; + header = ( +

+ React canary{' '} + + {commitSha} + +

+ ); + } else if (version.includes('alpha')) { + header =

React next

; + } + + return ( +
+ {header} + {apps} +
+ ); +} +TopLevelWrapperForDevTools.displayName = 'React'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/fixtures/regression/styles.css b/fixtures/regression/styles.css new file mode 100644 index 0000000000..6cbaaa5c01 --- /dev/null +++ b/fixtures/regression/styles.css @@ -0,0 +1,37 @@ +body { + font-family: sans-serif; + font-size: 12px; +} + +h1 { + margin: 0; + font-size: 20px; +} + +h2 { + margin: 1rem 0 0; +} + +iframe { + border: 1px solid #ddd; + border-radius: 0.5rem; +} + +code { + white-space: nowrap; +} + +.Feature { + margin: 1rem 0; + border-bottom: 1px solid #eee; + padding-bottom: 1rem; +} +.FeatureHeader { + font-size: 16px; + margin-bottom: 0.5rem; +} +.FeatureCode { + background-color: #eee; + padding: 0.25rem; + border-radius: 0.25rem; +} diff --git a/flow-typed/jest.js b/flow-typed/jest.js index df83ee3ca3..8563e20d93 100644 --- a/flow-typed/jest.js +++ b/flow-typed/jest.js @@ -133,11 +133,13 @@ type JestPromiseType = { * Use rejects to unwrap the reason of a rejected promise so any other * matcher can be chained. If the promise is fulfilled the assertion fails. */ + // eslint-disable-next-line no-use-before-define rejects: JestExpectType, /** * Use resolves to unwrap the value of a fulfilled promise so any other * matcher can be chained. If the promise is rejected the assertion fails. */ + // eslint-disable-next-line no-use-before-define resolves: JestExpectType, }; @@ -1085,8 +1087,10 @@ type JestPrettyFormatColors = { }; type JestPrettyFormatIndent = string => string; +// eslint-disable-next-line no-unused-vars type JestPrettyFormatRefs = Array; type JestPrettyFormatPrint = any => string; +// eslint-disable-next-line no-unused-vars type JestPrettyFormatStringOrNull = string | null; type JestPrettyFormatOptions = {| @@ -1097,6 +1101,7 @@ type JestPrettyFormatOptions = {| indent: number, maxDepth: number, min: boolean, + // eslint-disable-next-line no-use-before-define plugins: JestPrettyFormatPlugins, printFunctionName: boolean, spacing: string, diff --git a/package.json b/package.json index 9e9dbc8332..f9bb136be5 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "/src/__tests__/storeSerializer" ], "testMatch": [ - "**/__tests__/*-test.js" + "**/__tests__/**/*-test.js" ] }, "scripts": { @@ -132,8 +132,10 @@ "prettier": "^1.16.4", "prop-types": "^15.6.2", "react": "^0.0.0-50b50c26f", + "react-15": "npm:react@^15", "react-color": "^2.11.7", "react-dom": "^0.0.0-50b50c26f", + "react-dom-15": "npm:react-dom@^15", "react-is": "^0.0.0-50b50c26f", "react-test-renderer": "^0.0.0-50b50c26f", "react-virtualized-auto-sizer": "^1.0.2", @@ -142,6 +144,7 @@ "rimraf": "^2.6.3", "scheduler": "^0.0.0-50b50c26f", "semver": "^5.5.1", + "serve-static": "^1.14.1", "style-loader": "^0.23.1", "web-ext": "^3.0.0", "webpack": "^4.26.0", diff --git a/src/__tests__/legacy/__snapshots__/storeLegacy-v15-test.js.snap b/src/__tests__/legacy/__snapshots__/storeLegacy-v15-test.js.snap new file mode 100644 index 0000000000..d43112d058 --- /dev/null +++ b/src/__tests__/legacy/__snapshots__/storeLegacy-v15-test.js.snap @@ -0,0 +1,498 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Store (legacy) collapseNodesByDefault:false should not filter DOM nodes from the store tree: 1: mount 1`] = ` +[root] + ▾ + ▾
+ ▾
+ ▾ + ▾
+ ▾ +
+ ▾ + ▾
+ ▾ +
+ +`; + +exports[`Store (legacy) collapseNodesByDefault:false should not filter DOM nodes from the store tree: 2: update 1`] = ` +[root] + ▾ + ▾
+ ▾
+ ▾ + ▾
+ ▾ +
+ ▾ + ▾
+ ▾ +
+ +`; + +exports[`Store (legacy) collapseNodesByDefault:false should not filter DOM nodes from the store tree: 5: unmount 1`] = ``; + +exports[`Store (legacy) collapseNodesByDefault:false should support adding and removing children: 1: mount 1`] = ` +[root] + ▾ + ▾
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support adding and removing children: 2: add child 1`] = ` +[root] + ▾ + ▾
+ ▾ +
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support adding and removing children: 3: remove child 1`] = ` +[root] + ▾ + ▾
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support collapsing parts of the tree: 1: mount 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ + ▾
+ ▾ +
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support collapsing parts of the tree: 2: collapse first Parent 1`] = ` +[root] + ▾ + ▾
+ ▸ + ▾ + ▾
+ ▾ +
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support collapsing parts of the tree: 3: collapse second Parent 1`] = ` +[root] + ▾ + ▾
+ ▸ + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:false should support collapsing parts of the tree: 4: expand first Parent 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▾
+ ▾ +
+ ▾ +
+ ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:false should support collapsing parts of the tree: 5: collapse Grandparent 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:false should support collapsing parts of the tree: 6: expand Grandparent 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▾
+ ▾ +
+ ▾ +
+ ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:false should support mount and update operations for multiple roots: 1: mount 1`] = ` +[root] + ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ +
+[root] + ▾ + ▾
+ ▾ +
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support mount and update operations for multiple roots: 2: update 1`] = ` +[root] + ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ +
+ ▾ +
+[root] + ▾ + ▾
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support mount and update operations for multiple roots: 3: unmount B 1`] = ` +[root] + ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ +
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support mount and update operations for multiple roots: 4: unmount A 1`] = ``; + +exports[`Store (legacy) collapseNodesByDefault:false should support mount and update operations: 1: mount 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ +
+ ▾ +
+ ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ +
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support mount and update operations: 2: update 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ + ▾
+ ▾ +
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support mount and update operations: 3: unmount 1`] = ``; + +exports[`Store (legacy) collapseNodesByDefault:false should support reordering of children: 1: mount 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▾
+ ▾ +
+ ▾ + ▾
+ ▾ +
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support reordering of children: 2: reorder children 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ + ▾
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:false should support reordering of children: 3: collapse root 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:false should support reordering of children: 4: expand root 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▾
+ ▾ +
+ ▾ +
+ ▾ + ▾
+ ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should not filter DOM nodes from the store tree: 1: mount 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should not filter DOM nodes from the store tree: 2: expand Grandparent 1`] = ` +[root] + ▾ + ▸
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should not filter DOM nodes from the store tree: 3: expand div 1`] = ` +[root] + ▾ + ▾
+ ▸
+ ▸ + +`; + +exports[`Store (legacy) collapseNodesByDefault:true should not filter DOM nodes from the store tree: 4: final update 1`] = ` +[root] + ▾ + ▾
+ ▸
+ ▸ + +`; + +exports[`Store (legacy) collapseNodesByDefault:true should not filter DOM nodes from the store tree: 5: unmount 1`] = ``; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding deep parts of the tree: 1: mount 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding deep parts of the tree: 2: expand deepest node 1`] = ` +[root] + ▾ + ▾ + ▾ + ▾ + ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding deep parts of the tree: 3: collapse root 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding deep parts of the tree: 4: expand root 1`] = ` +[root] + ▾ + ▾ + ▾ + ▾ + ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding deep parts of the tree: 5: collapse middle node 1`] = ` +[root] + ▾ + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding deep parts of the tree: 6: expand middle node 1`] = ` +[root] + ▾ + ▾ + ▾ + ▾ + ▾ +
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding parts of the tree: 1: mount 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding parts of the tree: 2: expand Grandparent 1`] = ` +[root] + ▾ + ▸
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding parts of the tree: 3: expand parent div 1`] = ` +[root] + ▾ + ▾
+ ▸ + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding parts of the tree: 4: expand first Parent 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▸
+ ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding parts of the tree: 5: expand second Parent 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▸
+ ▾ + ▸
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding parts of the tree: 6: collapse first Parent 1`] = ` +[root] + ▾ + ▾
+ ▸ + ▾ + ▸
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding parts of the tree: 7: collapse second Parent 1`] = ` +[root] + ▾ + ▾
+ ▸ + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support expanding parts of the tree: 8: collapse Grandparent 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support mount and update operations for multiple roots: 1: mount 1`] = ` +[root] + ▸ +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support mount and update operations for multiple roots: 2: update 1`] = ` +[root] + ▸ +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support mount and update operations for multiple roots: 3: unmount B 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support mount and update operations for multiple roots: 4: unmount A 1`] = ``; + +exports[`Store (legacy) collapseNodesByDefault:true should support mount and update operations: 1: mount 1`] = ` +[root] + ▸
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support mount and update operations: 2: update 1`] = ` +[root] + ▸
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support mount and update operations: 3: unmount 1`] = ``; + +exports[`Store (legacy) collapseNodesByDefault:true should support reordering of children: 1: mount 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support reordering of children: 2: reorder children 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support reordering of children: 3: expand root 1`] = ` +[root] + ▾ + ▸
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support reordering of children: 4: expand div 1`] = ` +[root] + ▾ + ▾
+ ▸ + ▸ +`; + +exports[`Store (legacy) collapseNodesByDefault:true should support reordering of children: 4: expand leaves 1`] = ` +[root] + ▾ + ▾
+ ▾ + ▸
+ ▾ + ▸
+`; + +exports[`Store (legacy) collapseNodesByDefault:true should support reordering of children: 5: collapse root 1`] = ` +[root] + ▸ +`; + +exports[`Store (legacy) should not allow a root node to be collapsed: 1: mount 1`] = ` +[root] + ▸ +`; diff --git a/src/__tests__/legacy/storeLegacy-v15-test.js b/src/__tests__/legacy/storeLegacy-v15-test.js new file mode 100644 index 0000000000..fc5d373e5b --- /dev/null +++ b/src/__tests__/legacy/storeLegacy-v15-test.js @@ -0,0 +1,505 @@ +// @flow + +describe('Store (legacy)', () => { + let React; + let ReactDOM; + let store; + + const act = (callback: Function) => { + callback(); + + jest.runAllTimers(); // Flush Bridge operations + }; + + beforeEach(() => { + store = global.store; + + // Redirect all React/ReactDOM requires to the v15 UMD. + // We use the UMD because Jest doesn't enable us to mock deep imports (e.g. "react/lib/Something"). + jest.mock('react', () => jest.requireActual('react-15/dist/react.js')); + jest.mock('react-dom', () => + jest.requireActual('react-dom-15/dist/react-dom.js') + ); + + React = require('react'); + ReactDOM = require('react-dom'); + }); + + it('should not allow a root node to be collapsed', () => { + const Component = () =>
Hi
; + + act(() => + ReactDOM.render(, document.createElement('div')) + ); + expect(store).toMatchSnapshot('1: mount'); + + expect(store.roots).toHaveLength(1); + + const rootID = store.roots[0]; + + expect(() => store.toggleIsCollapsed(rootID, true)).toThrow( + 'Root nodes cannot be collapsed' + ); + }); + + describe('collapseNodesByDefault:false', () => { + beforeEach(() => { + store.collapseNodesByDefault = false; + }); + + it('should support mount and update operations', () => { + const Grandparent = ({ count }) => ( +
+ + +
+ ); + const Parent = ({ count }) => ( +
+ {new Array(count).fill(true).map((_, index) => ( + + ))} +
+ ); + const Child = () =>
Hi!
; + + const container = document.createElement('div'); + + act(() => ReactDOM.render(, container)); + expect(store).toMatchSnapshot('1: mount'); + + act(() => ReactDOM.render(, container)); + expect(store).toMatchSnapshot('2: update'); + + act(() => ReactDOM.unmountComponentAtNode(container)); + expect(store).toMatchSnapshot('3: unmount'); + }); + + it('should support mount and update operations for multiple roots', () => { + const Parent = ({ count }) => ( +
+ {new Array(count).fill(true).map((_, index) => ( + + ))} +
+ ); + const Child = () =>
Hi!
; + + const containerA = document.createElement('div'); + const containerB = document.createElement('div'); + + act(() => { + ReactDOM.render(, containerA); + ReactDOM.render(, containerB); + }); + expect(store).toMatchSnapshot('1: mount'); + + act(() => { + ReactDOM.render(, containerA); + ReactDOM.render(, containerB); + }); + expect(store).toMatchSnapshot('2: update'); + + act(() => ReactDOM.unmountComponentAtNode(containerB)); + expect(store).toMatchSnapshot('3: unmount B'); + + act(() => ReactDOM.unmountComponentAtNode(containerA)); + expect(store).toMatchSnapshot('4: unmount A'); + }); + + it('should not filter DOM nodes from the store tree', () => { + const Grandparent = ({ flip }) => ( +
+
+ +
+ + +
+ ); + const Parent = ({ flip }) => ( +
+ {flip ? 'foo' : null} + + {flip && [null, 'hello', 42]} + {flip ? 'bar' : 'baz'} +
+ ); + const Child = () =>
Hi!
; + const Nothing = () => null; + + const container = document.createElement('div'); + act(() => + ReactDOM.render(, container) + ); + expect(store).toMatchSnapshot('1: mount'); + + act(() => + ReactDOM.render(, container) + ); + expect(store).toMatchSnapshot('2: update'); + + act(() => ReactDOM.unmountComponentAtNode(container)); + expect(store).toMatchSnapshot('5: unmount'); + }); + + it('should support collapsing parts of the tree', () => { + const Grandparent = ({ count }) => ( +
+ + +
+ ); + const Parent = ({ count }) => ( +
+ {new Array(count).fill(true).map((_, index) => ( + + ))} +
+ ); + const Child = () =>
Hi!
; + + act(() => + ReactDOM.render( + , + document.createElement('div') + ) + ); + expect(store).toMatchSnapshot('1: mount'); + + const grandparentID = store.getElementIDAtIndex(0); + const parentOneID = store.getElementIDAtIndex(2); + const parentTwoID = store.getElementIDAtIndex(8); + + act(() => store.toggleIsCollapsed(parentOneID, true)); + expect(store).toMatchSnapshot('2: collapse first Parent'); + + act(() => store.toggleIsCollapsed(parentTwoID, true)); + expect(store).toMatchSnapshot('3: collapse second Parent'); + + act(() => store.toggleIsCollapsed(parentOneID, false)); + expect(store).toMatchSnapshot('4: expand first Parent'); + + act(() => store.toggleIsCollapsed(grandparentID, true)); + expect(store).toMatchSnapshot('5: collapse Grandparent'); + + act(() => store.toggleIsCollapsed(grandparentID, false)); + expect(store).toMatchSnapshot('6: expand Grandparent'); + }); + + it('should support adding and removing children', () => { + const Root = ({ children }) =>
{children}
; + const Component = () =>
; + + const container = document.createElement('div'); + + act(() => + ReactDOM.render( + + + , + container + ) + ); + expect(store).toMatchSnapshot('1: mount'); + + act(() => + ReactDOM.render( + + + + , + container + ) + ); + expect(store).toMatchSnapshot('2: add child'); + + act(() => + ReactDOM.render( + + + , + container + ) + ); + expect(store).toMatchSnapshot('3: remove child'); + }); + + it('should support reordering of children', () => { + const Root = ({ children }) =>
{children}
; + const Component = () =>
; + + const Foo = () =>
{[]}
; + const Bar = () => ( +
{[, ]}
+ ); + const foo = ; + const bar = ; + + const container = document.createElement('div'); + + act(() => ReactDOM.render({[foo, bar]}, container)); + expect(store).toMatchSnapshot('1: mount'); + + act(() => ReactDOM.render({[bar, foo]}, container)); + expect(store).toMatchSnapshot('2: reorder children'); + + act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true)); + expect(store).toMatchSnapshot('3: collapse root'); + + act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false)); + expect(store).toMatchSnapshot('4: expand root'); + }); + }); + + describe('collapseNodesByDefault:true', () => { + beforeEach(() => { + store.collapseNodesByDefault = true; + }); + + it('should support mount and update operations', () => { + const Parent = ({ count }) => ( +
+ {new Array(count).fill(true).map((_, index) => ( + + ))} +
+ ); + const Child = () =>
Hi!
; + + const container = document.createElement('div'); + + act(() => + ReactDOM.render( +
+ + +
, + container + ) + ); + expect(store).toMatchSnapshot('1: mount'); + + act(() => + ReactDOM.render( +
+ + +
, + container + ) + ); + expect(store).toMatchSnapshot('2: update'); + + act(() => ReactDOM.unmountComponentAtNode(container)); + expect(store).toMatchSnapshot('3: unmount'); + }); + + it('should support mount and update operations for multiple roots', () => { + const Parent = ({ count }) => ( +
+ {new Array(count).fill(true).map((_, index) => ( + + ))} +
+ ); + const Child = () =>
Hi!
; + + const containerA = document.createElement('div'); + const containerB = document.createElement('div'); + + act(() => { + ReactDOM.render(, containerA); + ReactDOM.render(, containerB); + }); + expect(store).toMatchSnapshot('1: mount'); + + act(() => { + ReactDOM.render(, containerA); + ReactDOM.render(, containerB); + }); + expect(store).toMatchSnapshot('2: update'); + + act(() => ReactDOM.unmountComponentAtNode(containerB)); + expect(store).toMatchSnapshot('3: unmount B'); + + act(() => ReactDOM.unmountComponentAtNode(containerA)); + expect(store).toMatchSnapshot('4: unmount A'); + }); + + it('should not filter DOM nodes from the store tree', () => { + const Grandparent = ({ flip }) => ( +
+
+ +
+ + +
+ ); + const Parent = ({ flip }) => ( +
+ {flip ? 'foo' : null} + + {flip && [null, 'hello', 42]} + {flip ? 'bar' : 'baz'} +
+ ); + const Child = () =>
Hi!
; + const Nothing = () => null; + + const container = document.createElement('div'); + act(() => + ReactDOM.render(, container) + ); + expect(store).toMatchSnapshot('1: mount'); + + act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false)); + expect(store).toMatchSnapshot('2: expand Grandparent'); + + act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false)); + expect(store).toMatchSnapshot('3: expand div'); + + act(() => + ReactDOM.render(, container) + ); + expect(store).toMatchSnapshot('4: final update'); + + act(() => ReactDOM.unmountComponentAtNode(container)); + expect(store).toMatchSnapshot('5: unmount'); + }); + + it('should support expanding parts of the tree', () => { + const Grandparent = ({ count }) => ( +
+ + +
+ ); + const Parent = ({ count }) => ( +
+ {new Array(count).fill(true).map((_, index) => ( + + ))} +
+ ); + const Child = () =>
Hi!
; + + act(() => + ReactDOM.render( + , + document.createElement('div') + ) + ); + expect(store).toMatchSnapshot('1: mount'); + + const grandparentID = store.getElementIDAtIndex(0); + + act(() => store.toggleIsCollapsed(grandparentID, false)); + expect(store).toMatchSnapshot('2: expand Grandparent'); + + const parentDivID = store.getElementIDAtIndex(1); + act(() => store.toggleIsCollapsed(parentDivID, false)); + expect(store).toMatchSnapshot('3: expand parent div'); + + const parentOneID = store.getElementIDAtIndex(2); + const parentTwoID = store.getElementIDAtIndex(3); + + act(() => store.toggleIsCollapsed(parentOneID, false)); + expect(store).toMatchSnapshot('4: expand first Parent'); + + act(() => store.toggleIsCollapsed(parentTwoID, false)); + expect(store).toMatchSnapshot('5: expand second Parent'); + + act(() => store.toggleIsCollapsed(parentOneID, true)); + expect(store).toMatchSnapshot('6: collapse first Parent'); + + act(() => store.toggleIsCollapsed(parentTwoID, true)); + expect(store).toMatchSnapshot('7: collapse second Parent'); + + act(() => store.toggleIsCollapsed(grandparentID, true)); + expect(store).toMatchSnapshot('8: collapse Grandparent'); + }); + + it('should support expanding deep parts of the tree', () => { + const Wrapper = ({ forwardedRef }) => ( + + ); + const Nested = ({ depth, forwardedRef }) => + depth > 0 ? ( + + ) : ( +
+ ); + + let ref = null; + const refSetter = value => { + ref = value; + }; + + act(() => + ReactDOM.render( + , + document.createElement('div') + ) + ); + expect(store).toMatchSnapshot('1: mount'); + + const deepestedNodeID = global.agent.getIDForNode(ref); + + act(() => store.toggleIsCollapsed(deepestedNodeID, false)); + expect(store).toMatchSnapshot('2: expand deepest node'); + + const rootID = store.getElementIDAtIndex(0); + + act(() => store.toggleIsCollapsed(rootID, true)); + expect(store).toMatchSnapshot('3: collapse root'); + + act(() => store.toggleIsCollapsed(rootID, false)); + expect(store).toMatchSnapshot('4: expand root'); + + const id = store.getElementIDAtIndex(1); + + act(() => store.toggleIsCollapsed(id, true)); + expect(store).toMatchSnapshot('5: collapse middle node'); + + act(() => store.toggleIsCollapsed(id, false)); + expect(store).toMatchSnapshot('6: expand middle node'); + }); + + it('should support reordering of children', () => { + const Root = ({ children }) =>
{children}
; + const Component = () =>
; + + const Foo = () =>
{[]}
; + const Bar = () => ( +
{[, ]}
+ ); + const foo = ; + const bar = ; + + const container = document.createElement('div'); + + act(() => ReactDOM.render({[foo, bar]}, container)); + expect(store).toMatchSnapshot('1: mount'); + + act(() => ReactDOM.render({[bar, foo]}, container)); + expect(store).toMatchSnapshot('2: reorder children'); + + act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false)); + expect(store).toMatchSnapshot('3: expand root'); + + act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false)); + expect(store).toMatchSnapshot('4: expand div'); + + act(() => { + store.toggleIsCollapsed(store.getElementIDAtIndex(3), false); + store.toggleIsCollapsed(store.getElementIDAtIndex(2), false); + }); + expect(store).toMatchSnapshot('4: expand leaves'); + + act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true)); + expect(store).toMatchSnapshot('5: collapse root'); + }); + }); +}); diff --git a/src/__tests__/utils.js b/src/__tests__/utils.js index 57fb2de3d3..089ddf045f 100644 --- a/src/__tests__/utils.js +++ b/src/__tests__/utils.js @@ -47,17 +47,15 @@ export async function actAsync( export function beforeEachProfiling(): void { // Mock React's timing information so that test runs are predictable. - jest.mock('scheduler', () => - // $FlowFixMe Flow does not konw about requireActual - require.requireActual('scheduler/unstable_mock') - ); + jest.mock('scheduler', () => jest.requireActual('scheduler/unstable_mock')); // DevTools itself uses performance.now() to offset commit times // so they appear relative to when profiling was started in the UI. - jest.spyOn(performance, 'now').mockImplementation( - // $FlowFixMe Flow does not konw about requireActual - require.requireActual('scheduler/unstable_mock').unstable_now - ); + jest + .spyOn(performance, 'now') + .mockImplementation( + jest.requireActual('scheduler/unstable_mock').unstable_now + ); } export function createDisplayNameFilter( diff --git a/src/backend/index.js b/src/backend/index.js index 58045bc0a2..3e65e4c00c 100644 --- a/src/backend/index.js +++ b/src/backend/index.js @@ -4,6 +4,7 @@ import type { DevToolsHook, ReactRenderer, RendererInterface } from './types'; import Agent from './agent'; import { attach } from './renderer'; +import { attach as attachLegacy } from './legacy/renderer'; export function initBackend( hook: DevToolsHook, @@ -40,7 +41,11 @@ export function initBackend( // Inject any not-yet-injected renderers (if we didn't reload-and-profile) if (!rendererInterface) { - rendererInterface = attach(hook, id, renderer, global); + if (typeof renderer.findFiberByHostInstance === 'function') { + rendererInterface = attach(hook, id, renderer, global); + } else { + rendererInterface = attachLegacy(hook, id, renderer, global); + } hook.rendererInterfaces.set(id, rendererInterface); } diff --git a/src/backend/legacy/getData.js b/src/backend/legacy/getData.js new file mode 100644 index 0000000000..d2fce307d3 --- /dev/null +++ b/src/backend/legacy/getData.js @@ -0,0 +1,40 @@ +// @flow + +import { + ElementTypeClass, + ElementTypeHostComponent, + ElementTypeOtherOrUnknown, +} from 'src/types'; +import { getDisplayName } from 'src/utils'; + +import type { InternalInstance } from './renderer'; +import type { FiberData } from '../types'; + +export default function getData(internalInstance: InternalInstance): FiberData { + let displayName = null; + let key = null; + let type = ElementTypeOtherOrUnknown; + + // != used deliberately here to catch undefined and null + if (internalInstance._currentElement != null) { + if (internalInstance._currentElement.key) { + key = String(internalInstance._currentElement.key); + } + + const elementType = internalInstance._currentElement.type; + if (typeof elementType === 'string') { + type = ElementTypeHostComponent; + displayName = elementType; + } else if (typeof elementType === 'function') { + // TODO: detect function components. + type = ElementTypeClass; + displayName = getDisplayName(elementType); + } + } + + return { + displayName, + key, + type, + }; +} diff --git a/src/backend/legacy/renderer.js b/src/backend/legacy/renderer.js new file mode 100644 index 0000000000..5ea0a9fb95 --- /dev/null +++ b/src/backend/legacy/renderer.js @@ -0,0 +1,791 @@ +// @flow + +import { + ElementTypeClass, + ElementTypeFunction, + ElementTypeRoot, + ElementTypeHostComponent, + ElementTypeOtherOrUnknown, +} from 'src/types'; +import { getUID, utfEncodeString, printOperationsArray } from '../../utils'; +import { cleanForBridge, copyWithSet } from '../utils'; +import { + __DEBUG__, + TREE_OPERATION_ADD, + TREE_OPERATION_REMOVE, + TREE_OPERATION_REORDER_CHILDREN, +} from '../../constants'; +import getData from './getData'; +import { decorateMany, forceUpdate, restoreMany } from './utils'; + +import type { + DevToolsHook, + GetFiberIDForNative, + NativeType, + PathFrame, + PathMatch, + RendererInterface, +} from '../types'; +import type { ComponentFilter, ElementType } from 'src/types'; +import type { Owner, InspectedElement } from '../types'; + +export type InternalInstance = Object; +type LegacyRenderer = Object; + +function getElementType(internalInstance: InternalInstance): ElementType { + // != used deliberately here to catch undefined and null + if (internalInstance._currentElement != null) { + const elementType = internalInstance._currentElement.type; + if (typeof elementType === 'function') { + return ElementTypeClass; + } else if (typeof elementType === 'string') { + return ElementTypeHostComponent; + } + } + return ElementTypeOtherOrUnknown; +} + +function getChildren(internalInstance: Object): Array { + let children = []; + + // If the parent is a native node without rendered children, but with + // multiple string children, then the `element` that gets passed in here is + // a plain value -- a string or number. + if (typeof internalInstance !== 'object') { + // No children + } else if ( + internalInstance._currentElement === null || + internalInstance._currentElement === false + ) { + // No children + } else if (internalInstance._renderedComponent) { + const child = internalInstance._renderedComponent; + if (getElementType(child) !== ElementTypeOtherOrUnknown) { + children.push(child); + } + } else if (internalInstance._renderedChildren) { + const renderedChildren = internalInstance._renderedChildren; + for (let name in renderedChildren) { + const child = renderedChildren[name]; + if (getElementType(child) !== ElementTypeOtherOrUnknown) { + children.push(child); + } + } + } + // Note: we skip the case where children are just strings or numbers + // because the new DevTools skips over host text nodes anyway. + return children; +} + +export function attach( + hook: DevToolsHook, + rendererID: number, + renderer: LegacyRenderer, + global: Object +): RendererInterface { + const idToInternalInstanceMap: Map = new Map(); + const internalInstanceToIDMap: WeakMap< + InternalInstance, + number + > = new WeakMap(); + const internalInstanceToRootIDMap: WeakMap< + InternalInstance, + number + > = new WeakMap(); + + let getInternalIDForNative: GetFiberIDForNative = ((null: any): GetFiberIDForNative); + let findNativeNodeForInternalID: (id: number) => ?NativeType; + + if (renderer.ComponentTree) { + getInternalIDForNative = (node, findNearestUnfilteredAncestor) => { + const internalInstance = renderer.ComponentTree.getClosestInstanceFromNode( + node + ); + return internalInstanceToIDMap.get(internalInstance) || null; + }; + findNativeNodeForInternalID = (id: number) => { + const internalInstance = idToInternalInstanceMap.get(id); + return renderer.ComponentTree.getNodeFromInstance(internalInstance); + }; + } else if (renderer.Mount.getID && renderer.Mount.getNode) { + getInternalIDForNative = (node, findNearestUnfilteredAncestor) => { + // Not implemented. + return null; + }; + findNativeNodeForInternalID = (id: number) => { + // Not implemented. + return null; + }; + } + + function getID(internalInstance: InternalInstance): number { + if (typeof internalInstance !== 'object') { + throw new Error('Invalid internal instance: ' + internalInstance); + } + if (!internalInstanceToIDMap.has(internalInstance)) { + const id = getUID(); + internalInstanceToIDMap.set(internalInstance, id); + idToInternalInstanceMap.set(id, internalInstance); + } + return ((internalInstanceToIDMap.get(internalInstance): any): number); + } + + function areEqualArrays(a, b) { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; + } + + // This is shared mutable state that lets us keep track of where we are. + let parentIDStack = []; + + let oldReconcilerMethods = null; + if (renderer.Reconciler) { + // React 15 + oldReconcilerMethods = decorateMany(renderer.Reconciler, { + mountComponent(fn, args) { + const [internalInstance, , , hostContainerInfo] = args; + if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) { + return fn.apply(this, args); + } + + const id = getID(internalInstance); + + // Push the operation. + const parentID = + parentIDStack.length > 0 + ? parentIDStack[parentIDStack.length - 1] + : 0; + recordMount(internalInstance, id, parentID); + parentIDStack.push(id); + + // Remember the root. + internalInstanceToRootIDMap.set( + internalInstance, + getID(hostContainerInfo._topLevelWrapper) + ); + + try { + const result = fn.apply(this, args); + parentIDStack.pop(); + return result; + } catch (err) { + parentIDStack = []; + throw err; + } finally { + if (parentIDStack.length === 0) { + const rootID = internalInstanceToRootIDMap.get(internalInstance); + if (rootID === undefined) { + throw new Error('Expected to find root ID.'); + } + flushPendingEvents(rootID); + } + } + }, + performUpdateIfNecessary(fn, args) { + const [internalInstance] = args; + if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) { + return fn.apply(this, args); + } + + const id = getID(internalInstance); + parentIDStack.push(id); + + const prevChildren = getChildren(internalInstance); + try { + const result = fn.apply(this, args); + + const nextChildren = getChildren(internalInstance); + if (!areEqualArrays(prevChildren, nextChildren)) { + // Push the operation + recordReorder(internalInstance, id, nextChildren); + } + + parentIDStack.pop(); + return result; + } catch (err) { + parentIDStack = []; + throw err; + } finally { + if (parentIDStack.length === 0) { + const rootID = internalInstanceToRootIDMap.get(internalInstance); + if (rootID === undefined) { + throw new Error('Expected to find root ID.'); + } + flushPendingEvents(rootID); + } + } + }, + receiveComponent(fn, args) { + const [internalInstance] = args; + if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) { + return fn.apply(this, args); + } + + const id = getID(internalInstance); + parentIDStack.push(id); + + const prevChildren = getChildren(internalInstance); + try { + const result = fn.apply(this, args); + + const nextChildren = getChildren(internalInstance); + if (!areEqualArrays(prevChildren, nextChildren)) { + // Push the operation + recordReorder(internalInstance, id, nextChildren); + } + + parentIDStack.pop(); + return result; + } catch (err) { + parentIDStack = []; + throw err; + } finally { + if (parentIDStack.length === 0) { + const rootID = internalInstanceToRootIDMap.get(internalInstance); + if (rootID === undefined) { + throw new Error('Expected to find root ID.'); + } + flushPendingEvents(rootID); + } + } + }, + unmountComponent(fn, args) { + const [internalInstance] = args; + if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) { + return fn.apply(this, args); + } + + const id = getID(internalInstance); + parentIDStack.push(id); + try { + const result = fn.apply(this, args); + parentIDStack.pop(); + + // Push the operation. + recordUnmount(internalInstance, id); + + return result; + } catch (err) { + parentIDStack = []; + throw err; + } finally { + if (parentIDStack.length === 0) { + const rootID = internalInstanceToRootIDMap.get(internalInstance); + if (rootID === undefined) { + throw new Error('Expected to find root ID.'); + } + flushPendingEvents(rootID); + } + } + }, + }); + } + + function cleanup() { + if (oldReconcilerMethods !== null) { + if (renderer.Component) { + restoreMany(renderer.Component.Mixin, oldReconcilerMethods); + } else { + restoreMany(renderer.Reconciler, oldReconcilerMethods); + } + } + oldReconcilerMethods = null; + } + + function recordMount( + internalInstance: InternalInstance, + id: number, + parentID: number + ) { + const isRoot = parentID === 0; + + if (__DEBUG__) { + console.log( + '%crecordMount()', + 'color: green; font-weight: bold;', + id, + getData(internalInstance).displayName + ); + } + + if (isRoot) { + // TODO Is this right? For all versions? + const hasOwnerMetadata = + internalInstance._currentElement != null && + internalInstance._currentElement._owner != null; + + pushOperation(TREE_OPERATION_ADD); + pushOperation(id); + pushOperation(ElementTypeRoot); + pushOperation(0); // isProfilingSupported? + pushOperation(hasOwnerMetadata ? 1 : 0); + } else { + const { displayName, key, type } = getData(internalInstance); + + const ownerID = + internalInstance._currentElement != null && + internalInstance._currentElement._owner != null + ? getID(internalInstance._currentElement._owner) + : 0; + + let displayNameStringID = getStringID(displayName); + let keyStringID = getStringID(key); + pushOperation(TREE_OPERATION_ADD); + pushOperation(id); + pushOperation(type); + pushOperation(parentID); + pushOperation(ownerID); + pushOperation(displayNameStringID); + pushOperation(keyStringID); + } + } + + function recordReorder( + internalInstance: InternalInstance, + id: number, + nextChildren: Array + ) { + pushOperation(TREE_OPERATION_REORDER_CHILDREN); + pushOperation(id); + const nextChildIDs = nextChildren.map(getID); + pushOperation(nextChildIDs.length); + for (let i = 0; i < nextChildIDs.length; i++) { + pushOperation(nextChildIDs[i]); + } + } + + function recordUnmount(internalInstance: InternalInstance, id: number) { + pendingUnmountedIDs.push(id); + idToInternalInstanceMap.delete(id); + } + + function crawlAndRecordInitialMounts(id: number, parentID: number) { + const internalInstance = idToInternalInstanceMap.get(id); + + if (__DEBUG__) { + console.group('crawlAndRecordInitialMounts() id:', id); + } + + recordMount(internalInstance, id, parentID); + getChildren(internalInstance).forEach(child => + crawlAndRecordInitialMounts(getID(child), id) + ); + + if (__DEBUG__) { + console.groupEnd(); + } + } + + function flushInitialOperations() { + // Crawl roots though and register any nodes that mounted before we were injected. + + const roots = + renderer.Mount._instancesByReactRootID || + renderer.Mount._instancesByContainerID; + + for (let key in roots) { + const internalInstance = roots[key]; + const id = getID(internalInstance); + crawlAndRecordInitialMounts(id, 0); + flushPendingEvents(id); + } + } + + let pendingOperations: Array = []; + let pendingStringTable: Map = new Map(); + let pendingUnmountedIDs: Array = []; + let pendingStringTableLength: number = 0; + let pendingUnmountedRootID: number | null = null; + + function flushPendingEvents(rootID: number) { + if ( + pendingOperations.length === 0 && + pendingUnmountedIDs.length === 0 && + pendingUnmountedRootID === null + ) { + return; + } + + const numUnmountIDs = + pendingUnmountedIDs.length + (pendingUnmountedRootID === null ? 0 : 1); + + const operations = new Uint32Array( + // Identify which renderer this update is coming from. + 2 + // [rendererID, rootFiberID] + // How big is the string table? + 1 + // [stringTableLength] + // Then goes the actual string table. + pendingStringTableLength + + // All unmounts are batched in a single message. + // [TREE_OPERATION_REMOVE, removedIDLength, ...ids] + (numUnmountIDs > 0 ? 2 + numUnmountIDs : 0) + + // Mount operations + pendingOperations.length + ); + + // Identify which renderer this update is coming from. + // This enables roots to be mapped to renderers, + // Which in turn enables fiber properations, states, and hooks to be inspected. + let i = 0; + operations[i++] = rendererID; + operations[i++] = rootID; + + // Now fill in the string table. + // [stringTableLength, str1Length, ...str1, str2Length, ...str2, ...] + operations[i++] = pendingStringTableLength; + pendingStringTable.forEach((value, key) => { + operations[i++] = key.length; + operations.set(utfEncodeString(key), i); + i += key.length; + }); + + if (numUnmountIDs > 0) { + // All unmounts except roots are batched in a single message. + operations[i++] = TREE_OPERATION_REMOVE; + // The first number is how many unmounted IDs we're gonna send. + operations[i++] = numUnmountIDs; + // Fill in the unmounts + for (let j = 0; j < pendingUnmountedIDs.length; j++) { + operations[i++] = pendingUnmountedIDs[j]; + } + // The root ID should always be unmounted last. + if (pendingUnmountedRootID !== null) { + operations[i] = pendingUnmountedRootID; + i++; + } + } + + // Fill in the rest of the operations. + operations.set(pendingOperations, i); + i += pendingOperations.length; + + if (__DEBUG__) { + printOperationsArray(operations); + } + + // If we've already connected to the frontend, just pass the operations through. + hook.emit('operations', operations); + + pendingOperations.length = 0; + pendingUnmountedIDs = []; + pendingUnmountedRootID = null; + pendingStringTable.clear(); + pendingStringTableLength = 0; + } + + function pushOperation(op: number): void { + if (__DEV__) { + if (!Number.isInteger(op)) { + console.error( + 'pushOperation() was called but the value is not an integer.', + op + ); + } + } + pendingOperations.push(op); + } + + function getStringID(str: string | null): number { + if (str === null) { + return 0; + } + const existingID = pendingStringTable.get(str); + if (existingID !== undefined) { + return existingID; + } + const stringID = pendingStringTable.size + 1; + pendingStringTable.set(str, stringID); + // The string table total length needs to account + // both for the string length, and for the array item + // that contains the length itself. Hence + 1. + pendingStringTableLength += str.length + 1; + return stringID; + } + + function inspectElement(id: number): InspectedElement | null { + let result = inspectElementRaw(id); + if (result === null) { + return null; + } + // TODO Review sanitization approach for the below inspectable values. + result.context = cleanForBridge(result.context); + result.props = cleanForBridge(result.props); + result.state = cleanForBridge(result.state); + return result; + } + + function inspectElementRaw(id: number): InspectedElement | null { + const internalInstance = idToInternalInstanceMap.get(id); + const data = getData(internalInstance); + + let context = null; + let owners = null; + let props = null; + let state = null; + let source = null; + + if (internalInstance != null) { + const element = internalInstance._currentElement; + if (element !== null) { + props = element.props; + source = element._source != null ? element._source : null; + + let owner = element._owner; + if (owner) { + owners = []; + while (owner != null) { + const ownerData = getData(owner); + owners.push({ + displayName: ownerData.displayName || 'Unknown', + id: getID(owner), + type: ownerData.type, + }); + owner = owner.owner; + } + } + } + context = internalInstance.context || null; + state = internalInstance.state || null; + } + + return { + id, + + // Hooks did not exist in legacy versions + canEditHooks: false, + + // Does the current renderer support editable function props? + canEditFunctionProps: true, + + // Suspense did not exist in legacy versions + canToggleSuspense: false, + + // Can view component source location. + canViewSource: + data.type === ElementTypeClass || data.type === ElementTypeFunction, + + displayName: data.displayName, + + type: data.type, + + // New events system did not exist in legacy versions + events: null, + + // Inspectable properties. + context, + hooks: null, + props, + state, + + // List of owners + owners, + + // Location of component in source coude. + source, + }; + } + + function logElementToConsole(id: number): void { + const result = inspectElementRaw(id); + if (result === null) { + console.warn(`Could not find element with id "${id}"`); + return; + } + + const supportsGroup = typeof console.groupCollapsed === 'function'; + if (supportsGroup) { + console.groupCollapsed( + `[Click to expand] %c<${result.displayName || 'Component'} />`, + // --dom-tag-name-color is the CSS variable Chrome styles HTML elements with in the console. + 'color: var(--dom-tag-name-color); font-weight: normal;' + ); + } + if (result.props !== null) { + console.log('Props:', result.props); + } + if (result.state !== null) { + console.log('State:', result.state); + } + if (result.context !== null) { + console.log('State:', result.context); + } + const nativeNode = findNativeNodeForInternalID(id); + if (nativeNode !== null) { + console.log('Node:', nativeNode); + } + if (window.chrome || /firefox/i.test(navigator.userAgent)) { + console.log( + 'Right-click any value to save it as a global variable for further inspection.' + ); + } + if (supportsGroup) { + console.groupEnd(); + } + } + + function prepareViewElementSource(id: number): void { + const internalInstance = idToInternalInstanceMap.get(id); + if (internalInstance == null) { + console.warn(`Could not find instance with id "${id}"`); + return; + } + + const element = internalInstance._currentElement; + if (element == null) { + console.warn(`Could not find element with id "${id}"`); + return; + } + + global.$type = element.type; + } + + function selectElement(id: number): void { + const internalInstance = idToInternalInstanceMap.get(id); + if (internalInstance == null) { + console.warn(`Could not find instance with id "${id}"`); + return; + } + + switch (getElementType(internalInstance)) { + case ElementTypeClass: + case ElementTypeFunction: + const element = internalInstance._currentElement; + if (element == null) { + console.warn(`Could not find element with id "${id}"`); + return; + } + + global.$r = { + props: element.props, + type: element.type, + }; + break; + default: + break; + } + } + + function setInProps(id: number, path: Array, value: any) { + const internalInstance = idToInternalInstanceMap.get(id); + if (internalInstance != null) { + const element = internalInstance._currentElement; + internalInstance._currentElement = { + ...element, + props: copyWithSet(element.props, path, value), + }; + forceUpdate(internalInstance._instance); + } + } + + function setInState(id: number, path: Array, value: any) { + const internalInstance = idToInternalInstanceMap.get(id); + if (internalInstance != null) { + setIn(internalInstance.state, path, value); + internalInstance.forceUpdate(); + } + } + + function setInContext(id: number, path: Array, value: any) { + const internalInstance = idToInternalInstanceMap.get(id); + if (internalInstance != null) { + setIn(internalInstance.context, path, value); + forceUpdate(internalInstance); + } + } + + function setIn(obj: Object, path: Array, value: any) { + const last = path.pop(); + const parent = path.reduce( + // $FlowFixMe + (reduced, attr) => (reduced ? reduced[attr] : null), + obj + ); + if (parent) { + // $FlowFixMe + parent[last] = value; + } + } + + // v16+ only features + const getProfilingData = () => { + throw new Error('getProfilingData not supported by this renderer'); + }; + const handleCommitFiberRoot = () => { + throw new Error('handleCommitFiberRoot not supported by this renderer'); + }; + const handleCommitFiberUnmount = () => { + throw new Error('handleCommitFiberUnmount not supported by this renderer'); + }; + const overrideSuspense = () => { + throw new Error('overrideSuspense not supported by this renderer'); + }; + const setInHook = () => { + throw new Error('setInHook not supported by this renderer'); + }; + const startProfiling = () => { + throw new Error('startProfiling not supported by this renderer'); + }; + const stopProfiling = () => { + throw new Error('stopProfiling not supported by this renderer'); + }; + + function getBestMatchForTrackedPath(): PathMatch | null { + // Not implemented. + return null; + } + + function getPathForElement(id: number): Array | null { + // Not implemented. + return null; + } + + function updateComponentFilters(componentFilters: Array) { + // Not implemented. + } + + function setTrackedPath(path: Array | null) { + // Not implemented. + } + + function getOwnersList(id: number): Array | null { + // Not implemented. + return null; + } + + return { + cleanup, + flushInitialOperations, + getBestMatchForTrackedPath, + getFiberIDForNative: getInternalIDForNative, + findNativeNodesForFiberID: (id: number) => { + const nativeNode = findNativeNodeForInternalID(id); + return nativeNode == null ? null : [nativeNode]; + }, + getOwnersList, + getPathForElement, + getProfilingData, + handleCommitFiberRoot, + handleCommitFiberUnmount, + inspectElement, + logElementToConsole, + overrideSuspense, + prepareViewElementSource, + renderer, + selectElement, + setInContext, + setInHook, + setInProps, + setInState, + setTrackedPath, + startProfiling, + stopProfiling, + updateComponentFilters, + }; +} diff --git a/src/backend/legacy/utils.js b/src/backend/legacy/utils.js new file mode 100644 index 0000000000..9fbe307935 --- /dev/null +++ b/src/backend/legacy/utils.js @@ -0,0 +1,39 @@ +// @flow + +import type { InternalInstance } from './renderer'; + +export function decorate(object: Object, attr: string, fn: Function): Function { + const old = object[attr]; + object[attr] = function(instance: InternalInstance) { + return fn.call(this, old, arguments); + }; + return old; +} + +export function decorateMany( + source: Object, + fns: { [attr: string]: Function } +): Object { + const olds = {}; + for (const name in fns) { + olds[name] = decorate(source, name, fns[name]); + } + return olds; +} + +export function restoreMany(source: Object, olds: Object): void { + for (let name in olds) { + source[name] = olds[name]; + } +} + +export function forceUpdate(instance: InternalInstance): void { + if (typeof instance.forceUpdate === 'function') { + instance.forceUpdate(); + } else if ( + instance.updater != null && + typeof instance.updater.enqueueForceUpdate === 'function' + ) { + instance.updater.enqueueForceUpdate(this, () => {}, 'forceUpdate'); + } +} diff --git a/src/backend/types.js b/src/backend/types.js index d2fd74139e..af2cdd2923 100644 --- a/src/backend/types.js +++ b/src/backend/types.js @@ -79,11 +79,17 @@ export type FiberData = {| type: ElementType, |}; -export type NativeType = {}; +export type NativeType = Object; export type RendererID = number; type Dispatcher = any; +export type GetFiberIDForNative = ( + component: NativeType, + findNearestUnfilteredAncestor?: boolean +) => number | null; +export type FindNativeNodesForFiberID = (id: number) => ?Array; + export type ReactRenderer = { findFiberByHostInstance: (hostInstance: NativeType) => ?Fiber, version: string, @@ -208,13 +214,10 @@ export type InspectedElement = {| export type RendererInterface = { cleanup: () => void, - findNativeNodesForFiberID: (id: number) => ?Array, + findNativeNodesForFiberID: FindNativeNodesForFiberID, flushInitialOperations: () => void, getBestMatchForTrackedPath: () => PathMatch | null, - getFiberIDForNative: ( - component: NativeType, - findNearestUnfilteredAncestor?: boolean - ) => number | null, + getFiberIDForNative: GetFiberIDForNative, getProfilingData(): ProfilingDataBackend, getOwnersList: (id: number) => Array | null, getPathForElement: (id: number) => Array | null, diff --git a/src/utils.js b/src/utils.js index 7c7a711f0b..85d8556cb7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,13 @@ // @flow import LRU from 'lru-cache'; +import { + TREE_OPERATION_ADD, + TREE_OPERATION_REMOVE, + TREE_OPERATION_REORDER_CHILDREN, + TREE_OPERATION_UPDATE_TREE_BASE_DURATION, +} from './constants'; +import { ElementTypeRoot } from 'src/types'; import { LOCAL_STORAGE_FILTER_PREFERENCES_KEY } from './constants'; import { ComponentFilterElementType, ElementTypeHostComponent } from './types'; import { @@ -71,6 +78,99 @@ function toCodePoint(string: string) { return string.codePointAt(0); } +export function printOperationsArray(operations: Uint32Array) { + // The first two values are always rendererID and rootID + const rendererID = operations[0]; + const rootID = operations[1]; + + const logs = [`opertions for renderer:${rendererID} and root:${rootID}`]; + + let i = 2; + + // Reassemble the string table. + const stringTable = [ + null, // ID = 0 corresponds to the null string. + ]; + const stringTableSize = operations[i++]; + const stringTableEnd = i + stringTableSize; + while (i < stringTableEnd) { + const nextLength = operations[i++]; + const nextString = utfDecodeString( + (operations.slice(i, i + nextLength): any) + ); + stringTable.push(nextString); + i += nextLength; + } + + while (i < operations.length) { + const operation = operations[i]; + + switch (operation) { + case TREE_OPERATION_ADD: { + const id = ((operations[i + 1]: any): number); + const type = ((operations[i + 2]: any): ElementType); + + i += 3; + + if (type === ElementTypeRoot) { + logs.push(`Add new root node ${id}`); + + i++; // supportsProfiling + i++; // hasOwnerMetadata + } else { + const parentID = ((operations[i]: any): number); + i++; + + i++; // ownerID + + const displayNameStringID = operations[i]; + const displayName = stringTable[displayNameStringID]; + i++; + + i++; // key + + logs.push( + `Add node ${id} (${displayName || 'null'}) as child of ${parentID}` + ); + } + break; + } + case TREE_OPERATION_REMOVE: { + const removeLength = ((operations[i + 1]: any): number); + i += 2; + + for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) { + const id = ((operations[i]: any): number); + i += 1; + + logs.push(`Remove node ${id}`); + } + break; + } + case TREE_OPERATION_REORDER_CHILDREN: { + const id = ((operations[i + 1]: any): number); + const numChildren = ((operations[i + 2]: any): number); + i += 3; + const children = operations.slice(i, i + numChildren); + i += numChildren; + + logs.push(`Re-order node ${id} children ${children.join(',')}`); + break; + } + case TREE_OPERATION_UPDATE_TREE_BASE_DURATION: + // Base duration updates are only sent while profiling is in progress. + // We can ignore them at this point. + // The profiler UI uses them lazily in order to generate the tree. + i += 3; + break; + default: + throw Error(`Unsupported Bridge operation ${operation}`); + } + } + + console.log(logs.join('\n ')); +} + export function getDefaultComponentFilters(): Array { return [ { diff --git a/yarn.lock b/yarn.lock index 0f838a637f..8b7b830466 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3646,6 +3646,15 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-class@^15.6.0: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-env@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2" @@ -5243,6 +5252,19 @@ fbjs@0.5.1: ua-parser-js "^0.7.9" whatwg-fetch "^0.9.0" +fbjs@^0.8.9: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -6162,6 +6184,17 @@ http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-errors@~1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + http-parser-js@>=0.4.0: version "0.5.0" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8" @@ -6751,7 +6784,7 @@ is-retry-allowed@^1.0.0: resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= -is-stream@^1.0.0, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -6826,6 +6859,14 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -7989,7 +8030,7 @@ loglevel@^1.6.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -8262,6 +8303,11 @@ mime@1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + mime@^2.3.1: version "2.4.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.2.tgz#ce5229a5e99ffc313abac806b482c10e7ba6ac78" @@ -8395,7 +8441,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.1.1: +ms@2.1.1, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== @@ -8536,6 +8582,14 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-forge@0.7.5: version "0.7.5" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" @@ -9155,7 +9209,7 @@ parse5@^3.0.1: dependencies: "@types/node" "*" -parseurl@~1.3.2: +parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -9517,6 +9571,13 @@ promise@^7.0.3: dependencies: asap "~2.0.3" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + prompts@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.4.tgz#179f9d4db3128b9933aa35f93a800d8fce76a682" @@ -9683,6 +9744,11 @@ range-parser@^1.0.3, range-parser@~1.2.0: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + raw-body@2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" @@ -9703,6 +9769,17 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +"react-15@npm:react@^15": + version "15.6.2" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72" + integrity sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI= + dependencies: + create-react-class "^15.6.0" + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + react-color@^2.11.7: version "2.17.0" resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.17.0.tgz#e14b8a11f4e89163f65a34c8b43faf93f7f02aaa" @@ -9715,6 +9792,16 @@ react-color@^2.11.7: reactcss "^1.2.0" tinycolor2 "^1.4.1" +"react-dom-15@npm:react-dom@^15": + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730" + integrity sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA= + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + react-dom@^0.0.0-50b50c26f: version "0.0.0-50b50c26f" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-50b50c26f.tgz#3cd8da0f2276ed4b7a926e1807d2675b2eb40227" @@ -10497,6 +10584,25 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + serialize-javascript@^1.4.0: version "1.6.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" @@ -10525,6 +10631,16 @@ serve-static@1.13.2: parseurl "~1.3.2" send "0.16.2" +serve-static@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -10550,7 +10666,7 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -10560,6 +10676,11 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + sha.js@2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba" @@ -10962,7 +11083,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2": +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -11512,6 +11633,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + tosource@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/tosource/-/tosource-1.0.0.tgz#42d88dd116618bcf00d6106dd5446f3427902ff1" @@ -11613,7 +11739,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -ua-parser-js@^0.7.9: +ua-parser-js@^0.7.18, ua-parser-js@^0.7.9: version "0.7.19" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== @@ -12113,6 +12239,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: dependencies: iconv-lite "0.4.19" +whatwg-fetch@>=0.10.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + whatwg-fetch@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0"