Compare commits
2 Commits
v16.9.0
...
top-setnat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5e5f2a350 | ||
|
|
ef52e22e89 |
@@ -1,262 +1,48 @@
|
||||
version: 2
|
||||
|
||||
aliases:
|
||||
- &docker
|
||||
- image: circleci/openjdk:8-jdk-node-browsers
|
||||
|
||||
- &environment
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
- &restore_yarn_cache
|
||||
restore_cache:
|
||||
name: Restore node_modules cache
|
||||
keys:
|
||||
- v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
- v1-node-{{ arch }}-{{ .Branch }}-
|
||||
- v1-node-{{ arch }}-
|
||||
- &run_yarn
|
||||
run:
|
||||
name: Install Packages
|
||||
command: yarn --frozen-lockfile
|
||||
|
||||
- &attach_workspace
|
||||
at: build
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
build:
|
||||
|
||||
docker:
|
||||
- image: circleci/openjdk:8-jdk-node-browsers
|
||||
|
||||
environment:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
TRAVIS_REPO_SLUG: facebook/react
|
||||
|
||||
parallelism: 4
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: echo $CIRCLE_COMPARE_URL | cut -d/ -f7
|
||||
|
||||
- restore_cache:
|
||||
name: Restore node_modules cache
|
||||
keys:
|
||||
- v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
- v1-node-{{ arch }}-{{ .Branch }}-
|
||||
- v1-node-{{ arch }}-
|
||||
|
||||
- run:
|
||||
name: Nodejs Version
|
||||
command: node --version
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
|
||||
- run:
|
||||
name: Install Packages
|
||||
command: yarn install --frozen-lockfile
|
||||
|
||||
- run:
|
||||
name: Test Packages
|
||||
command: ./scripts/circleci/test_entry_point.sh
|
||||
|
||||
- save_cache:
|
||||
name: Save node_modules cache
|
||||
key: v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
lint:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: node ./scripts/prettier/index
|
||||
- run: node ./scripts/tasks/eslint
|
||||
- run: ./scripts/circleci/check_license.sh
|
||||
- run: ./scripts/circleci/check_modules.sh
|
||||
- run: ./scripts/circleci/test_print_warnings.sh
|
||||
|
||||
flow:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: node ./scripts/tasks/flow-ci
|
||||
|
||||
test_source:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test --maxWorkers=2
|
||||
|
||||
test_source_persistent:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test-persistent --maxWorkers=2
|
||||
|
||||
test_source_prod:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test-prod --maxWorkers=2
|
||||
|
||||
test_coverage:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: ./scripts/circleci/test_coverage.sh
|
||||
|
||||
build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: 20
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: ./scripts/circleci/add_build_info_json.sh
|
||||
- run: ./scripts/circleci/update_package_versions.sh
|
||||
- run: yarn build
|
||||
- persist_to_workspace:
|
||||
root: build
|
||||
paths:
|
||||
- facebook-www
|
||||
- node_modules
|
||||
- react-native
|
||||
- dist
|
||||
- sizes/*.json
|
||||
|
||||
process_artifacts:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: node ./scripts/rollup/consolidateBundleSizes.js
|
||||
- run: node ./scripts/tasks/danger
|
||||
- run: ./scripts/circleci/upload_build.sh
|
||||
- run: ./scripts/circleci/pack_and_store_artifact.sh
|
||||
- store_artifacts:
|
||||
path: ./node_modules.tgz
|
||||
|
||||
- store_artifacts:
|
||||
path: ./build.tgz
|
||||
- store_artifacts:
|
||||
path: ./build/bundle-sizes.json
|
||||
- store_artifacts:
|
||||
# TODO: Update release script to use local file instead of pulling
|
||||
# from artifacts.
|
||||
path: ./scripts/error-codes/codes.json
|
||||
|
||||
lint_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn lint-build
|
||||
- run: scripts/circleci/check_minified_errors.sh
|
||||
|
||||
test_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test-build --maxWorkers=2
|
||||
|
||||
test_dom_fixtures:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- run:
|
||||
name: Run DOM fixture tests
|
||||
command: |
|
||||
cd fixtures/dom
|
||||
yarn --frozen-lockfile
|
||||
yarn prestart
|
||||
yarn test --maxWorkers=2
|
||||
|
||||
test_fuzz:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run:
|
||||
name: Run fuzz tests
|
||||
command: |
|
||||
FUZZ_TEST_SEED=$RANDOM yarn test fuzz --maxWorkers=2
|
||||
FUZZ_TEST_SEED=$RANDOM yarn test-prod fuzz --maxWorkers=2
|
||||
|
||||
test_build_prod:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test-build-prod --maxWorkers=2
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
commit:
|
||||
jobs:
|
||||
- setup
|
||||
- lint:
|
||||
requires:
|
||||
- setup
|
||||
- flow:
|
||||
requires:
|
||||
- setup
|
||||
- test_source:
|
||||
requires:
|
||||
- setup
|
||||
- test_source_prod:
|
||||
requires:
|
||||
- setup
|
||||
- test_source_persistent:
|
||||
requires:
|
||||
- setup
|
||||
- build:
|
||||
requires:
|
||||
- setup
|
||||
- process_artifacts:
|
||||
requires:
|
||||
- build
|
||||
- lint_build:
|
||||
requires:
|
||||
- build
|
||||
- test_build:
|
||||
requires:
|
||||
- build
|
||||
- test_build_prod:
|
||||
requires:
|
||||
- build
|
||||
- test_dom_fixtures:
|
||||
requires:
|
||||
- build
|
||||
hourly:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 * * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
jobs:
|
||||
- setup
|
||||
- test_fuzz:
|
||||
requires:
|
||||
- setup
|
||||
- test_coverage:
|
||||
requires:
|
||||
- setup
|
||||
path: ./scripts/error-codes/codes.json
|
||||
@@ -125,12 +125,6 @@ module.exports = {
|
||||
// https://github.com/jest-community/eslint-plugin-jest
|
||||
'jest/no-focused-tests': ERROR,
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['packages/react-native-renderer/**/*.js'],
|
||||
globals: {
|
||||
nativeFabricUIManager: true,
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
|
||||
47
CHANGELOG.md
@@ -6,53 +6,6 @@
|
||||
</summary>
|
||||
</details>
|
||||
|
||||
## 16.8.6 (March 27, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix an incorrect bailout in `useReducer()`. ([@acdlite](https://github.com/acdlite) in [#15124](https://github.com/facebook/react/pull/15124))
|
||||
* Fix iframe warnings in Safari DevTools. ([@renanvalentin](https://github.com/renanvalentin) in [#15099](https://github.com/facebook/react/pull/15099))
|
||||
* Warn if `contextType` is set to `Context.Consumer` instead of `Context`. ([@aweary](https://github.com/aweary) in [#14831](https://github.com/facebook/react/pull/14831))
|
||||
* Warn if `contextType` is set to invalid values. ([@gaearon](https://github.com/gaearon) in [#15142](https://github.com/facebook/react/pull/15142))
|
||||
|
||||
## 16.8.5 (March 22, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Don't set the first option as selected in select tag with `size` attribute. ([@kulek1](https://github.com/kulek1) in [#14242](https://github.com/facebook/react/pull/14242))
|
||||
* Improve the `useEffect(async () => ...)` warning message. ([@gaearon](https://github.com/gaearon) in [#15118](https://github.com/facebook/react/pull/15118))
|
||||
* Improve the error message sometimes caused by duplicate React. ([@jaredpalmer](https://github.com/jaredpalmer) in [#15139](https://github.com/facebook/react/pull/15139))
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Improve the `useLayoutEffect` warning message when server rendering. ([@gaearon](https://github.com/gaearon) in [#15158](https://github.com/facebook/react/pull/15158))
|
||||
|
||||
### React Shallow Renderer
|
||||
|
||||
* Fix `setState` in shallow renderer to work with Hooks. ([@gaearon](https://github.com/gaearon) in [#15120](https://github.com/facebook/react/pull/15120))
|
||||
* Fix shallow renderer to support `React.memo`. ([@aweary](https://github.com/aweary) in [#14816](https://github.com/facebook/react/pull/14816))
|
||||
* Fix shallow renderer to support Hooks inside `forwardRef`. ([@eps1lon](https://github.com/eps1lon) in [#15100](https://github.com/facebook/react/pull/15100))
|
||||
|
||||
## 16.8.4 (March 5, 2019)
|
||||
|
||||
### React DOM and other renderers
|
||||
|
||||
- Fix a bug where DevTools caused a runtime error when inspecting a component that used a `useContext` hook. ([@bvaughn](https://github.com/bvaughn) in [#14940](https://github.com/facebook/react/pull/14940))
|
||||
|
||||
## 16.8.3 (February 21, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix a bug that caused inputs to behave incorrectly in UMD builds. ([@gaearon](https://github.com/gaearon) in [#14914](https://github.com/facebook/react/pull/14914))
|
||||
* Fix a bug that caused render phase updates to be discarded. ([@gaearon](https://github.com/gaearon) in [#14852](https://github.com/facebook/react/pull/14852))
|
||||
|
||||
### React DOM Server
|
||||
* Unwind the context stack when a stream is destroyed without completing, to prevent incorrect values during a subsequent render. ([@overlookmotel](https://github.com/overlookmotel) in [#14706](https://github.com/facebook/react/pull/14706/))
|
||||
|
||||
### ESLint Plugin for React Hooks
|
||||
|
||||
* Add a new recommended `exhaustive-deps` rule. ([@gaearon](https://github.com/gaearon) in [#14636](https://github.com/facebook/react/pull/14636))
|
||||
|
||||
## 16.8.2 (February 14, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# [React](https://reactjs.org/) · [](https://github.com/facebook/react/blob/master/LICENSE) [](https://www.npmjs.com/package/react) [](https://circleci.com/gh/facebook/react) [](https://reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
||||
# [React](https://reactjs.org/) · [](https://github.com/facebook/react/blob/master/LICENSE) [](https://www.npmjs.com/package/react) [](https://coveralls.io/github/facebook/react?branch=master) [](https://circleci.com/gh/facebook/react) [](https://reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
||||
|
||||
React is a JavaScript library for building user interfaces.
|
||||
|
||||
|
||||
@@ -25,26 +25,22 @@
|
||||
//
|
||||
// `DANGER_GITHUB_API_TOKEN=[ENV_ABOVE] yarn danger pr https://github.com/facebook/react/pull/11865
|
||||
|
||||
const {markdown, danger, warn} = require('danger');
|
||||
const {markdown, danger} = require('danger');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const {generateResultsArray} = require('./scripts/rollup/stats');
|
||||
const {existsSync, readFileSync} = require('fs');
|
||||
const {exec} = require('child_process');
|
||||
|
||||
if (!existsSync('./build/bundle-sizes.json')) {
|
||||
if (!existsSync('./scripts/rollup/results.json')) {
|
||||
// This indicates the build failed previously.
|
||||
// In that case, there's nothing for the Dangerfile to do.
|
||||
// Exit early to avoid leaving a redundant (and potentially confusing) PR comment.
|
||||
warn(
|
||||
'No bundle size information found. This indicates the build ' +
|
||||
'job failed.'
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const currentBuildResults = JSON.parse(
|
||||
readFileSync('./build/bundle-sizes.json')
|
||||
readFileSync('./scripts/rollup/results.json')
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -112,74 +108,18 @@ function git(args) {
|
||||
// Use git locally to grab the commit which represents the place
|
||||
// where the branches differ
|
||||
const upstreamRepo = danger.github.pr.base.repo.full_name;
|
||||
if (upstreamRepo !== 'facebook/react') {
|
||||
// Exit unless we're running in the main repo
|
||||
return;
|
||||
}
|
||||
|
||||
const upstreamRef = danger.github.pr.base.ref;
|
||||
await git(`remote add upstream https://github.com/facebook/react.git`);
|
||||
await git(`remote add upstream https://github.com/${upstreamRepo}.git`);
|
||||
await git('fetch upstream');
|
||||
const baseCommit = await git(`merge-base HEAD upstream/${upstreamRef}`);
|
||||
const mergeBaseCommit = await git(`merge-base HEAD upstream/${upstreamRef}`);
|
||||
|
||||
let previousBuildResults = null;
|
||||
try {
|
||||
let baseCIBuildId = null;
|
||||
const statusesResponse = await fetch(
|
||||
`https://api.github.com/repos/facebook/react/commits/${baseCommit}/status`
|
||||
);
|
||||
const {statuses, state} = await statusesResponse.json();
|
||||
if (state === 'failure') {
|
||||
warn(`Base commit is broken: ${baseCommit}`);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < statuses.length; i++) {
|
||||
const status = statuses[i];
|
||||
// This must match the name of the CI job that creates the build artifacts
|
||||
if (status.context === 'ci/circleci: process_artifacts') {
|
||||
if (status.state === 'success') {
|
||||
baseCIBuildId = /\/facebook\/react\/([0-9]+)/.exec(
|
||||
status.target_url
|
||||
)[1];
|
||||
break;
|
||||
}
|
||||
if (status.state === 'pending') {
|
||||
warn(`Build job for base commit is still pending: ${baseCommit}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (baseCIBuildId === null) {
|
||||
warn(`Could not find build artifacts for base commit: ${baseCommit}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const baseArtifactsInfoResponse = await fetch(
|
||||
`https://circleci.com/api/v1.1/project/github/facebook/react/${baseCIBuildId}/artifacts`
|
||||
);
|
||||
const baseArtifactsInfo = await baseArtifactsInfoResponse.json();
|
||||
|
||||
for (let i = 0; i < baseArtifactsInfo.length; i++) {
|
||||
const info = baseArtifactsInfo[i];
|
||||
if (info.path === 'home/circleci/project/build/bundle-sizes.json') {
|
||||
const resultsResponse = await fetch(info.url);
|
||||
previousBuildResults = await resultsResponse.json();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
warn(`Failed to fetch build artifacts for base commit: ${baseCommit}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousBuildResults === null) {
|
||||
warn(`Could not find build artifacts for base commit: ${baseCommit}`);
|
||||
return;
|
||||
}
|
||||
const commitURL = sha =>
|
||||
`http://react.zpao.com/builds/master/_commits/${sha}/results.json`;
|
||||
const response = await fetch(commitURL(mergeBaseCommit));
|
||||
|
||||
// Take the JSON of the build response and
|
||||
// make an array comparing the results for printing
|
||||
const previousBuildResults = await response.json();
|
||||
const results = generateResultsArray(
|
||||
currentBuildResults,
|
||||
previousBuildResults
|
||||
@@ -272,7 +212,7 @@ function git(args) {
|
||||
<details>
|
||||
<summary>Details of bundled changes.</summary>
|
||||
|
||||
<p>Comparing: ${baseCommit}...${danger.github.pr.head.sha}</p>
|
||||
<p>Comparing: ${mergeBaseCommit}...${danger.github.pr.head.sha}</p>
|
||||
|
||||
|
||||
${allTables.join('\n')}
|
||||
@@ -280,7 +220,5 @@ function git(args) {
|
||||
</details>
|
||||
`;
|
||||
markdown(summary);
|
||||
} else {
|
||||
markdown('No significant bundle size changes to report.');
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -448,11 +448,6 @@ const attributes = [
|
||||
read: getSVGAttribute('direction'),
|
||||
},
|
||||
{name: 'disabled', tagName: 'input'},
|
||||
{
|
||||
name: 'disablePictureInPicture',
|
||||
tagName: 'video',
|
||||
read: getProperty('disablepictureinpicture'),
|
||||
},
|
||||
{
|
||||
name: 'display',
|
||||
tagName: 'svg',
|
||||
|
||||
4
fixtures/dom/.gitignore
vendored
@@ -8,16 +8,12 @@ coverage
|
||||
|
||||
# production
|
||||
build
|
||||
public/scheduler-unstable_mock.development.js
|
||||
public/scheduler-unstable_mock.production.min.js
|
||||
public/react.development.js
|
||||
public/react.production.min.js
|
||||
public/react-dom.development.js
|
||||
public/react-dom.production.min.js
|
||||
public/react-dom-server.browser.development.js
|
||||
public/react-dom-server.browser.production.min.js
|
||||
public/react-dom-test-utils.development.js
|
||||
public/react-dom-test-utils.production.min.js
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/standalone": "^7.0.0",
|
||||
"art": "^0.10.3",
|
||||
"classnames": "^2.2.5",
|
||||
"codemirror": "^5.40.0",
|
||||
"core-js": "^2.4.1",
|
||||
"jest-diff": "^24.8.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"query-string": "^4.2.3",
|
||||
"react": "^15.4.1",
|
||||
@@ -20,7 +18,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"prestart": "cp ../../build/node_modules/scheduler/umd/scheduler-unstable_mock.development.js ../../build/node_modules/scheduler/umd/scheduler-unstable_mock.production.min.js ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.development.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.production.min.js public/ && cp -a ../../build/node_modules/. node_modules",
|
||||
"prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js public/",
|
||||
"build": "react-scripts build && cp build/index.html build/200.html",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>sanity test for ReactTestUtils.act</title>
|
||||
</head>
|
||||
<body>
|
||||
this page tests whether act runs properly in a browser.
|
||||
<br />
|
||||
your console should say "5"
|
||||
<script src="scheduler-unstable_mock.development.js"></script>
|
||||
<script src="react.development.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler =
|
||||
window.SchedulerMock;
|
||||
</script>
|
||||
<script src="react-dom.development.js"></script>
|
||||
<script src="react-dom-test-utils.development.js"></script>
|
||||
<script>
|
||||
// from ReactTestUtilsAct-test.js
|
||||
function App() {
|
||||
let [state, setState] = React.useState(0);
|
||||
async function ticker() {
|
||||
await null;
|
||||
setState(x => x + 1);
|
||||
}
|
||||
React.useEffect(() => {
|
||||
ticker();
|
||||
}, [Math.min(state, 4)]);
|
||||
return state;
|
||||
}
|
||||
|
||||
async function testAsyncAct() {
|
||||
const el = document.createElement("div");
|
||||
await ReactTestUtils.act(async () => {
|
||||
ReactDOM.render(React.createElement(App), el);
|
||||
});
|
||||
// all 5 ticks present and accounted for
|
||||
console.log(el.innerHTML);
|
||||
}
|
||||
|
||||
testAsyncAct();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,30 +13,6 @@
|
||||
var renders = 0;
|
||||
var failed = false;
|
||||
|
||||
var needsReactDOM = getBooleanQueryParam('needsReactDOM');
|
||||
var needsCreateElement = getBooleanQueryParam('needsCreateElement');
|
||||
|
||||
function unmountComponent(node) {
|
||||
// ReactDOM was moved into a separate package in 0.14
|
||||
if (needsReactDOM) {
|
||||
ReactDOM.unmountComponentAtNode(node);
|
||||
} else if (React.unmountComponentAtNode) {
|
||||
React.unmountComponentAtNode(node);
|
||||
} else {
|
||||
// Unmounting for React 0.4 and lower
|
||||
React.unmountAndReleaseReactRootNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
function createElement(value) {
|
||||
// React.createElement replaced function invocation in 0.12
|
||||
if (needsCreateElement) {
|
||||
return React.createElement(value);
|
||||
} else {
|
||||
return value();
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryParam(key) {
|
||||
var pattern = new RegExp(key + '=([^&]+)(&|$)');
|
||||
var matches = window.location.search.match(pattern);
|
||||
@@ -59,56 +35,20 @@
|
||||
function prerender() {
|
||||
setStatus('Generating markup');
|
||||
|
||||
return Promise.resolve()
|
||||
.then(function() {
|
||||
const element = createElement(Fixture);
|
||||
output.innerHTML = ReactDOMServer.renderToString(
|
||||
React.createElement(Fixture)
|
||||
);
|
||||
|
||||
// Server rendering moved to a separate package along with ReactDOM
|
||||
// in 0.14.0
|
||||
if (needsReactDOM) {
|
||||
return ReactDOMServer.renderToString(element);
|
||||
}
|
||||
|
||||
// React.renderComponentToString was renamed in 0.12
|
||||
if (React.renderToString) {
|
||||
return React.renderToString(element);
|
||||
}
|
||||
|
||||
// React.renderComponentToString became synchronous in React 0.9.0
|
||||
if (React.renderComponentToString.length === 1) {
|
||||
return React.renderComponentToString(element);
|
||||
}
|
||||
|
||||
// Finally, React 0.4 and lower emits markup in a callback
|
||||
return new Promise(function(resolve) {
|
||||
React.renderComponentToString(element, resolve);
|
||||
});
|
||||
})
|
||||
.then(function(string) {
|
||||
output.innerHTML = string;
|
||||
setStatus('Markup only (No React)');
|
||||
})
|
||||
.catch(handleError);
|
||||
setStatus('Markup only (No React)');
|
||||
}
|
||||
|
||||
function render() {
|
||||
setStatus('Hydrating');
|
||||
|
||||
var element = createElement(Fixture);
|
||||
|
||||
// ReactDOM was split out into another package in 0.14
|
||||
if (needsReactDOM) {
|
||||
// Hydration changed to a separate method in React 16
|
||||
if (ReactDOM.hydrate) {
|
||||
ReactDOM.hydrate(element, output);
|
||||
} else {
|
||||
ReactDOM.render(element, output);
|
||||
}
|
||||
} else if (React.render) {
|
||||
// React.renderComponent was renamed in 0.12
|
||||
React.render(element, output);
|
||||
if (ReactDOM.hydrate) {
|
||||
ReactDOM.hydrate(React.createElement(Fixture), output);
|
||||
} else {
|
||||
React.renderComponent(element, output);
|
||||
ReactDOM.render(React.createElement(Fixture), output);
|
||||
}
|
||||
|
||||
setStatus(renders > 0 ? 'Re-rendered (' + renders + 'x)' : 'Hydrated');
|
||||
@@ -145,17 +85,17 @@
|
||||
setStatus('Failed');
|
||||
output.innerHTML = 'Please name your root component "Fixture"';
|
||||
} else {
|
||||
prerender().then(function() {
|
||||
if (getBooleanQueryParam('hydrate')) {
|
||||
render();
|
||||
}
|
||||
});
|
||||
prerender();
|
||||
|
||||
if (getBooleanQueryParam('hydrate')) {
|
||||
render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reloadFixture(code) {
|
||||
renders = 0;
|
||||
unmountComponent(output);
|
||||
ReactDOM.unmountComponentAtNode(output);
|
||||
injectFixture(code);
|
||||
}
|
||||
|
||||
@@ -169,12 +109,12 @@
|
||||
|
||||
loadScript(getQueryParam('reactPath'))
|
||||
.then(function() {
|
||||
if (needsReactDOM) {
|
||||
return Promise.all([
|
||||
loadScript(getQueryParam('reactDOMPath')),
|
||||
loadScript(getQueryParam('reactDOMServerPath')),
|
||||
]);
|
||||
}
|
||||
return getBooleanQueryParam('needsReactDOM')
|
||||
? loadScript(getQueryParam('reactDOMPath'))
|
||||
: null;
|
||||
})
|
||||
.then(function() {
|
||||
return loadScript(getQueryParam('reactDOMServerPath'));
|
||||
})
|
||||
.then(function() {
|
||||
if (failed) {
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
let React;
|
||||
let TestUtils;
|
||||
let TestRenderer;
|
||||
|
||||
global.__DEV__ = process.env.NODE_ENV !== 'production';
|
||||
|
||||
expect.extend(require('../toWarnDev'));
|
||||
|
||||
describe('unmocked scheduler', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
TestUtils = require('react-dom/test-utils');
|
||||
TestRenderer = require('react-test-renderer');
|
||||
});
|
||||
|
||||
it('flushes work only outside the outermost act() corresponding to its own renderer', () => {
|
||||
let log = [];
|
||||
function Effecty() {
|
||||
React.useEffect(() => {
|
||||
log.push('called');
|
||||
}, []);
|
||||
return null;
|
||||
}
|
||||
// in legacy mode, this tests whether an act only flushes its own effects
|
||||
TestRenderer.act(() => {
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.create(<Effecty />);
|
||||
});
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
expect(log).toEqual(['called']);
|
||||
|
||||
log = [];
|
||||
// for doublechecking, we flip it inside out, and assert on the outermost
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.act(() => {
|
||||
TestRenderer.create(<Effecty />);
|
||||
});
|
||||
expect(log).toEqual(['called']);
|
||||
});
|
||||
expect(log).toEqual(['called']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mocked scheduler', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.mock('scheduler', () =>
|
||||
require.requireActual('scheduler/unstable_mock')
|
||||
);
|
||||
React = require('react');
|
||||
TestUtils = require('react-dom/test-utils');
|
||||
TestRenderer = require('react-test-renderer');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.unmock('scheduler');
|
||||
});
|
||||
|
||||
it('flushes work only outside the outermost act()', () => {
|
||||
let log = [];
|
||||
function Effecty() {
|
||||
React.useEffect(() => {
|
||||
log.push('called');
|
||||
}, []);
|
||||
return null;
|
||||
}
|
||||
// with a mocked scheduler, this tests whether it flushes all work only on the outermost act
|
||||
TestRenderer.act(() => {
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.create(<Effecty />);
|
||||
});
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
expect(log).toEqual(['called']);
|
||||
|
||||
log = [];
|
||||
// for doublechecking, we flip it inside out, and assert on the outermost
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.act(() => {
|
||||
TestRenderer.create(<Effecty />);
|
||||
});
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
expect(log).toEqual(['called']);
|
||||
});
|
||||
});
|
||||
@@ -1,194 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactART;
|
||||
let ARTSVGMode;
|
||||
let ARTCurrentMode;
|
||||
let TestUtils;
|
||||
let TestRenderer;
|
||||
let ARTTest;
|
||||
|
||||
global.__DEV__ = process.env.NODE_ENV !== 'production';
|
||||
|
||||
expect.extend(require('../toWarnDev'));
|
||||
|
||||
function App(props) {
|
||||
return 'hello world';
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactART = require('react-art');
|
||||
ARTSVGMode = require('art/modes/svg');
|
||||
ARTCurrentMode = require('art/modes/current');
|
||||
TestUtils = require('react-dom/test-utils');
|
||||
TestRenderer = require('react-test-renderer');
|
||||
|
||||
ARTCurrentMode.setCurrent(ARTSVGMode);
|
||||
|
||||
ARTTest = function ARTTestComponent(props) {
|
||||
return (
|
||||
<ReactART.Surface width={150} height={200}>
|
||||
<ReactART.Group>
|
||||
<ReactART.Shape
|
||||
d="M0,0l50,0l0,50l-50,0z"
|
||||
fill={new ReactART.LinearGradient(['black', 'white'])}
|
||||
key="a"
|
||||
width={50}
|
||||
height={50}
|
||||
x={50}
|
||||
y={50}
|
||||
opacity={0.1}
|
||||
/>
|
||||
<ReactART.Shape
|
||||
fill="#3C5A99"
|
||||
key="b"
|
||||
scale={0.5}
|
||||
x={50}
|
||||
y={50}
|
||||
title="This is an F"
|
||||
cursor="pointer">
|
||||
M64.564,38.583H54l0.008-5.834c0-3.035,0.293-4.666,4.657-4.666
|
||||
h5.833V16.429h-9.33c-11.213,0-15.159,5.654-15.159,15.16v6.994
|
||||
h-6.99v11.652h6.99v33.815H54V50.235h9.331L64.564,38.583z
|
||||
</ReactART.Shape>
|
||||
</ReactART.Group>
|
||||
</ReactART.Surface>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
it("doesn't warn when you use the right act + renderer: dom", () => {
|
||||
TestUtils.act(() => {
|
||||
TestUtils.renderIntoDocument(<App />);
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't warn when you use the right act + renderer: test", () => {
|
||||
TestRenderer.act(() => {
|
||||
TestRenderer.create(<App />);
|
||||
});
|
||||
});
|
||||
|
||||
it('resets correctly across renderers', () => {
|
||||
function Effecty() {
|
||||
React.useEffect(() => {}, []);
|
||||
return null;
|
||||
}
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.act(() => {});
|
||||
expect(() => {
|
||||
TestRenderer.create(<Effecty />);
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"], {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when using the wrong act version - test + dom: render', () => {
|
||||
expect(() => {
|
||||
TestRenderer.act(() => {
|
||||
TestUtils.renderIntoDocument(<App />);
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"], {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when using the wrong act version - test + dom: updates', () => {
|
||||
let setCtr;
|
||||
function Counter(props) {
|
||||
const [ctr, _setCtr] = React.useState(0);
|
||||
setCtr = _setCtr;
|
||||
return ctr;
|
||||
}
|
||||
TestUtils.renderIntoDocument(<Counter />);
|
||||
expect(() => {
|
||||
TestRenderer.act(() => {
|
||||
setCtr(1);
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"]);
|
||||
});
|
||||
|
||||
it('warns when using the wrong act version - dom + test: .create()', () => {
|
||||
expect(() => {
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.create(<App />);
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"], {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when using the wrong act version - dom + test: .update()', () => {
|
||||
const root = TestRenderer.create(<App key="one" />);
|
||||
expect(() => {
|
||||
TestUtils.act(() => {
|
||||
root.update(<App key="two" />);
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"], {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when using the wrong act version - dom + test: updates', () => {
|
||||
let setCtr;
|
||||
function Counter(props) {
|
||||
const [ctr, _setCtr] = React.useState(0);
|
||||
setCtr = _setCtr;
|
||||
return ctr;
|
||||
}
|
||||
TestRenderer.create(<Counter />);
|
||||
expect(() => {
|
||||
TestUtils.act(() => {
|
||||
setCtr(1);
|
||||
});
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"]);
|
||||
});
|
||||
|
||||
it('does not warn when nesting react-act inside react-dom', () => {
|
||||
TestUtils.act(() => {
|
||||
TestUtils.renderIntoDocument(<ARTTest />);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not warn when nesting react-act inside react-test-renderer', () => {
|
||||
TestRenderer.act(() => {
|
||||
TestRenderer.create(<ARTTest />);
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't warn if you use nested acts from different renderers", () => {
|
||||
TestRenderer.act(() => {
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.create(<App />);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when using createRoot() + .render', () => {
|
||||
const root = ReactDOM.unstable_createRoot(document.createElement('div'));
|
||||
expect(() => {
|
||||
TestRenderer.act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
}).toWarnDev(
|
||||
[
|
||||
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked',
|
||||
"It looks like you're using the wrong act()",
|
||||
],
|
||||
{
|
||||
withoutStack: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -4,15 +4,13 @@ import '../style.css';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<Fixtures />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<Fixtures />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {parse, stringify} from 'query-string';
|
||||
import VersionPicker from './VersionPicker';
|
||||
|
||||
import getVersionTags from '../tags';
|
||||
const React = window.React;
|
||||
|
||||
class Header extends React.Component {
|
||||
@@ -10,12 +9,18 @@ class Header extends React.Component {
|
||||
const version = query.version || 'local';
|
||||
const production = query.production || false;
|
||||
const versions = [version];
|
||||
|
||||
this.state = {version, versions, production};
|
||||
}
|
||||
handleVersionChange(version) {
|
||||
componentWillMount() {
|
||||
getVersionTags().then(tags => {
|
||||
let versions = tags.map(tag => tag.name.slice(1));
|
||||
versions = [`local`, ...versions];
|
||||
this.setState({versions});
|
||||
});
|
||||
}
|
||||
handleVersionChange(event) {
|
||||
const query = parse(window.location.search);
|
||||
query.version = version;
|
||||
query.version = event.target.value;
|
||||
if (query.version === 'local') {
|
||||
delete query.version;
|
||||
}
|
||||
@@ -43,10 +48,7 @@ class Header extends React.Component {
|
||||
width="20"
|
||||
height="20"
|
||||
/>
|
||||
<a href="/">
|
||||
DOM Test Fixtures (v
|
||||
{React.version})
|
||||
</a>
|
||||
<a href="/">DOM Test Fixtures (v{React.version})</a>
|
||||
</span>
|
||||
|
||||
<div className="header-controls">
|
||||
@@ -88,14 +90,17 @@ class Header extends React.Component {
|
||||
<option value="/suspense">Suspense</option>
|
||||
</select>
|
||||
</label>
|
||||
<label htmlFor="global_version">
|
||||
<label htmlFor="react_version">
|
||||
<span className="sr-only">Select a version to test</span>
|
||||
<VersionPicker
|
||||
id="global_version"
|
||||
name="global_version"
|
||||
version={this.state.version}
|
||||
onChange={this.handleVersionChange}
|
||||
/>
|
||||
<select
|
||||
value={this.state.version}
|
||||
onChange={this.handleVersionChange}>
|
||||
{this.state.versions.map(version => (
|
||||
<option key={version} value={version}>
|
||||
{version}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import getVersionTags from '../tags';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class VersionPicker extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
const version = props.version || 'local';
|
||||
const versions = [version];
|
||||
this.state = {versions};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
getVersionTags().then(tags => {
|
||||
let versions = tags.map(tag => tag.name.slice(1));
|
||||
versions = [`local`, ...versions];
|
||||
this.setState({versions});
|
||||
});
|
||||
}
|
||||
|
||||
onChange = event => {
|
||||
this.props.onChange(event.target.value);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {version, id, name} = this.props;
|
||||
const {versions} = this.state;
|
||||
|
||||
return (
|
||||
<select id={id} name={name} value={version} onChange={this.onChange}>
|
||||
{versions.map(version => (
|
||||
<option key={version} value={version}>
|
||||
{version}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default VersionPicker;
|
||||
@@ -1,5 +1,3 @@
|
||||
import {findDOMNode} from '../../../find-dom-node';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
export class CodeEditor extends React.Component {
|
||||
@@ -8,8 +6,6 @@ export class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.textarea = findDOMNode(this);
|
||||
|
||||
// Important: CodeMirror incorrectly lays out the editor
|
||||
// if it executes before CSS has loaded
|
||||
// https://github.com/graphql/graphiql/issues/33#issuecomment-318188555
|
||||
@@ -48,6 +44,7 @@ export class CodeEditor extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<textarea
|
||||
ref={ref => (this.textarea = ref)}
|
||||
defaultValue={this.props.code}
|
||||
autoComplete="off"
|
||||
hidden={true}
|
||||
@@ -75,10 +72,6 @@ export class CodeError extends React.Component {
|
||||
if (supportsDetails) {
|
||||
const [summary, ...body] = error.message.split(/\n+/g);
|
||||
|
||||
if (body.length >= 0) {
|
||||
return <div className={className}>{summary}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<details className={className}>
|
||||
<summary>{summary}</summary>
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
.hydration-options label {
|
||||
font-size: 13px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.hydration-options input[type=checkbox] {
|
||||
@@ -31,11 +30,6 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.hydration-options select {
|
||||
margin-left: 10px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.hydration .CodeMirror {
|
||||
font-size: 13px;
|
||||
padding-top: 8px;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import './hydration.css';
|
||||
import VersionPicker from '../../VersionPicker';
|
||||
import {SAMPLE_CODE} from './data';
|
||||
import {CodeEditor, CodeError} from './Code';
|
||||
import {compile} from './code-transformer';
|
||||
@@ -7,17 +6,12 @@ import {reactPaths} from '../../../react-loader';
|
||||
import qs from 'query-string';
|
||||
|
||||
const React = window.React;
|
||||
// The Hydration fixture can render at a different version than the parent
|
||||
// app. This allows rendering for versions of React older than the DOM
|
||||
// test fixtures can support.
|
||||
const initialVersion = qs.parse(window.location.search).version || 'local';
|
||||
|
||||
class Hydration extends React.Component {
|
||||
state = {
|
||||
error: null,
|
||||
code: SAMPLE_CODE,
|
||||
hydrate: true,
|
||||
version: initialVersion,
|
||||
};
|
||||
|
||||
ready = false;
|
||||
@@ -78,14 +72,9 @@ class Hydration extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
setVersion = version => {
|
||||
this.setState({version});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {code, error, hydrate, version} = this.state;
|
||||
const src =
|
||||
'/renderer.html?' + qs.stringify({hydrate, ...reactPaths(version)});
|
||||
const {code, error, hydrate} = this.state;
|
||||
const src = '/renderer.html?' + qs.stringify({hydrate, ...reactPaths()});
|
||||
|
||||
return (
|
||||
<div className="hydration">
|
||||
@@ -100,16 +89,6 @@ class Hydration extends React.Component {
|
||||
/>
|
||||
Auto-Hydrate
|
||||
</label>
|
||||
|
||||
<label htmlFor="hydration_version">
|
||||
Version:
|
||||
<VersionPicker
|
||||
id="hydration_version"
|
||||
name="hyration_version"
|
||||
version={version}
|
||||
onChange={this.setVersion}
|
||||
/>
|
||||
</label>
|
||||
</header>
|
||||
|
||||
<CodeEditor code={code} onChange={this.setCode} />
|
||||
|
||||
@@ -202,34 +202,6 @@ class SelectFixture extends React.Component {
|
||||
</select>
|
||||
</div>
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="A select with the size attribute should not set first option as selected"
|
||||
relatedIssues="14239"
|
||||
introducedIn="16.0.0">
|
||||
<TestCase.ExpectedResult>
|
||||
No options should be selected.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<div className="test-fixture">
|
||||
<select size="3">
|
||||
<option>0</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<p className="footnote">
|
||||
<b>Notes:</b> This happens if <code>size</code> is assigned after
|
||||
options are selected. The select element picks the first item by
|
||||
default, then it is expanded to show more options when{' '}
|
||||
<code>size</code> is assigned, preserving the default selection.
|
||||
</p>
|
||||
<p className="footnote">
|
||||
This was introduced in React 16.0.0 when options were added before
|
||||
select attribute assignment.
|
||||
</p>
|
||||
</TestCase>
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ class TextInputFixtures extends React.Component {
|
||||
the "Go" button.
|
||||
</li>
|
||||
<li>
|
||||
Instead of clicking "Go", which switches focus, press Command +
|
||||
Intead of clicking "Go", which switches focus, press Command +
|
||||
Enter (or Control + Enter on Windows, Linux).
|
||||
</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Provides a standard way to access a DOM node across all versions of
|
||||
* React.
|
||||
*/
|
||||
|
||||
import {reactPaths} from './react-loader';
|
||||
|
||||
const React = window.React;
|
||||
const ReactDOM = window.ReactDOM;
|
||||
|
||||
export function findDOMNode(target) {
|
||||
const {needsReactDOM} = reactPaths();
|
||||
|
||||
if (needsReactDOM) {
|
||||
return ReactDOM.findDOMNode(target);
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
return React.findDOMNode(target);
|
||||
}
|
||||
}
|
||||
57
fixtures/dom/src/react-loader.js
vendored
@@ -36,29 +36,19 @@ function loadScript(src) {
|
||||
});
|
||||
}
|
||||
|
||||
function getVersion() {
|
||||
let query = parseQuery(window.location.search);
|
||||
return query.version || 'local';
|
||||
}
|
||||
|
||||
export function reactPaths(version = getVersion()) {
|
||||
export function reactPaths() {
|
||||
let query = parseQuery(window.location.search);
|
||||
let version = query.version || 'local';
|
||||
let isProduction = query.production === 'true';
|
||||
|
||||
let environment = isProduction ? 'production.min' : 'development';
|
||||
let reactPath = `react.${environment}.js`;
|
||||
let reactDOMPath = `react-dom.${environment}.js`;
|
||||
let reactDOMServerPath = `react-dom-server.browser.${environment}.js`;
|
||||
let needsCreateElement = true;
|
||||
let needsReactDOM = true;
|
||||
|
||||
let reactPath = 'react.' + environment + '.js';
|
||||
let reactDOMPath = 'react-dom.' + environment + '.js';
|
||||
let reactDOMServerPath = 'react-dom-server.browser.' + environment + '.js';
|
||||
|
||||
if (version !== 'local') {
|
||||
const {major, minor, prerelease} = semver(version);
|
||||
|
||||
if (major === 0) {
|
||||
needsCreateElement = minor >= 12;
|
||||
needsReactDOM = minor >= 14;
|
||||
}
|
||||
|
||||
const [preReleaseStage] = prerelease;
|
||||
// The file structure was updated in 16. This wasn't the case for alphas.
|
||||
// Load the old module location for anything less than 16 RC
|
||||
@@ -78,27 +68,26 @@ export function reactPaths(version = getVersion()) {
|
||||
reactDOMServerPath =
|
||||
'https://unpkg.com/react-dom@' +
|
||||
version +
|
||||
'/umd/react-dom-server.browser' +
|
||||
environment;
|
||||
} else if (major > 0 || minor > 11) {
|
||||
reactPath = 'https://unpkg.com/react@' + version + '/dist/react.js';
|
||||
reactDOMPath =
|
||||
'https://unpkg.com/react-dom@' + version + '/dist/react-dom.js';
|
||||
reactDOMServerPath =
|
||||
'https://unpkg.com/react-dom@' + version + '/dist/react-dom-server.js';
|
||||
'/umd/react-dom-server.browser.' +
|
||||
environment +
|
||||
'.js';
|
||||
} else {
|
||||
reactPath =
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/react/' + version + '/react.js';
|
||||
let suffix = isProduction ? '.min.js' : '.js';
|
||||
|
||||
reactPath = 'https://unpkg.com/react@' + version + '/dist/react' + suffix;
|
||||
reactDOMPath =
|
||||
'https://unpkg.com/react-dom@' + version + '/dist/react-dom' + suffix;
|
||||
reactDOMServerPath =
|
||||
'https://unpkg.com/react-dom@' +
|
||||
version +
|
||||
'/dist/react-dom-server' +
|
||||
suffix;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
reactPath,
|
||||
reactDOMPath,
|
||||
reactDOMServerPath,
|
||||
needsCreateElement,
|
||||
needsReactDOM,
|
||||
};
|
||||
const needsReactDOM = version === 'local' || parseFloat(version, 10) > 0.13;
|
||||
|
||||
return {reactPath, reactDOMPath, reactDOMServerPath, needsReactDOM};
|
||||
}
|
||||
|
||||
export default function loadReact() {
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
// copied from scripts/jest/matchers/toWarnDev.js
|
||||
'use strict';
|
||||
|
||||
const jestDiff = require('jest-diff');
|
||||
const util = require('util');
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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'),
|
||||
};
|
||||
@@ -6,40 +6,6 @@
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.0.0.tgz#856446641620c1c5f0ca775621d478324ebd1f52"
|
||||
|
||||
"@jest/types@^24.8.0":
|
||||
version "24.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad"
|
||||
integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^12.0.9"
|
||||
|
||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
|
||||
integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==
|
||||
|
||||
"@types/istanbul-lib-report@*":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c"
|
||||
integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "*"
|
||||
|
||||
"@types/istanbul-reports@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a"
|
||||
integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "*"
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/yargs@^12.0.9":
|
||||
version "12.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
|
||||
integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
|
||||
|
||||
abab@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
|
||||
@@ -174,11 +140,6 @@ ansi-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
|
||||
ansi-regex@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
@@ -189,13 +150,6 @@ ansi-styles@^3.0.0, ansi-styles@^3.1.0:
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
anymatch@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
|
||||
@@ -295,11 +249,6 @@ arrify@^1.0.0, arrify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
||||
|
||||
art@^0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/art/-/art-0.10.3.tgz#b01d84a968ccce6208df55a733838c96caeeaea2"
|
||||
integrity sha512-HXwbdofRTiJT6qZX/FnchtldzJjS3vkLJxQilc3Xj+ma2MXjY4UAyQ0ls1XZYVnDvVIBiFZbC6QsvtW86TD6tQ==
|
||||
|
||||
asap@~2.0.3:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f"
|
||||
@@ -1517,15 +1466,6 @@ chalk@^2.0.0, chalk@^2.1.0:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^4.0.0"
|
||||
|
||||
chalk@^2.0.1:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chokidar@^1.6.0, chokidar@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
|
||||
@@ -2118,11 +2058,6 @@ detect-port-alt@1.1.3:
|
||||
address "^1.0.1"
|
||||
debug "^2.6.0"
|
||||
|
||||
diff-sequences@^24.3.0:
|
||||
version "24.3.0"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
|
||||
integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==
|
||||
|
||||
diff@^3.2.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.0.tgz#056695150d7aa93237ca7e378ac3b1682b7963b9"
|
||||
@@ -3184,11 +3119,6 @@ has-flag@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-unicode@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||
@@ -3840,16 +3770,6 @@ jest-diff@^20.0.3:
|
||||
jest-matcher-utils "^20.0.3"
|
||||
pretty-format "^20.0.3"
|
||||
|
||||
jest-diff@^24.8.0:
|
||||
version "24.8.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172"
|
||||
integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
diff-sequences "^24.3.0"
|
||||
jest-get-type "^24.8.0"
|
||||
pretty-format "^24.8.0"
|
||||
|
||||
jest-docblock@^20.0.3:
|
||||
version "20.0.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712"
|
||||
@@ -3869,11 +3789,6 @@ jest-environment-node@^20.0.3:
|
||||
jest-mock "^20.0.3"
|
||||
jest-util "^20.0.3"
|
||||
|
||||
jest-get-type@^24.8.0:
|
||||
version "24.8.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc"
|
||||
integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==
|
||||
|
||||
jest-haste-map@^20.0.4:
|
||||
version "20.0.5"
|
||||
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-20.0.5.tgz#abad74efb1a005974a7b6517e11010709cab9112"
|
||||
@@ -5328,16 +5243,6 @@ pretty-format@^20.0.3:
|
||||
ansi-regex "^2.1.1"
|
||||
ansi-styles "^3.0.0"
|
||||
|
||||
pretty-format@^24.8.0:
|
||||
version "24.8.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
|
||||
integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==
|
||||
dependencies:
|
||||
"@jest/types" "^24.8.0"
|
||||
ansi-regex "^4.0.0"
|
||||
ansi-styles "^3.2.0"
|
||||
react-is "^16.8.4"
|
||||
|
||||
private@^0.1.6:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.6.tgz#55c6a976d0f9bafb9924851350fe47b9b5fbb7c1"
|
||||
@@ -5524,11 +5429,6 @@ react-error-overlay@^1.0.10:
|
||||
settle-promise "1.0.0"
|
||||
source-map "0.5.6"
|
||||
|
||||
react-is@^16.8.4:
|
||||
version "16.8.6"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
|
||||
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
|
||||
|
||||
react-scripts@^1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-1.0.11.tgz#483d49e27f417ec981ae415a4456120a2a2bc8c1"
|
||||
@@ -6348,13 +6248,6 @@ supports-color@^4.0.0, supports-color@^4.2.1:
|
||||
dependencies:
|
||||
has-flag "^2.0.0"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
svgo@^0.7.0:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.1.tgz#287320fed972cb097e72c2bb1685f96fe08f8034"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"root": true,
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 8,
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
|
||||
@@ -105,51 +105,9 @@
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-8"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Can force a specific framerate</p>
|
||||
<p><b>IMPORTANT:</b> This test may be flaky if other tests have been run in this js instance. To get a clean test refresh the page before running test 9</p>
|
||||
<button onClick="runTestNine()">Run Test 9</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-9-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-9"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Runs scheduled JS work for 99% of the frame time when nothing else is using the thread.</p>
|
||||
<p><b>NOTE:</b> Try this test both when nothing else is running and when something is using the compositor thread in another visible tab with video or <a href="https://www.shadertoy.com/view/MtffDX">WebGL content</a> (Shift+Click).</p>
|
||||
<button onClick="runTestTen()">Run Test 10</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-10-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-10"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Runs scheduled JS work more than 95% of the frame time when inserting DOM nodes.</p>
|
||||
<p><b>NOTE:</b> Try this test both when nothing else is running and when something is using the compositor thread in another visible tab with video or <a href="https://www.shadertoy.com/view/MtffDX">WebGL content</a> (Shift+Click).</p>
|
||||
<button onClick="runTestEleven()">Run Test 11</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-11-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-11"></div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<script src="../../build/node_modules/react/umd/react.production.min.js"></script>
|
||||
<script src="../../build/node_modules/scheduler/umd/scheduler.production.min.js"></script>
|
||||
<script src="../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../build/node_modules/scheduler/umd/scheduler.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
const {
|
||||
@@ -159,9 +117,6 @@ const {
|
||||
unstable_getFirstCallbackNode: getFirstCallbackNode,
|
||||
unstable_pauseExecution: pauseExecution,
|
||||
unstable_continueExecution: continueExecution,
|
||||
unstable_forceFrameRate: forceFrameRate,
|
||||
unstable_shouldYield: shouldYield,
|
||||
unstable_NormalPriority: NormalPriority,
|
||||
} = Scheduler;
|
||||
function displayTestResult(testNumber) {
|
||||
const expectationNode = document.getElementById('test-' + testNumber + '-expected');
|
||||
@@ -233,7 +188,7 @@ const expectedResults = [
|
||||
[
|
||||
'scheduled Cb1',
|
||||
'frame 1 started',
|
||||
'cb1 called with argument of false',
|
||||
'cb1 called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
@@ -242,8 +197,8 @@ const expectedResults = [
|
||||
'scheduled CbA',
|
||||
'scheduled CbB',
|
||||
'frame 1 started',
|
||||
'cbA called with argument of false',
|
||||
'cbB called with argument of false',
|
||||
'cbA called with argument of {"didTimeout":false}',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
@@ -253,9 +208,9 @@ const expectedResults = [
|
||||
'scheduled CbB',
|
||||
'frame 1 started',
|
||||
'scheduled CbA again',
|
||||
'cbA0 called with argument of false',
|
||||
'cbB called with argument of false',
|
||||
'cbA1 called with argument of false',
|
||||
'cbA0 called with argument of {"didTimeout":false}',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'cbA1 called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
@@ -267,11 +222,11 @@ const expectedResults = [
|
||||
'scheduled cbD',
|
||||
'frame 1 started',
|
||||
'cbC called with argument of {"didTimeout":true}',
|
||||
'cbA called with argument of false',
|
||||
'cbA called with argument of {"didTimeout":false}',
|
||||
'cbA running and taking some time',
|
||||
'frame 2 started',
|
||||
'cbB called with argument of false',
|
||||
'cbD called with argument of false',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'cbD called with argument of {"didTimeout":false}',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 5
|
||||
@@ -288,23 +243,6 @@ const expectedResults = [
|
||||
'Finishing...',
|
||||
'Done!',
|
||||
],
|
||||
// test 9
|
||||
[
|
||||
'Forcing new frame times...',
|
||||
'Using new frame time!',
|
||||
'Using new frame time!',
|
||||
'Finished!',
|
||||
],
|
||||
// test 10
|
||||
[
|
||||
'Running work for 10 seconds...',
|
||||
'Ran scheduled work for >99% of the time.',
|
||||
],
|
||||
// test 11
|
||||
[
|
||||
'Running work for 10 seconds...',
|
||||
'Ran scheduled work for >95% of the time.',
|
||||
],
|
||||
];
|
||||
function runTestOne() {
|
||||
// Test 1
|
||||
@@ -315,7 +253,7 @@ function runTestOne() {
|
||||
const cb1 = (x) => {
|
||||
updateTestResult(1, 'cb1 called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleCallback(NormalPriority, cb1);
|
||||
scheduleCallback(cb1);
|
||||
updateTestResult(1, 'scheduled Cb1');
|
||||
logWhenFramesStart(1, () => {
|
||||
displayTestResult(1);
|
||||
@@ -333,9 +271,9 @@ function runTestTwo() {
|
||||
const cbB = (x) => {
|
||||
updateTestResult(2, 'cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleCallback(NormalPriority, cbA);
|
||||
scheduleCallback(cbA);
|
||||
updateTestResult(2, 'scheduled CbA');
|
||||
scheduleCallback(NormalPriority, cbB);
|
||||
scheduleCallback(cbB);
|
||||
updateTestResult(2, 'scheduled CbB');
|
||||
logWhenFramesStart(2, () => {
|
||||
displayTestResult(2);
|
||||
@@ -350,7 +288,7 @@ function runTestThree() {
|
||||
let callbackAIterations = 0;
|
||||
const cbA = (x) => {
|
||||
if (callbackAIterations < 1) {
|
||||
scheduleCallback(NormalPriority, cbA);
|
||||
scheduleCallback(cbA);
|
||||
updateTestResult(3, 'scheduled CbA again');
|
||||
}
|
||||
updateTestResult(3, 'cbA' + callbackAIterations + ' called with argument of ' + JSON.stringify(x));
|
||||
@@ -359,9 +297,9 @@ function runTestThree() {
|
||||
const cbB = (x) => {
|
||||
updateTestResult(3, 'cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleCallback(NormalPriority, cbA);
|
||||
scheduleCallback(cbA);
|
||||
updateTestResult(3, 'scheduled CbA');
|
||||
scheduleCallback(NormalPriority, cbB);
|
||||
scheduleCallback(cbB);
|
||||
updateTestResult(3, 'scheduled CbB');
|
||||
logWhenFramesStart(3, () => {
|
||||
displayTestResult(3);
|
||||
@@ -395,13 +333,13 @@ function runTestFour() {
|
||||
const cbD = (x) => {
|
||||
updateTestResult(4, 'cbD called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleCallback(NormalPriority, cbA); // won't time out
|
||||
scheduleCallback(cbA); // won't time out
|
||||
updateTestResult(4, 'scheduled cbA');
|
||||
scheduleCallback(NormalPriority, cbB, {timeout: 100}); // times out later
|
||||
scheduleCallback(cbB, {timeout: 100}); // times out later
|
||||
updateTestResult(4, 'scheduled cbB');
|
||||
scheduleCallback(NormalPriority, cbC, {timeout: 1}); // will time out fast
|
||||
scheduleCallback(cbC, {timeout: 1}); // will time out fast
|
||||
updateTestResult(4, 'scheduled cbC');
|
||||
scheduleCallback(NormalPriority, cbD); // won't time out
|
||||
scheduleCallback(cbD); // won't time out
|
||||
updateTestResult(4, 'scheduled cbD');
|
||||
|
||||
// should have run in order of C, A, B, D
|
||||
@@ -480,15 +418,15 @@ function runTestFive() {
|
||||
});
|
||||
});
|
||||
});
|
||||
scheduleCallback(NormalPriority, cbA);
|
||||
scheduleCallback(cbA);
|
||||
console.log('scheduled cbA');
|
||||
scheduleCallback(NormalPriority, cbB); // will throw error
|
||||
scheduleCallback(cbB); // will throw error
|
||||
console.log('scheduled cbB');
|
||||
scheduleCallback(NormalPriority, cbC);
|
||||
scheduleCallback(cbC);
|
||||
console.log('scheduled cbC');
|
||||
scheduleCallback(NormalPriority, cbD); // will throw error
|
||||
scheduleCallback(cbD); // will throw error
|
||||
console.log('scheduled cbD');
|
||||
scheduleCallback(NormalPriority, cbE);
|
||||
scheduleCallback(cbE);
|
||||
console.log('scheduled cbE');
|
||||
};
|
||||
}
|
||||
@@ -558,15 +496,15 @@ function runTestSix() {
|
||||
});
|
||||
});
|
||||
});
|
||||
scheduleCallback(NormalPriority, cbA);
|
||||
scheduleCallback(cbA);
|
||||
console.log('scheduled cbA');
|
||||
scheduleCallback(NormalPriority, cbB); // will throw error
|
||||
scheduleCallback(cbB); // will throw error
|
||||
console.log('scheduled cbB');
|
||||
scheduleCallback(NormalPriority, cbC, {timeout: 1});
|
||||
scheduleCallback(cbC, {timeout: 1});
|
||||
console.log('scheduled cbC');
|
||||
scheduleCallback(NormalPriority, cbD, {timeout: 1}); // will throw error
|
||||
scheduleCallback(cbD, {timeout: 1}); // will throw error
|
||||
console.log('scheduled cbD');
|
||||
scheduleCallback(NormalPriority, cbE, {timeout: 1});
|
||||
scheduleCallback(cbE, {timeout: 1});
|
||||
console.log('scheduled cbE');
|
||||
};
|
||||
}
|
||||
@@ -582,9 +520,9 @@ function runTestSeven() {
|
||||
counter++;
|
||||
counterNode.innerHTML = counter;
|
||||
waitForTimeToPass(100);
|
||||
scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);
|
||||
scheduleCallback(incrementCounterAndScheduleNextCallback);
|
||||
}
|
||||
scheduleCallback(NormalPriority, incrementCounterAndScheduleNextCallback);
|
||||
scheduleCallback(incrementCounterAndScheduleNextCallback);
|
||||
}
|
||||
|
||||
function runTestEight() {
|
||||
@@ -604,18 +542,18 @@ function runTestEight() {
|
||||
return count;
|
||||
}
|
||||
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
scheduleCallback(() => {
|
||||
|
||||
// size should be 0
|
||||
updateTestResult(8, `Queue size: ${countNodesInStack(getFirstCallbackNode())}.`);
|
||||
updateTestResult(8, 'Pausing... press continue to resume.');
|
||||
pauseExecution();
|
||||
|
||||
scheduleCallback(NormalPriority, function () {
|
||||
scheduleCallback(function () {
|
||||
updateTestResult(8, 'Finishing...');
|
||||
displayTestResult(8);
|
||||
})
|
||||
scheduleCallback(NormalPriority, function () {
|
||||
scheduleCallback(function () {
|
||||
updateTestResult(8, 'Done!');
|
||||
displayTestResult(8);
|
||||
checkTestResult(8);
|
||||
@@ -631,101 +569,6 @@ function continueTestEight() {
|
||||
continueExecution();
|
||||
}
|
||||
|
||||
function runTestNine() {
|
||||
clearTestResult(9);
|
||||
// We have this to make sure that the thing that goes right after it can get a full frame
|
||||
var forceFrameFinish = () => {
|
||||
while (!shouldYield()) {
|
||||
waitForTimeToPass(1);
|
||||
}
|
||||
waitForTimeToPass(100);
|
||||
}
|
||||
scheduleCallback(NormalPriority, forceFrameFinish);
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
var startTime = now();
|
||||
while (!shouldYield()) {}
|
||||
var initialFrameTime = now() - startTime;
|
||||
var newFrameTime = (initialFrameTime * 2) > 60 ? (initialFrameTime * 2) : 60;
|
||||
var newFrameRate = Math.floor(1000/newFrameTime);
|
||||
updateTestResult(9, `Forcing new frame times...`);
|
||||
displayTestResult(9);
|
||||
forceFrameRate(newFrameRate);
|
||||
var toSchedule = (again) => {
|
||||
var startTime = now();
|
||||
while (!shouldYield()) {}
|
||||
var frameTime = now() - startTime;
|
||||
if (frameTime >= (newFrameTime-8)) {
|
||||
updateTestResult(9, `Using new frame time!`);
|
||||
} else {
|
||||
updateTestResult(9, `Failed to use new frame time. (off by ${newFrameTime - frameTime}ms)`);
|
||||
}
|
||||
displayTestResult(9);
|
||||
if (again) {
|
||||
scheduleCallback(NormalPriority, forceFrameFinish);
|
||||
scheduleCallback(NormalPriority, () => {toSchedule(false);});
|
||||
} else {
|
||||
updateTestResult(9, `Finished!`);
|
||||
forceFrameRate(0);
|
||||
displayTestResult(9);
|
||||
checkTestResult(9);
|
||||
}
|
||||
}
|
||||
scheduleCallback(NormalPriority, forceFrameFinish);
|
||||
scheduleCallback(NormalPriority, () => {toSchedule(true);});
|
||||
});
|
||||
}
|
||||
|
||||
function runTestTen() {
|
||||
clearTestResult(10);
|
||||
updateTestResult(10, `Running work for 10 seconds...`);
|
||||
var testStartTime = now();
|
||||
var accumulatedWork = 0
|
||||
function loop() {
|
||||
var startTime = now();
|
||||
while (!shouldYield()) {}
|
||||
var endTime = now();
|
||||
accumulatedWork += endTime - startTime;
|
||||
var runTime = endTime - testStartTime;
|
||||
if (runTime > 10000) {
|
||||
updateTestResult(10, `Ran scheduled work for ${(100 * accumulatedWork / runTime).toFixed(2)}% of the time.`);
|
||||
displayTestResult(10);
|
||||
return;
|
||||
}
|
||||
scheduleCallback(NormalPriority, loop);
|
||||
}
|
||||
scheduleCallback(NormalPriority, loop);
|
||||
}
|
||||
|
||||
function runTestEleven() {
|
||||
clearTestResult(11);
|
||||
updateTestResult(11, `Running work for 10 seconds...`);
|
||||
var testStartTime = now();
|
||||
var lastInsertion = 0;
|
||||
var accumulatedWork = 0
|
||||
function loop() {
|
||||
var startTime = now();
|
||||
var timeSinceLastDOMInteraction = startTime - lastInsertion;
|
||||
if (timeSinceLastDOMInteraction > 15) {
|
||||
lastInsertion = startTime;
|
||||
var node = document.createElement('div');
|
||||
node.textContent = startTime;
|
||||
document.body.appendChild(node);
|
||||
document.body.clientHeight; // force layout
|
||||
}
|
||||
while (!shouldYield()) {}
|
||||
var endTime = now();
|
||||
accumulatedWork += endTime - startTime;
|
||||
var runTime = endTime - testStartTime;
|
||||
if (runTime > 10000) {
|
||||
updateTestResult(11, `Ran scheduled work for ${(100 * accumulatedWork / runTime).toFixed(2)}% of the time.`);
|
||||
displayTestResult(11);
|
||||
return;
|
||||
}
|
||||
scheduleCallback(NormalPriority, loop);
|
||||
}
|
||||
scheduleCallback(NormalPriority, loop);
|
||||
}
|
||||
|
||||
</script type="text/babel">
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -36,8 +36,7 @@ function checkSchedulerAPI() {
|
||||
throw 'API is not defined';
|
||||
}
|
||||
|
||||
const abs = Math.abs(Scheduler.unstable_now() - performance.now());
|
||||
if (typeof abs !== 'number' || Number.isNaN(abs) || abs > 5) {
|
||||
if (Scheduler.unstable_now() !== performance.now()) {
|
||||
throw 'API does not work';
|
||||
}
|
||||
|
||||
@@ -176,7 +175,7 @@ function checkEndToEndIntegration() {
|
||||
SchedulerTracing.unstable_trace('render', 123, () => {
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
React.Profiler,
|
||||
React.unstable_Profiler,
|
||||
{id: 'profiler', onRender},
|
||||
React.createElement('div', null, 'hi')
|
||||
),
|
||||
|
||||
14
fixtures/unstable-async/suspense/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# production
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
31
fixtures/unstable-async/suspense/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# IO "suspense" demo
|
||||
|
||||
## What is this fixture?
|
||||
|
||||
This is a demo application based on [Dan Abramov's](https://github.com/gaearon) recent [JSConf Iceland talk](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) about React.
|
||||
|
||||
It depends on a local build of React and enables us to easily test async and "suspense" APIs in a more "real world app" like context.
|
||||
|
||||
## Can I use this code in production?
|
||||
|
||||
No. The APIs being tested here are unstable and some of them have still not been released to NPM. For now, this fixture is only a test harness.
|
||||
|
||||
## How do I run this fixture?
|
||||
|
||||
Clone the React repository.
|
||||
|
||||
Follow these steps:
|
||||
|
||||
```shell
|
||||
# 1: Build react from source
|
||||
cd /path/to/react
|
||||
yarn
|
||||
yarn build react-dom/index,react/index,react-cache,scheduler --type=NODE
|
||||
|
||||
# 2: Install fixture dependencies
|
||||
cd fixtures/unstable-async/suspense/
|
||||
yarn
|
||||
|
||||
# 3: Run the app
|
||||
yarn start
|
||||
```
|
||||
40
fixtures/unstable-async/suspense/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "io-demo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": ".",
|
||||
"devDependencies": {
|
||||
"gh-pages": "^1.1.0",
|
||||
"react-scripts": "^1.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"clipboard": "^1.7.1",
|
||||
"github-fork-ribbon-css": "^0.2.1",
|
||||
"react-draggable": "^3.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "cp -r ../../../build/node_modules/* ./node_modules/",
|
||||
"prebuild": "cp -r ../../../build/node_modules/* ./node_modules/",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"deploy": "gh-pages -d build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "./node_modules/react-scripts/config/eslint.js"
|
||||
},
|
||||
"browserslist": {
|
||||
"development": [
|
||||
"last 2 chrome versions",
|
||||
"last 2 firefox versions",
|
||||
"last 2 edge versions"
|
||||
],
|
||||
"production": [
|
||||
">1%",
|
||||
"last 4 versions",
|
||||
"Firefox ESR",
|
||||
"not ie < 11"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
fixtures/unstable-async/suspense/public/favicon.ico
Executable file
|
After Width: | Height: | Size: 24 KiB |
BIN
fixtures/unstable-async/suspense/public/img/acdlite.jpeg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
fixtures/unstable-async/suspense/public/img/bvaughn.jpeg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
fixtures/unstable-async/suspense/public/img/flarnie.jpeg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
fixtures/unstable-async/suspense/public/img/gaearon.jpeg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
fixtures/unstable-async/suspense/public/img/sebmarkbage.jpeg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
fixtures/unstable-async/suspense/public/img/sophiebits.jpeg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
fixtures/unstable-async/suspense/public/img/trueadm.jpeg
Normal file
|
After Width: | Height: | Size: 26 KiB |
13
fixtures/unstable-async/suspense/public/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="./src/favicon.ico">
|
||||
<title>React Core Team</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="debugger"></div>
|
||||
</body>
|
||||
</html>
|
||||
15
fixtures/unstable-async/suspense/public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Emoji Search",
|
||||
"name": "Emoji Search Example App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
334
fixtures/unstable-async/suspense/src/api/data.js
Normal file
@@ -0,0 +1,334 @@
|
||||
export const coreContributorListJSON = [
|
||||
{
|
||||
id: 'acdlite',
|
||||
name: 'Andrew Clark',
|
||||
},
|
||||
{
|
||||
id: 'bvaughn',
|
||||
name: 'Brian Vaughn',
|
||||
},
|
||||
{
|
||||
id: 'gaearon',
|
||||
name: 'Dan Abramov',
|
||||
},
|
||||
{
|
||||
id: 'trueadm',
|
||||
name: 'Dominic Gannaway',
|
||||
},
|
||||
{
|
||||
id: 'flarnie',
|
||||
name: 'Flarnie Marchan',
|
||||
},
|
||||
{
|
||||
id: 'sebmarkbage',
|
||||
name: 'Sebastian Markbåge',
|
||||
},
|
||||
{
|
||||
id: 'sophiebits',
|
||||
name: 'Sophie Alpert',
|
||||
},
|
||||
];
|
||||
|
||||
export const userProfileJSON = {
|
||||
acdlite: {
|
||||
id: 'acdlite',
|
||||
name: 'Andrew Clark',
|
||||
image: '/img/acdlite.jpeg',
|
||||
location: 'Redwood City, CA',
|
||||
email: 'acdlite@me.com',
|
||||
tagline: 'React core at Facebook. Hi!',
|
||||
},
|
||||
bvaughn: {
|
||||
id: 'bvaughn',
|
||||
name: 'Brian Vaughn',
|
||||
image: '/img/bvaughn.jpeg',
|
||||
location: 'Mountain View, CA',
|
||||
email: 'brian.david.vaughn@gmail.com',
|
||||
tagline:
|
||||
'React JS core team at @facebook; formerly at @treasure-data and @google.',
|
||||
},
|
||||
gaearon: {
|
||||
id: 'gaearon',
|
||||
name: 'Dan Abramov',
|
||||
image: '/img/gaearon.jpeg',
|
||||
location: 'London, UK',
|
||||
email: 'dan.abramov@me.com',
|
||||
tagline:
|
||||
'Working on @reactjs. Co-author of Redux and Create React App. Building tools for humans.',
|
||||
},
|
||||
flarnie: {
|
||||
id: 'flarnie',
|
||||
name: 'Flarnie Marchan',
|
||||
image: '/img/flarnie.jpeg',
|
||||
location: 'Oakland, CA',
|
||||
email: null,
|
||||
tagline:
|
||||
'Software Engineer at Facebook React Core Team & Co-maintainer of Draft.js',
|
||||
},
|
||||
sebmarkbage: {
|
||||
id: 'sebmarkbage',
|
||||
name: 'Sebastian Markbåge',
|
||||
image: '/img/sebmarkbage.jpeg',
|
||||
location: 'San Francisco',
|
||||
email: 'sebastian@calyptus.eu',
|
||||
tagline: null,
|
||||
},
|
||||
sophiebits: {
|
||||
id: 'sophiebits',
|
||||
name: 'Sophie Alpert',
|
||||
image: '/img/sophiebits.jpeg',
|
||||
location: 'California',
|
||||
email: 'hi@sophiebits.com',
|
||||
tagline:
|
||||
'I like fixing things. eng manager of @reactjs at Facebook. ex-@khanacademy. 💎🌸 she/her. kindness, intersectional feminism, music.',
|
||||
},
|
||||
trueadm: {
|
||||
id: 'trueadm',
|
||||
name: 'Dominic Gannaway',
|
||||
image: '/img/trueadm.jpeg',
|
||||
location: 'London, United Kingdom',
|
||||
email: null,
|
||||
tagline:
|
||||
'Currently an engineer on the React core team at @facebook. Author of @infernojs and t7. Enjoys coding + being a Dad.',
|
||||
},
|
||||
};
|
||||
export const userRepositoriesListJSON = {
|
||||
acdlite: [
|
||||
{
|
||||
name: 'recompose',
|
||||
url: 'https://github.com/acdlite/recompose',
|
||||
description:
|
||||
'A React utility belt for function components and higher-order components.',
|
||||
},
|
||||
{
|
||||
name: 'react-fiber-architecture',
|
||||
url: 'https://github.com/acdlite/react-fiber-architecture',
|
||||
description: "A description of React's new core algorithm, React Fiber",
|
||||
},
|
||||
{
|
||||
name: 'redux-router',
|
||||
url: 'https://github.com/acdlite/redux-router',
|
||||
description:
|
||||
'Redux bindings for React Router – keep your router state inside your Redux store',
|
||||
},
|
||||
{
|
||||
name: 'flummox',
|
||||
url: 'https://github.com/acdlite/flummox',
|
||||
description: 'Minimal, isomorphic Flux.',
|
||||
},
|
||||
{
|
||||
name: 'redux-rx',
|
||||
url: 'https://github.com/acdlite/redux-rx',
|
||||
description: 'RxJS utilities for Redux.',
|
||||
},
|
||||
{
|
||||
name: 'react-remarkable',
|
||||
url: 'https://github.com/acdlite/react-remarkable',
|
||||
description: 'A React component for rendering Markdown with remarkable',
|
||||
},
|
||||
],
|
||||
bvaughn: [
|
||||
{
|
||||
name: 'react-virtualized',
|
||||
url: 'https://github.com/bvaughn/react-virtualized',
|
||||
description:
|
||||
'React components for efficiently rendering large lists and tabular data',
|
||||
},
|
||||
{
|
||||
name: 'redux-search',
|
||||
url: 'https://github.com/bvaughn/redux-search',
|
||||
description: 'Redux bindings for client-side search',
|
||||
},
|
||||
{
|
||||
name: 'react-window',
|
||||
url: 'https://github.com/bvaughn/react-window',
|
||||
description:
|
||||
'React components for efficiently rendering large lists and tabular data',
|
||||
},
|
||||
{
|
||||
name: 'react-virtualized-select',
|
||||
url: 'https://github.com/bvaughn/react-virtualized-select',
|
||||
description:
|
||||
'HOC that uses react-virtualized and react-select to display large lists of options in a drop-down',
|
||||
},
|
||||
{
|
||||
name: 'js-search',
|
||||
url: 'https://github.com/bvaughn/js-search',
|
||||
description:
|
||||
'JS Search is an efficient, client-side search library for JavaScript and JSON objects',
|
||||
},
|
||||
{
|
||||
name: 'react-highlight-words',
|
||||
url: 'https://github.com/bvaughn/react-highlight-words',
|
||||
description:
|
||||
'React component to highlight words within a larger body of text',
|
||||
},
|
||||
],
|
||||
gaearon: [
|
||||
{
|
||||
name: 'facebook/react',
|
||||
url: 'https://github.com/facebook/react',
|
||||
description:
|
||||
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
|
||||
},
|
||||
{
|
||||
name: 'reduxjs/redux',
|
||||
url: 'https://github.com/reduxjs/redux',
|
||||
description: 'Predictable state container for JavaScript apps',
|
||||
},
|
||||
{
|
||||
name: 'facebook/create-react-app',
|
||||
url: 'https://github.com/facebook/create-react-app',
|
||||
description: 'Create React apps with no build configuration.',
|
||||
},
|
||||
{
|
||||
name: 'reduxjs/redux-devtools',
|
||||
url: 'https://github.com/reduxjs/redux-devtools',
|
||||
description:
|
||||
'DevTools for Redux with hot reloading, action replay, and customizable UI',
|
||||
},
|
||||
{
|
||||
name: 'react-dnd/react-dnd',
|
||||
url: 'https://github.com/react-dnd/react-dnd',
|
||||
description: 'Drag and Drop for React',
|
||||
},
|
||||
{
|
||||
name: 'paularmstrong/normalizr',
|
||||
url: 'https://github.com/paularmstrong/normalizr',
|
||||
description: 'Normalizes nested JSON according to a schema',
|
||||
},
|
||||
],
|
||||
flarnie: [
|
||||
{
|
||||
name: 'diffux/diffux',
|
||||
url: 'https://github.com/diffux/diffux',
|
||||
description: 'Perceptual diffs of responsive screenshots made simple.',
|
||||
},
|
||||
{
|
||||
name: 'facebook/draft-js',
|
||||
url: 'https://github.com/facebook/draft-js',
|
||||
description: 'A React framework for building text editors.',
|
||||
},
|
||||
{
|
||||
name: 'facebook/react',
|
||||
url: 'https://github.com/facebook/react',
|
||||
description:
|
||||
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
|
||||
},
|
||||
{
|
||||
name: 'facebook/jest',
|
||||
url: 'https://github.com/facebook/jest',
|
||||
description: '🃏 Delightful JavaScript Testing.',
|
||||
},
|
||||
{
|
||||
name: 'Galooshi/import-js',
|
||||
url: 'https://github.com/Galooshi/import-js',
|
||||
description: 'A tool to simplify importing JS modules',
|
||||
},
|
||||
{
|
||||
name: 'webpack_rails_demo',
|
||||
url: 'https://github.com/flarnie/webpack_rails_demo',
|
||||
description: 'Setting up webpack with Ruby on Rails: a basic demo',
|
||||
},
|
||||
],
|
||||
sebmarkbage: [
|
||||
{
|
||||
name: 'art',
|
||||
url: 'https://github.com/sebmarkbage/art',
|
||||
description:
|
||||
"Retained mode vector drawing API designed for multiple output modes. There's also a built-in SVG parser.",
|
||||
},
|
||||
{
|
||||
name: 'ecmascript-immutable-data-structures',
|
||||
url:
|
||||
'https://github.com/sebmarkbage/ecmascript-immutable-data-structures',
|
||||
description: null,
|
||||
},
|
||||
{
|
||||
name: 'ocamlrun-wasm',
|
||||
url: 'https://github.com/sebmarkbage/ocamlrun-wasm',
|
||||
description: 'OCamlrun WebAssembly - OCaml Bytecode Interpreter in WASM',
|
||||
},
|
||||
{
|
||||
name: 'ecmascript-generator-expression',
|
||||
url: 'https://github.com/sebmarkbage/ecmascript-generator-expression',
|
||||
description:
|
||||
'Proposal for do Generator Expressions in ECMAScript. Work in progress. Edit Add topics',
|
||||
},
|
||||
{
|
||||
name: 'ecmascript-undefined-propagation',
|
||||
url: 'https://github.com/sebmarkbage/ecmascript-undefined-propagation',
|
||||
description:
|
||||
'ECMAScript proposal to relax the rules to return `undefined` for property access on `null` or `undefined` instead of throwing.',
|
||||
},
|
||||
{
|
||||
name: 'ecmascript-shallow-equal',
|
||||
url: 'https://github.com/sebmarkbage/ecmascript-shallow-equal',
|
||||
description: 'A proposal for ECMAScript for Object.shallowEqual',
|
||||
},
|
||||
],
|
||||
sophiebits: [
|
||||
{
|
||||
name: 'facebook/react',
|
||||
url: 'https://github.com/facebook/react',
|
||||
description:
|
||||
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
|
||||
},
|
||||
{
|
||||
name: 'Khan/KaTeX',
|
||||
url: 'https://github.com/Khan/KaTeX',
|
||||
description: 'Fast math typesetting for the web.',
|
||||
},
|
||||
{
|
||||
name: 'facebook/react-devtools',
|
||||
url: 'https://github.com/facebook/react-devtools',
|
||||
description:
|
||||
'An extension that allows inspection of React component hierarchy in the Chrome and Firefox Developer Tools.',
|
||||
},
|
||||
{
|
||||
name: 'vim-awesome/vim-awesome',
|
||||
url: 'https://github.com/vim-awesome/vim-awesome',
|
||||
description: 'Awesome Vim plugins from across the universe',
|
||||
},
|
||||
{
|
||||
name: 'facebook/draft-js',
|
||||
url: 'https://github.com/facebook/draft-js',
|
||||
description: 'A React framework for building text editors.',
|
||||
},
|
||||
{
|
||||
name: 'es3ify',
|
||||
url: 'https://github.com/sophiebits/es3ify',
|
||||
description:
|
||||
'Browserify transform to convert ES5 syntax to be ES3-compatible.',
|
||||
},
|
||||
],
|
||||
trueadm: [
|
||||
{
|
||||
name: 'facebook/react',
|
||||
url: 'https://github.com/facebook/react',
|
||||
description:
|
||||
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
|
||||
},
|
||||
{
|
||||
name: 'infernojs/inferno',
|
||||
url: 'https://github.com/infernojs/inferno',
|
||||
description:
|
||||
'An extremely fast, React-like JavaScript library for building modern user interfaces',
|
||||
},
|
||||
{
|
||||
name: 'facebook/prepack',
|
||||
url: 'https://github.com/facebook/prepack',
|
||||
description: 'A JavaScript bundle optimizer.',
|
||||
},
|
||||
{
|
||||
name: 't7',
|
||||
url: 'https://github.com/trueadm/t7',
|
||||
description: 'Lightweight virtual DOM templating library',
|
||||
},
|
||||
{
|
||||
name: 'infernojs/babel-plugin-inferno',
|
||||
url: 'https://github.com/infernojs/babel-plugin-inferno',
|
||||
description: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
67
fixtures/unstable-async/suspense/src/api/index.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
coreContributorListJSON,
|
||||
userProfileJSON,
|
||||
userRepositoriesListJSON,
|
||||
} from './data';
|
||||
|
||||
export function fetchCoreContributorListJSON() {
|
||||
return makeFakeAPICall('/react/contributors', coreContributorListJSON);
|
||||
}
|
||||
|
||||
export function fetchUserProfileJSON(id) {
|
||||
return makeFakeAPICall(`/${id}/details`, userProfileJSON[id]);
|
||||
}
|
||||
|
||||
export function fetchUserRepositoriesListJSON(id) {
|
||||
return makeFakeAPICall(`/${id}/repositories`, userRepositoriesListJSON[id]);
|
||||
}
|
||||
|
||||
let fakeRequestTime = 1000;
|
||||
let onProgress = () => true;
|
||||
|
||||
export function setFakeRequestTime(val) {
|
||||
fakeRequestTime = val;
|
||||
}
|
||||
|
||||
export function setProgressHandler(handler) {
|
||||
onProgress = handler;
|
||||
}
|
||||
|
||||
export function setPauseNewRequests(value) {
|
||||
shouldPauseNewRequests = value;
|
||||
}
|
||||
|
||||
let shouldPauseNewRequests = false;
|
||||
let notifiers = {};
|
||||
let isPausedUrl = {};
|
||||
|
||||
export function setPaused(url, isPaused) {
|
||||
const wasPaused = isPausedUrl[url];
|
||||
isPausedUrl[url] = isPaused;
|
||||
if (isPaused !== wasPaused) {
|
||||
notifiers[url]();
|
||||
}
|
||||
}
|
||||
|
||||
function makeFakeAPICall(url, result) {
|
||||
let i = 1;
|
||||
return new Promise(resolve => {
|
||||
isPausedUrl[url] = shouldPauseNewRequests;
|
||||
function notify() {
|
||||
if (!isPausedUrl[url]) {
|
||||
i++;
|
||||
}
|
||||
onProgress(url, i, isPausedUrl[url]);
|
||||
if (isPausedUrl[url]) {
|
||||
return;
|
||||
}
|
||||
if (i === 100) {
|
||||
resolve(result);
|
||||
} else {
|
||||
setTimeout(notify, fakeRequestTime / 100);
|
||||
}
|
||||
}
|
||||
notifiers[url] = notify;
|
||||
notify();
|
||||
});
|
||||
}
|
||||
89
fixtures/unstable-async/suspense/src/components/App.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, {lazy, Suspense, PureComponent} from 'react';
|
||||
import {unstable_scheduleCallback} from 'scheduler';
|
||||
import {
|
||||
unstable_trace as trace,
|
||||
unstable_wrap as wrap,
|
||||
} from 'scheduler/tracing';
|
||||
import Spinner from './Spinner';
|
||||
import ContributorListPage from './ContributorListPage';
|
||||
|
||||
const UserPage = lazy(() => import('./UserPage'));
|
||||
|
||||
export default class App extends PureComponent {
|
||||
state = {
|
||||
currentId: null,
|
||||
showDetail: false,
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (
|
||||
prevState.showDetail !== this.state.showDetail ||
|
||||
(prevState.currentId !== this.state.currentId && this.state.showDetail)
|
||||
) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
handleUserClick = id => {
|
||||
trace(`View ${id}`, performance.now(), () => {
|
||||
trace(`View ${id} (high-pri)`, performance.now(), () =>
|
||||
this.setState({
|
||||
currentId: id,
|
||||
})
|
||||
);
|
||||
unstable_scheduleCallback(
|
||||
wrap(() =>
|
||||
trace(`View ${id} (low-pri)`, performance.now(), () =>
|
||||
this.setState({
|
||||
showDetail: true,
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
handleBackClick = () =>
|
||||
trace('View list', performance.now(), () =>
|
||||
this.setState({
|
||||
currentId: null,
|
||||
showDetail: false,
|
||||
})
|
||||
);
|
||||
|
||||
render() {
|
||||
const {currentId, showDetail} = this.state;
|
||||
return showDetail
|
||||
? this.renderDetail(currentId)
|
||||
: this.renderList(currentId);
|
||||
}
|
||||
|
||||
renderDetail(id) {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={this.handleBackClick}
|
||||
style={{
|
||||
display: 'block',
|
||||
marginBottom: '1rem',
|
||||
}}>
|
||||
Return to list
|
||||
</button>
|
||||
<Suspense maxDuration={2000} fallback={<Spinner size="large" />}>
|
||||
<UserPage id={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderList(loadingId) {
|
||||
return (
|
||||
<Suspense maxDuration={1500} fallback={<Spinner size="large" />}>
|
||||
<ContributorListPage
|
||||
loadingId={loadingId}
|
||||
onUserClick={this.handleUserClick}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import {unstable_createResource} from 'react-cache';
|
||||
import Spinner from './Spinner';
|
||||
import {fetchCoreContributorListJSON} from '../api';
|
||||
|
||||
const ContributorListResource = unstable_createResource(
|
||||
fetchCoreContributorListJSON
|
||||
);
|
||||
|
||||
const ContributorListPage = ({loadingId, onUserClick}) => (
|
||||
<Fragment>
|
||||
<h1>React Core Team</h1>
|
||||
<ul
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridGap: '0.5rem',
|
||||
gridTemplateColumns: 'repeat(auto-fill, 20rem)',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
}}>
|
||||
{ContributorListResource.read().map(user => (
|
||||
<ContributorListItem
|
||||
key={user.id}
|
||||
onClick={() => onUserClick(user.id)}
|
||||
isLoading={loadingId && user.id === loadingId}
|
||||
user={user}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const ContributorListItem = ({isLoading, onClick, user}) => (
|
||||
<li
|
||||
onClick={onClick}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'var(--color-buttonBg)',
|
||||
border: '1px solid var(--color-buttonBorder)',
|
||||
borderRadius: '1rem',
|
||||
opacity: isLoading === false ? 0.5 : 1,
|
||||
cursor: isLoading ? 'default' : 'pointer',
|
||||
}}
|
||||
tabIndex="0">
|
||||
<div>
|
||||
<strong>{user.name}</strong>
|
||||
<div style={{marginTop: '0.5rem'}}>{user.id}</div>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<Spinner size="small" />
|
||||
) : (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
|
||||
</svg>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
|
||||
export default ContributorListPage;
|
||||
75
fixtures/unstable-async/suspense/src/components/Spinner.css
Normal file
@@ -0,0 +1,75 @@
|
||||
.Spinner {
|
||||
animation: rotate 1.3s linear infinite;
|
||||
}
|
||||
|
||||
.SpinnerContainer-large {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(270deg); }
|
||||
}
|
||||
|
||||
.SmallSpinnerPath {
|
||||
stroke-dasharray: 100;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
animation:
|
||||
SmallDash 1.3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes SmallDash {
|
||||
0% { stroke-dashoffset: 100; }
|
||||
50% {
|
||||
stroke-dashoffset: 50;
|
||||
transform:rotate(135deg);
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 100;
|
||||
transform:rotate(450deg);
|
||||
}
|
||||
}
|
||||
|
||||
.MediumSpinnerPath {
|
||||
stroke-dasharray: 150;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
animation:
|
||||
MediumDash 1.3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes MediumDash {
|
||||
0% { stroke-dashoffset: 150; }
|
||||
50% {
|
||||
stroke-dashoffset: 50;
|
||||
transform:rotate(135deg);
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 150;
|
||||
transform:rotate(450deg);
|
||||
}
|
||||
}
|
||||
|
||||
.LargeSpinnerPath {
|
||||
stroke-dasharray: 200;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
animation:
|
||||
LargeDash 1.3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes LargeDash {
|
||||
0% { stroke-dashoffset: 200; }
|
||||
50% {
|
||||
stroke-dashoffset: 50;
|
||||
transform:rotate(135deg);
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 200;
|
||||
transform:rotate(450deg);
|
||||
}
|
||||
}
|
||||
51
fixtures/unstable-async/suspense/src/components/Spinner.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import './Spinner.css';
|
||||
|
||||
const SPINNER_SIZES = {
|
||||
small: 30,
|
||||
medium: 50,
|
||||
large: 70,
|
||||
};
|
||||
|
||||
const STROKE_WIDTHS = {
|
||||
small: 4,
|
||||
medium: 5,
|
||||
large: 6,
|
||||
};
|
||||
|
||||
const PATH_CLASS_NAMES = {
|
||||
small: 'SmallSpinnerPath',
|
||||
medium: 'MediumSpinnerPath',
|
||||
large: 'LargeSpinnerPath',
|
||||
};
|
||||
|
||||
// Heavily inspired by https://codepen.io/mrrocks/pen/EiplA
|
||||
export default function Spinner({size = 'small'}) {
|
||||
const baseSize = SPINNER_SIZES[size];
|
||||
const pathSize = baseSize / 2;
|
||||
const strokeWidth = STROKE_WIDTHS[size];
|
||||
const pathRadius = `${baseSize / 2 - strokeWidth}px`;
|
||||
const className = PATH_CLASS_NAMES[size];
|
||||
const containerClassName = `SpinnerContainer SpinnerContainer-${size}`;
|
||||
|
||||
return (
|
||||
<div className={containerClassName}>
|
||||
<svg
|
||||
className={className}
|
||||
width={baseSize}
|
||||
height={baseSize}
|
||||
viewBox={`0 0 ${baseSize} ${baseSize}`}>
|
||||
<circle
|
||||
className="SpinnerPath"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={strokeWidth}
|
||||
strokeLinecap="round"
|
||||
cx={pathSize}
|
||||
cy={pathSize}
|
||||
r={pathRadius}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
170
fixtures/unstable-async/suspense/src/components/UserPage.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import React, {Suspense} from 'react';
|
||||
import {unstable_createResource} from 'react-cache';
|
||||
import Spinner from './Spinner';
|
||||
import {fetchUserProfileJSON, fetchUserRepositoriesListJSON} from '../api';
|
||||
|
||||
export default function UserPage({id}) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, 20rem)',
|
||||
gridGap: '1rem',
|
||||
alignItems: 'start',
|
||||
}}>
|
||||
<UserDetails id={id} />
|
||||
<Suspense maxDuration={1000} fallback={<Spinner size="medium" />}>
|
||||
<Repositories id={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const UserDetailsResource = unstable_createResource(fetchUserProfileJSON);
|
||||
|
||||
function UserDetails({id}) {
|
||||
const user = UserDetailsResource.read(id);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridGap: '0.5rem',
|
||||
width: '20rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'var(--color-buttonBg)',
|
||||
border: '1px solid var(--color-buttonBorder)',
|
||||
borderRadius: '1rem',
|
||||
}}>
|
||||
<UserPicture source={user.image} />
|
||||
<div
|
||||
style={{
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'var(--color-pageTextDark)',
|
||||
}}>
|
||||
{user.name}
|
||||
</div>
|
||||
<div style={{fontSize: '1.25rem'}}>{user.id}</div>
|
||||
{user.tagline !== null && <div>{user.tagline}</div>}
|
||||
<hr
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
border: 'none',
|
||||
backgroundColor: '#ddd',
|
||||
}}
|
||||
/>
|
||||
{user.location && <Location location={user.location} />}
|
||||
{user.email && <Email email={user.email} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Location = ({location}) => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
marginRight: '0.5rem',
|
||||
fill: 'currentColor',
|
||||
}}>
|
||||
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z" />
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
</svg>
|
||||
{location}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Email = ({email}) => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
marginRight: '0.5rem',
|
||||
fill: 'currentColor',
|
||||
}}>
|
||||
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
</svg>
|
||||
<a href={`mailto:${email}`}>{email}</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ImageResource = unstable_createResource(
|
||||
src =>
|
||||
new Promise(resolve => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(src);
|
||||
img.src = src;
|
||||
})
|
||||
);
|
||||
|
||||
function Img({src, alt, ...rest}) {
|
||||
return <img src={ImageResource.read(src)} alt={alt} {...rest} />;
|
||||
}
|
||||
|
||||
function UserPicture({source}) {
|
||||
return (
|
||||
<Suspense maxDuration={1500} fallback={<img src={source} alt="poster" />}>
|
||||
<Img
|
||||
src={source}
|
||||
alt="profile picture"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const UserRepositoriesResource = unstable_createResource(
|
||||
fetchUserRepositoriesListJSON
|
||||
);
|
||||
|
||||
function Repositories({id}) {
|
||||
const repos = UserRepositoriesResource.read(id);
|
||||
return (
|
||||
<ul
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridGap: '1rem',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
}}>
|
||||
{repos.map(repo => <Repository key={repo.name} {...repo} />)}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function Repository({description, name, url}) {
|
||||
return (
|
||||
<li
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridGap: '0.5rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'var(--color-buttonBg)',
|
||||
border: '1px solid var(--color-buttonBorder)',
|
||||
borderRadius: '1rem',
|
||||
}}>
|
||||
<strong>
|
||||
<a href={url}>{name}</a>
|
||||
</strong>
|
||||
<div>{description}</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
90
fixtures/unstable-async/suspense/src/index.css
Normal file
@@ -0,0 +1,90 @@
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
:root {
|
||||
--color-debuggerBg: #f7f7f7;
|
||||
--color-debuggerText: #333;
|
||||
--color-debuggerBorder: #e7e7e7;
|
||||
|
||||
--color-panelBg: #f7f7f7;
|
||||
--color-panelText: #333;
|
||||
|
||||
--color-pageTextDark: #000;
|
||||
--color-pageText: #333;
|
||||
--color-pageBg: #fff;
|
||||
|
||||
--color-buttonBg: #f7f7f7;
|
||||
--color-buttonBorder: #e7e7e7;
|
||||
|
||||
--pt: 8px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: calc(var(--pt)*4);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
color: var(--color-pageText);
|
||||
background-color: var(--color-pageBg);
|
||||
}
|
||||
|
||||
/* -------------------------------- */
|
||||
/* Debugger */
|
||||
/* -------------------------------- */
|
||||
|
||||
#debugger {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
/* width: 100vw; */
|
||||
/* height: 100vh; */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.🎛 {
|
||||
position: fixed;
|
||||
max-width: calc(var(--pt)*28);
|
||||
border-radius: var(--pt);
|
||||
padding: calc(var(--pt)*2);
|
||||
background-color: var(--color-debuggerBg);
|
||||
border: 1px solid var(--color-debuggerBorder);
|
||||
border-radius: 0.5rem;
|
||||
color: var(--color-debuggerText);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.🕹 {
|
||||
background-color: var(--color-buttonBg);
|
||||
border: 1px solid var(--color-buttonBorder);
|
||||
border-radius: var(--pt);
|
||||
padding: 0;
|
||||
width: calc(var(--pt)*5);
|
||||
height: calc(var(--pt)*5);
|
||||
font-size: calc(var(--pt)*3);
|
||||
line-height: 1;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.🕹.👶 {
|
||||
width: calc(var(--pt)*3);
|
||||
height: calc(var(--pt)*3);
|
||||
font-size: calc(var(--pt)*2);
|
||||
}
|
||||
|
||||
.🕹.🐘 {
|
||||
width: auto;
|
||||
height: calc(var(--pt)*7);
|
||||
padding: var(--pt) calc(var(--pt)*2);
|
||||
}
|
||||
|
||||
.🕹:hover {
|
||||
background-color: white;
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
box-shadow: 4px 4px 0 var(--color-buttonBorder);
|
||||
}
|
||||
|
||||
.🕹:active {
|
||||
box-shadow: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
275
fixtures/unstable-async/suspense/src/index.js
Normal file
@@ -0,0 +1,275 @@
|
||||
import React, {Fragment, PureComponent} from 'react';
|
||||
import {unstable_createRoot, render} from 'react-dom';
|
||||
import {unstable_trace as trace} from 'scheduler/tracing';
|
||||
import {
|
||||
setFakeRequestTime,
|
||||
setPaused,
|
||||
setPauseNewRequests,
|
||||
setProgressHandler,
|
||||
} from './api';
|
||||
import App from './components/App';
|
||||
import Draggable from 'react-draggable';
|
||||
import './index.css';
|
||||
|
||||
let handleReset;
|
||||
|
||||
class Shell extends PureComponent {
|
||||
state = {
|
||||
iteration: 0,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
handleReset = this.handleReset;
|
||||
}
|
||||
|
||||
handleReset = () =>
|
||||
this.setState(prevState => ({
|
||||
iteration: prevState.iteration + 1,
|
||||
}));
|
||||
|
||||
render() {
|
||||
return <App key={this.state.iteration} />;
|
||||
}
|
||||
}
|
||||
|
||||
class Debugger extends PureComponent {
|
||||
state = {
|
||||
iteration: 0,
|
||||
strategy: 'async',
|
||||
requestTime: 1,
|
||||
showDebugger: false,
|
||||
pauseNewRequests: false,
|
||||
waitTime: 0,
|
||||
requests: {},
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
setFakeRequestTime(this.state.requestTime * 1000);
|
||||
setProgressHandler(this.handleProgress);
|
||||
window.addEventListener('keydown', e => {
|
||||
if (e.key.toLowerCase() === '/') {
|
||||
this.setState(state => ({
|
||||
showDebugger: !state.showDebugger,
|
||||
}));
|
||||
} else if (e.key.toLowerCase() === 'p') {
|
||||
this.togglePauseRequests();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.requestTime !== this.state.requestTime) {
|
||||
setFakeRequestTime(this.state.requestTime * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
trace('Clear cache', performance.now(), () => {
|
||||
// TODO: this is not implemented.
|
||||
// cache.invalidate();
|
||||
this.setState(state => ({
|
||||
requests: {},
|
||||
}));
|
||||
handleReset();
|
||||
});
|
||||
};
|
||||
|
||||
handleProgress = (url, progress, isPaused) => {
|
||||
this.setState(state => ({
|
||||
requests: {
|
||||
...state.requests,
|
||||
[url]: {
|
||||
url,
|
||||
progress,
|
||||
isPaused,
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
togglePauseRequests = () => {
|
||||
this.setState(
|
||||
prevState => {
|
||||
return {pauseNewRequests: !prevState.pauseNewRequests};
|
||||
},
|
||||
() => {
|
||||
setPauseNewRequests(this.state.pauseNewRequests);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.showDebugger) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Draggable cancel="input">
|
||||
<div
|
||||
className="🎛"
|
||||
style={{
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
}}>
|
||||
<div>
|
||||
Latency: {this.state.requestTime} second{this.state.requestTime !==
|
||||
1
|
||||
? 's'
|
||||
: ''}{' '}
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="3"
|
||||
step="0.5"
|
||||
style={{width: '100%'}}
|
||||
value={this.state.requestTime}
|
||||
onChange={e => {
|
||||
e.stopPropagation();
|
||||
this.setState({requestTime: parseFloat(e.target.value)});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={this.state.pauseNewRequests}
|
||||
onChange={this.togglePauseRequests}
|
||||
/>
|
||||
Pause new requests
|
||||
</label>
|
||||
<br />
|
||||
<br />
|
||||
{Object.values(this.state.requests).filter(x => x.progress !== 100)
|
||||
.length > 0 ? (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: 10}}>
|
||||
<b>Loading</b>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: 10}}>
|
||||
<b>Loading</b>
|
||||
</div>
|
||||
<small style={{height: 20, display: 'block'}}>(None)</small>
|
||||
</Fragment>
|
||||
)}
|
||||
{Object.keys(this.state.requests)
|
||||
.reverse()
|
||||
.map(url => {
|
||||
const {progress, isPaused} = this.state.requests[url];
|
||||
if (progress === 100) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={url}
|
||||
style={{
|
||||
height: 20,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
title: isPaused ? 'Resume' : 'Pause',
|
||||
}}
|
||||
onClick={e => {
|
||||
setPaused(url, !isPaused);
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: progress + '%',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
backgroundColor: isPaused ? '#fbfb0e' : '#61dafb',
|
||||
zIndex: -1,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 'bold',
|
||||
color: 'black',
|
||||
}}>
|
||||
{url}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{Object.values(this.state.requests).filter(x => x.progress === 100)
|
||||
.length > 0 ? (
|
||||
<Fragment>
|
||||
<br />
|
||||
<div style={{marginBottom: 10}}>
|
||||
<b>Cached</b>{' '}
|
||||
<button
|
||||
style={{
|
||||
height: 16,
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={this.handleReset}>
|
||||
🗑
|
||||
</button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<br />
|
||||
<div style={{marginBottom: 10}}>
|
||||
<b>Cached</b>
|
||||
</div>
|
||||
<small style={{height: 20, display: 'block'}}>(None)</small>
|
||||
</Fragment>
|
||||
)}
|
||||
{Object.keys(this.state.requests)
|
||||
.reverse()
|
||||
.map(url => {
|
||||
const {progress} = this.state.requests[url];
|
||||
if (progress !== 100) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={url}
|
||||
style={{
|
||||
height: 20,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: progress + '%',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
backgroundColor:
|
||||
progress !== 100 ? '#61dafb' : 'lightgreen',
|
||||
zIndex: -1,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 'bold',
|
||||
color: 'black',
|
||||
}}>
|
||||
{url}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unstable_createRoot(document.getElementById('root')).render(<Shell />);
|
||||
|
||||
render(<Debugger />, document.getElementById('debugger'));
|
||||
7418
fixtures/unstable-async/suspense/yarn.lock
Normal file
@@ -1,6 +1,6 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import {flushSync, unstable_createRoot} from 'react-dom';
|
||||
import Scheduler from 'scheduler';
|
||||
import {flushSync, render} from 'react-dom';
|
||||
import {unstable_scheduleCallback} from 'scheduler';
|
||||
import _ from 'lodash';
|
||||
import Charts from './Charts';
|
||||
import Clock from './Clock';
|
||||
@@ -67,7 +67,7 @@ class App extends PureComponent {
|
||||
}
|
||||
this._ignoreClick = true;
|
||||
|
||||
Scheduler.unstable_next(() => {
|
||||
unstable_scheduleCallback(() => {
|
||||
this.setState({showDemo: true}, () => {
|
||||
this._ignoreClick = false;
|
||||
});
|
||||
@@ -146,5 +146,9 @@ class App extends PureComponent {
|
||||
}
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<App />, container);
|
||||
render(
|
||||
<React.unstable_ConcurrentMode>
|
||||
<App />
|
||||
</React.unstable_ConcurrentMode>,
|
||||
container
|
||||
);
|
||||
|
||||
14
fixtures/unstable-fire/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<body>
|
||||
<script src="../../build/node_modules/react/umd/react.development.js"></script>
|
||||
<script src="../../build/node_modules/react-dom/umd/react-dom-unstable-fire.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<div id="container"></div>
|
||||
<script type="text/babel">
|
||||
ReactFire.render(
|
||||
<h1>Hello World!</h1>,
|
||||
document.getElementById('container')
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
17
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "16.6.1",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@mattiasbuelens/web-streams-polyfill": "0.1.0",
|
||||
"art": "^0.10.1",
|
||||
"babel-cli": "^6.6.5",
|
||||
"babel-code-frame": "^6.26.0",
|
||||
@@ -13,7 +13,6 @@
|
||||
"babel-jest": "^23.0.1",
|
||||
"babel-plugin-check-es2015-constants": "^6.5.0",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
|
||||
"babel-plugin-transform-async-to-generator": "^6.22.0",
|
||||
"babel-plugin-transform-class-properties": "^6.11.5",
|
||||
@@ -59,12 +58,11 @@
|
||||
"flow-bin": "^0.72.0",
|
||||
"glob": "^6.0.4",
|
||||
"glob-stream": "^6.1.0",
|
||||
"google-closure-compiler": "20190301.0.0",
|
||||
"google-closure-compiler": "20190106.0.0",
|
||||
"gzip-size": "^3.0.0",
|
||||
"jasmine-check": "^1.0.0-rc.0",
|
||||
"jest": "^23.1.0",
|
||||
"jest-diff": "^23.0.1",
|
||||
"jest-snapshot-serializer-raw": "^1.1.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
@@ -86,10 +84,11 @@
|
||||
"targz": "^1.0.1",
|
||||
"through2": "^2.0.0",
|
||||
"tmp": "~0.0.28",
|
||||
"typescript": "~1.8.10"
|
||||
"typescript": "~1.8.10",
|
||||
"@mattiasbuelens/web-streams-polyfill": "0.1.0"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": "8.x || 9.x || 10.x || 11.x || 12.x"
|
||||
"node": "8.x || 9.x || 10.x || 11.x"
|
||||
},
|
||||
"jest": {
|
||||
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
|
||||
@@ -99,17 +98,15 @@
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
"lint": "node ./scripts/tasks/eslint.js",
|
||||
"lint-build": "node ./scripts/rollup/validate/index.js",
|
||||
"extract-errors": "yarn build --type=dev --extract-errors",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js",
|
||||
"debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source.js --runInBand",
|
||||
"test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
|
||||
"test-persistent": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-persistent.js",
|
||||
"debug-test-persistent": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source-persistent.js --runInBand",
|
||||
"test-fire": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-fire.js",
|
||||
"test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
|
||||
"test-fire-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source-fire.js",
|
||||
"test-prod-build": "yarn test-build-prod",
|
||||
"test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js",
|
||||
"test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js",
|
||||
"test-dom-fixture": "cd fixtures/dom && yarn && yarn prestart && yarn test",
|
||||
"flow": "node ./scripts/tasks/flow.js",
|
||||
"flow-ci": "node ./scripts/tasks/flow-ci.js",
|
||||
"prettier": "node ./scripts/prettier/index.js write-changed",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.8.6",
|
||||
"version": "16.8.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
|
||||
@@ -14,7 +14,6 @@ let BehaviorSubject;
|
||||
let ReactFeatureFlags;
|
||||
let React;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let ReplaySubject;
|
||||
|
||||
describe('createSubscription', () => {
|
||||
@@ -25,7 +24,6 @@ describe('createSubscription', () => {
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
BehaviorSubject = require('rxjs/BehaviorSubject').BehaviorSubject;
|
||||
ReplaySubject = require('rxjs/ReplaySubject').ReplaySubject;
|
||||
@@ -60,23 +58,23 @@ describe('createSubscription', () => {
|
||||
ReactNoop.render(
|
||||
<Subscription source={observable}>
|
||||
{(value = 'default') => {
|
||||
Scheduler.unstable_yieldValue(value);
|
||||
ReactNoop.yield(value);
|
||||
return null;
|
||||
}}
|
||||
</Subscription>,
|
||||
);
|
||||
|
||||
// Updates while subscribed should re-render the child component
|
||||
expect(Scheduler).toFlushAndYield(['default']);
|
||||
expect(ReactNoop.flush()).toEqual(['default']);
|
||||
observable.next(123);
|
||||
expect(Scheduler).toFlushAndYield([123]);
|
||||
expect(ReactNoop.flush()).toEqual([123]);
|
||||
observable.next('abc');
|
||||
expect(Scheduler).toFlushAndYield(['abc']);
|
||||
expect(ReactNoop.flush()).toEqual(['abc']);
|
||||
|
||||
// Unmounting the subscriber should remove listeners
|
||||
ReactNoop.render(<div />);
|
||||
observable.next(456);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should support observable types like RxJS ReplaySubject', () => {
|
||||
@@ -97,20 +95,20 @@ describe('createSubscription', () => {
|
||||
});
|
||||
|
||||
function render(value = 'default') {
|
||||
Scheduler.unstable_yieldValue(value);
|
||||
ReactNoop.yield(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
const observable = createReplaySubject('initial');
|
||||
|
||||
ReactNoop.render(<Subscription source={observable}>{render}</Subscription>);
|
||||
expect(Scheduler).toFlushAndYield(['initial']);
|
||||
expect(ReactNoop.flush()).toEqual(['initial']);
|
||||
observable.next('updated');
|
||||
expect(Scheduler).toFlushAndYield(['updated']);
|
||||
expect(ReactNoop.flush()).toEqual(['updated']);
|
||||
|
||||
// Unsetting the subscriber prop should reset subscribed values
|
||||
ReactNoop.render(<Subscription>{render}</Subscription>);
|
||||
expect(Scheduler).toFlushAndYield(['default']);
|
||||
expect(ReactNoop.flush()).toEqual(['default']);
|
||||
});
|
||||
|
||||
describe('Promises', () => {
|
||||
@@ -126,9 +124,9 @@ describe('createSubscription', () => {
|
||||
|
||||
function render(hasLoaded) {
|
||||
if (hasLoaded === undefined) {
|
||||
Scheduler.unstable_yieldValue('loading');
|
||||
ReactNoop.yield('loading');
|
||||
} else {
|
||||
Scheduler.unstable_yieldValue(hasLoaded ? 'finished' : 'failed');
|
||||
ReactNoop.yield(hasLoaded ? 'finished' : 'failed');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -143,19 +141,19 @@ describe('createSubscription', () => {
|
||||
|
||||
// Test a promise that resolves after render
|
||||
ReactNoop.render(<Subscription source={promiseA}>{render}</Subscription>);
|
||||
expect(Scheduler).toFlushAndYield(['loading']);
|
||||
expect(ReactNoop.flush()).toEqual(['loading']);
|
||||
resolveA(true);
|
||||
await promiseA;
|
||||
expect(Scheduler).toFlushAndYield(['finished']);
|
||||
expect(ReactNoop.flush()).toEqual(['finished']);
|
||||
|
||||
// Test a promise that resolves before render
|
||||
// Note that this will require an extra render anyway,
|
||||
// Because there is no way to synchronously get a Promise's value
|
||||
rejectB(false);
|
||||
ReactNoop.render(<Subscription source={promiseB}>{render}</Subscription>);
|
||||
expect(Scheduler).toFlushAndYield(['loading']);
|
||||
expect(ReactNoop.flush()).toEqual(['loading']);
|
||||
await promiseB.catch(() => true);
|
||||
expect(Scheduler).toFlushAndYield(['failed']);
|
||||
expect(ReactNoop.flush()).toEqual(['failed']);
|
||||
});
|
||||
|
||||
it('should still work if unsubscription is managed incorrectly', async () => {
|
||||
@@ -169,7 +167,7 @@ describe('createSubscription', () => {
|
||||
});
|
||||
|
||||
function render(value = 'default') {
|
||||
Scheduler.unstable_yieldValue(value);
|
||||
ReactNoop.yield(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -179,9 +177,9 @@ describe('createSubscription', () => {
|
||||
|
||||
// Subscribe first to Promise A then Promise B
|
||||
ReactNoop.render(<Subscription source={promiseA}>{render}</Subscription>);
|
||||
expect(Scheduler).toFlushAndYield(['default']);
|
||||
expect(ReactNoop.flush()).toEqual(['default']);
|
||||
ReactNoop.render(<Subscription source={promiseB}>{render}</Subscription>);
|
||||
expect(Scheduler).toFlushAndYield(['default']);
|
||||
expect(ReactNoop.flush()).toEqual(['default']);
|
||||
|
||||
// Resolve both Promises
|
||||
resolveB(123);
|
||||
@@ -189,7 +187,7 @@ describe('createSubscription', () => {
|
||||
await Promise.all([promiseA, promiseB]);
|
||||
|
||||
// Ensure that only Promise B causes an update
|
||||
expect(Scheduler).toFlushAndYield([123]);
|
||||
expect(ReactNoop.flush()).toEqual([123]);
|
||||
});
|
||||
|
||||
it('should not call setState for a Promise that resolves after unmount', async () => {
|
||||
@@ -203,7 +201,7 @@ describe('createSubscription', () => {
|
||||
});
|
||||
|
||||
function render(hasLoaded) {
|
||||
Scheduler.unstable_yieldValue('rendered');
|
||||
ReactNoop.yield('rendered');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -213,11 +211,11 @@ describe('createSubscription', () => {
|
||||
});
|
||||
|
||||
ReactNoop.render(<Subscription source={promise}>{render}</Subscription>);
|
||||
expect(Scheduler).toFlushAndYield(['rendered']);
|
||||
expect(ReactNoop.flush()).toEqual(['rendered']);
|
||||
|
||||
// Unmount
|
||||
ReactNoop.render(null);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
ReactNoop.flush();
|
||||
|
||||
// Resolve Promise should not trigger a setState warning
|
||||
resolvePromise(true);
|
||||
@@ -235,7 +233,7 @@ describe('createSubscription', () => {
|
||||
});
|
||||
|
||||
function render(value = 'default') {
|
||||
Scheduler.unstable_yieldValue(value);
|
||||
ReactNoop.yield(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -247,28 +245,28 @@ describe('createSubscription', () => {
|
||||
);
|
||||
|
||||
// Updates while subscribed should re-render the child component
|
||||
expect(Scheduler).toFlushAndYield(['a-0']);
|
||||
expect(ReactNoop.flush()).toEqual(['a-0']);
|
||||
|
||||
// Unsetting the subscriber prop should reset subscribed values
|
||||
ReactNoop.render(
|
||||
<Subscription source={observableB}>{render}</Subscription>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['b-0']);
|
||||
expect(ReactNoop.flush()).toEqual(['b-0']);
|
||||
|
||||
// Updates to the old subscribable should not re-render the child component
|
||||
observableA.next('a-1');
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
|
||||
// Updates to the bew subscribable should re-render the child component
|
||||
observableB.next('b-1');
|
||||
expect(Scheduler).toFlushAndYield(['b-1']);
|
||||
expect(ReactNoop.flush()).toEqual(['b-1']);
|
||||
});
|
||||
|
||||
it('should ignore values emitted by a new subscribable until the commit phase', () => {
|
||||
const log = [];
|
||||
|
||||
function Child({value}) {
|
||||
Scheduler.unstable_yieldValue('Child: ' + value);
|
||||
ReactNoop.yield('Child: ' + value);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -305,7 +303,7 @@ describe('createSubscription', () => {
|
||||
return (
|
||||
<Subscription source={this.state.observed}>
|
||||
{(value = 'default') => {
|
||||
Scheduler.unstable_yieldValue('Subscriber: ' + value);
|
||||
ReactNoop.yield('Subscriber: ' + value);
|
||||
return <Child value={value} />;
|
||||
}}
|
||||
</Subscription>
|
||||
@@ -317,12 +315,12 @@ describe('createSubscription', () => {
|
||||
const observableB = createBehaviorSubject('b-0');
|
||||
|
||||
ReactNoop.render(<Parent observed={observableA} />);
|
||||
expect(Scheduler).toFlushAndYield(['Subscriber: a-0', 'Child: a-0']);
|
||||
expect(ReactNoop.flush()).toEqual(['Subscriber: a-0', 'Child: a-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Start React update, but don't finish
|
||||
ReactNoop.render(<Parent observed={observableB} />);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Subscriber: b-0']);
|
||||
ReactNoop.flushThrough(['Subscriber: b-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Emit some updates from the uncommitted subscribable
|
||||
@@ -337,7 +335,7 @@ describe('createSubscription', () => {
|
||||
// We expect the last emitted update to be rendered (because of the commit phase value check)
|
||||
// But the intermediate ones should be ignored,
|
||||
// And the final rendered output should be the higher-priority observable.
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'Child: b-0',
|
||||
'Subscriber: b-3',
|
||||
'Child: b-3',
|
||||
@@ -355,7 +353,7 @@ describe('createSubscription', () => {
|
||||
const log = [];
|
||||
|
||||
function Child({value}) {
|
||||
Scheduler.unstable_yieldValue('Child: ' + value);
|
||||
ReactNoop.yield('Child: ' + value);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -392,7 +390,7 @@ describe('createSubscription', () => {
|
||||
return (
|
||||
<Subscription source={this.state.observed}>
|
||||
{(value = 'default') => {
|
||||
Scheduler.unstable_yieldValue('Subscriber: ' + value);
|
||||
ReactNoop.yield('Subscriber: ' + value);
|
||||
return <Child value={value} />;
|
||||
}}
|
||||
</Subscription>
|
||||
@@ -404,12 +402,12 @@ describe('createSubscription', () => {
|
||||
const observableB = createBehaviorSubject('b-0');
|
||||
|
||||
ReactNoop.render(<Parent observed={observableA} />);
|
||||
expect(Scheduler).toFlushAndYield(['Subscriber: a-0', 'Child: a-0']);
|
||||
expect(ReactNoop.flush()).toEqual(['Subscriber: a-0', 'Child: a-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Start React update, but don't finish
|
||||
ReactNoop.render(<Parent observed={observableB} />);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Subscriber: b-0']);
|
||||
ReactNoop.flushThrough(['Subscriber: b-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Emit some updates from the old subscribable
|
||||
@@ -422,7 +420,7 @@ describe('createSubscription', () => {
|
||||
// Flush everything and ensure that the correct subscribable is used
|
||||
// We expect the new subscribable to finish rendering,
|
||||
// But then the updated values from the old subscribable should be used.
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'Child: b-0',
|
||||
'Subscriber: a-2',
|
||||
'Child: a-2',
|
||||
@@ -435,7 +433,7 @@ describe('createSubscription', () => {
|
||||
|
||||
// Updates from the new subscribable should be ignored.
|
||||
observableB.next('b-1');
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
expect(log).toEqual([
|
||||
'Parent.componentDidMount',
|
||||
'Parent.componentDidUpdate',
|
||||
@@ -481,7 +479,7 @@ describe('createSubscription', () => {
|
||||
<Subscription source={observable}>{value => null}</Subscription>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndThrow(
|
||||
expect(ReactNoop.flush).toThrow(
|
||||
'A subscription must return an unsubscribe function.',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -28,8 +28,7 @@ Then add it to your ESLint configuration:
|
||||
],
|
||||
"rules": {
|
||||
// ...
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn"
|
||||
"react-hooks/rules-of-hooks": "error"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -38,8 +37,6 @@ Then add it to your ESLint configuration:
|
||||
|
||||
Please refer to the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) documentation and the [Hooks FAQ](https://reactjs.org/docs/hooks-faq.html#what-exactly-do-the-lint-rules-enforce) to learn more about this rule.
|
||||
|
||||
For feedback about the `exhaustive-deps` rule, please post in [this thread](https://github.com/facebook/react/issues/14920).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -12,7 +12,7 @@ const ReactHooksESLintPlugin = require('eslint-plugin-react-hooks');
|
||||
const ReactHooksESLintRule = ReactHooksESLintPlugin.rules['rules-of-hooks'];
|
||||
|
||||
ESLintTester.setDefaultConfig({
|
||||
parser: require.resolve('babel-eslint'),
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "1.6.1",
|
||||
"version": "1.0.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
@@ -25,10 +25,10 @@
|
||||
"url": "https://github.com/facebook/react/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7"
|
||||
"node": ">=6"
|
||||
},
|
||||
"homepage": "https://reactjs.org/",
|
||||
"peerDependencies": {
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import {rethrowCaughtError} from 'shared/ReactErrorUtils';
|
||||
|
||||
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
|
||||
import accumulateInto from './accumulateInto';
|
||||
import forEachAccumulated from './forEachAccumulated';
|
||||
import {executeDispatchesInOrder} from './EventPluginUtils';
|
||||
|
||||
/**
|
||||
* Internal queue of events that have accumulated their dispatches and are
|
||||
* waiting to have their dispatches executed.
|
||||
*/
|
||||
let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null;
|
||||
|
||||
/**
|
||||
* Dispatches an event and releases it back into the pool, unless persistent.
|
||||
*
|
||||
* @param {?object} event Synthetic event to be dispatched.
|
||||
* @private
|
||||
*/
|
||||
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
|
||||
if (event) {
|
||||
executeDispatchesInOrder(event);
|
||||
|
||||
if (!event.isPersistent()) {
|
||||
event.constructor.release(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
const executeDispatchesAndReleaseTopLevel = function(e) {
|
||||
return executeDispatchesAndRelease(e);
|
||||
};
|
||||
|
||||
export function runEventsInBatch(
|
||||
events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
|
||||
) {
|
||||
if (events !== null) {
|
||||
eventQueue = accumulateInto(eventQueue, events);
|
||||
}
|
||||
|
||||
// Set `eventQueue` to null before processing it so that we can tell if more
|
||||
// events get enqueued while processing.
|
||||
const processingEventQueue = eventQueue;
|
||||
eventQueue = null;
|
||||
|
||||
if (!processingEventQueue) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
|
||||
invariant(
|
||||
!eventQueue,
|
||||
'processEventQueue(): Additional events were enqueued while processing ' +
|
||||
'an event queue. Support for this has not yet been implemented.',
|
||||
);
|
||||
// This would be a good time to rethrow if any of the event handlers threw.
|
||||
rethrowCaughtError();
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {rethrowCaughtError} from 'shared/ReactErrorUtils';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {
|
||||
@@ -13,9 +14,12 @@ import {
|
||||
injectEventPluginsByName,
|
||||
plugins,
|
||||
} from './EventPluginRegistry';
|
||||
import {getFiberCurrentPropsFromNode} from './EventPluginUtils';
|
||||
import {
|
||||
executeDispatchesInOrder,
|
||||
getFiberCurrentPropsFromNode,
|
||||
} from './EventPluginUtils';
|
||||
import accumulateInto from './accumulateInto';
|
||||
import {runEventsInBatch} from './EventBatching';
|
||||
import forEachAccumulated from './forEachAccumulated';
|
||||
|
||||
import type {PluginModule} from './PluginModuleType';
|
||||
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
|
||||
@@ -23,6 +27,31 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {AnyNativeEvent} from './PluginModuleType';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
/**
|
||||
* Internal queue of events that have accumulated their dispatches and are
|
||||
* waiting to have their dispatches executed.
|
||||
*/
|
||||
let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null;
|
||||
|
||||
/**
|
||||
* Dispatches an event and releases it back into the pool, unless persistent.
|
||||
*
|
||||
* @param {?object} event Synthetic event to be dispatched.
|
||||
* @private
|
||||
*/
|
||||
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
|
||||
if (event) {
|
||||
executeDispatchesInOrder(event);
|
||||
|
||||
if (!event.isPersistent()) {
|
||||
event.constructor.release(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
const executeDispatchesAndReleaseTopLevel = function(e) {
|
||||
return executeDispatchesAndRelease(e);
|
||||
};
|
||||
|
||||
function isInteractive(tag) {
|
||||
return (
|
||||
tag === 'button' ||
|
||||
@@ -129,7 +158,7 @@ export function getListener(inst: Fiber, registrationName: string) {
|
||||
* @return {*} An accumulation of synthetic events.
|
||||
* @internal
|
||||
*/
|
||||
function extractPluginEvents(
|
||||
function extractEvents(
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
@@ -154,13 +183,39 @@ function extractPluginEvents(
|
||||
return events;
|
||||
}
|
||||
|
||||
export function runExtractedPluginEventsInBatch(
|
||||
export function runEventsInBatch(
|
||||
events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
|
||||
) {
|
||||
if (events !== null) {
|
||||
eventQueue = accumulateInto(eventQueue, events);
|
||||
}
|
||||
|
||||
// Set `eventQueue` to null before processing it so that we can tell if more
|
||||
// events get enqueued while processing.
|
||||
const processingEventQueue = eventQueue;
|
||||
eventQueue = null;
|
||||
|
||||
if (!processingEventQueue) {
|
||||
return;
|
||||
}
|
||||
|
||||
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
|
||||
invariant(
|
||||
!eventQueue,
|
||||
'processEventQueue(): Additional events were enqueued while processing ' +
|
||||
'an event queue. Support for this has not yet been implemented.',
|
||||
);
|
||||
// This would be a good time to rethrow if any of the event handlers threw.
|
||||
rethrowCaughtError();
|
||||
}
|
||||
|
||||
export function runExtractedEventsInBatch(
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
) {
|
||||
const events = extractPluginEvents(
|
||||
const events = extractEvents(
|
||||
topLevelType,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
|
||||
@@ -63,7 +63,7 @@ if (__DEV__) {
|
||||
* @param {function} listener Application-level callback
|
||||
* @param {*} inst Internal component instance
|
||||
*/
|
||||
export function executeDispatch(event, listener, inst) {
|
||||
function executeDispatch(event, listener, inst) {
|
||||
const type = event.type || 'unknown-event';
|
||||
event.currentTarget = getNodeFromInstance(inst);
|
||||
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type EventSystemFlags = number;
|
||||
|
||||
export const PLUGIN_EVENT_SYSTEM = 1;
|
||||
export const RESPONDER_EVENT_SYSTEM = 1 << 1;
|
||||
export const IS_PASSIVE = 1 << 2;
|
||||
export const IS_ACTIVE = 1 << 3;
|
||||
export const PASSIVE_NOT_SUPPORTED = 1 << 4;
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
needsStateRestore,
|
||||
restoreStateIfNeeded,
|
||||
} from './ReactControlledComponent';
|
||||
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
|
||||
|
||||
// Used as a way to call batchedUpdates when we don't have a reference to
|
||||
// the renderer. Such as when we're dispatching events or if third party
|
||||
@@ -18,107 +17,55 @@ import {enableFlareAPI} from 'shared/ReactFeatureFlags';
|
||||
// scheduled work and instead do synchronous work.
|
||||
|
||||
// Defaults
|
||||
let batchedUpdatesImpl = function(fn, bookkeeping) {
|
||||
let _batchedUpdatesImpl = function(fn, bookkeeping) {
|
||||
return fn(bookkeeping);
|
||||
};
|
||||
let discreteUpdatesImpl = function(fn, a, b, c) {
|
||||
return fn(a, b, c);
|
||||
let _interactiveUpdatesImpl = function(fn, a, b) {
|
||||
return fn(a, b);
|
||||
};
|
||||
let flushDiscreteUpdatesImpl = function() {};
|
||||
let batchedEventUpdatesImpl = batchedUpdatesImpl;
|
||||
|
||||
let isInsideEventHandler = false;
|
||||
|
||||
function finishEventHandler() {
|
||||
// Here we wait until all updates have propagated, which is important
|
||||
// when using controlled components within layers:
|
||||
// https://github.com/facebook/react/issues/1698
|
||||
// Then we restore state of any controlled component.
|
||||
const controlledComponentsHavePendingUpdates = needsStateRestore();
|
||||
if (controlledComponentsHavePendingUpdates) {
|
||||
// If a controlled event was fired, we may need to restore the state of
|
||||
// the DOM node back to the controlled value. This is necessary when React
|
||||
// bails out of the update without touching the DOM.
|
||||
flushDiscreteUpdatesImpl();
|
||||
restoreStateIfNeeded();
|
||||
}
|
||||
}
|
||||
let _flushInteractiveUpdatesImpl = function() {};
|
||||
|
||||
let isBatching = false;
|
||||
export function batchedUpdates(fn, bookkeeping) {
|
||||
if (isInsideEventHandler) {
|
||||
if (isBatching) {
|
||||
// If we are currently inside another batch, we need to wait until it
|
||||
// fully completes before restoring state.
|
||||
return fn(bookkeeping);
|
||||
}
|
||||
isInsideEventHandler = true;
|
||||
isBatching = true;
|
||||
try {
|
||||
return batchedUpdatesImpl(fn, bookkeeping);
|
||||
return _batchedUpdatesImpl(fn, bookkeeping);
|
||||
} finally {
|
||||
isInsideEventHandler = false;
|
||||
finishEventHandler();
|
||||
}
|
||||
}
|
||||
|
||||
export function batchedEventUpdates(fn, a, b) {
|
||||
if (isInsideEventHandler) {
|
||||
// If we are currently inside another batch, we need to wait until it
|
||||
// fully completes before restoring state.
|
||||
return fn(a, b);
|
||||
}
|
||||
isInsideEventHandler = true;
|
||||
try {
|
||||
return batchedEventUpdatesImpl(fn, a, b);
|
||||
} finally {
|
||||
isInsideEventHandler = false;
|
||||
finishEventHandler();
|
||||
}
|
||||
}
|
||||
|
||||
export function discreteUpdates(fn, a, b, c) {
|
||||
const prevIsInsideEventHandler = isInsideEventHandler;
|
||||
isInsideEventHandler = true;
|
||||
try {
|
||||
return discreteUpdatesImpl(fn, a, b, c);
|
||||
} finally {
|
||||
isInsideEventHandler = prevIsInsideEventHandler;
|
||||
if (!isInsideEventHandler) {
|
||||
finishEventHandler();
|
||||
// Here we wait until all updates have propagated, which is important
|
||||
// when using controlled components within layers:
|
||||
// https://github.com/facebook/react/issues/1698
|
||||
// Then we restore state of any controlled component.
|
||||
isBatching = false;
|
||||
const controlledComponentsHavePendingUpdates = needsStateRestore();
|
||||
if (controlledComponentsHavePendingUpdates) {
|
||||
// If a controlled event was fired, we may need to restore the state of
|
||||
// the DOM node back to the controlled value. This is necessary when React
|
||||
// bails out of the update without touching the DOM.
|
||||
_flushInteractiveUpdatesImpl();
|
||||
restoreStateIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lastFlushedEventTimeStamp = 0;
|
||||
export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
|
||||
// event.timeStamp isn't overly reliable due to inconsistencies in
|
||||
// how different browsers have historically provided the time stamp.
|
||||
// Some browsers provide high-resolution time stamps for all events,
|
||||
// some provide low-resolution time stamps for all events. FF < 52
|
||||
// even mixes both time stamps together. Some browsers even report
|
||||
// negative time stamps or time stamps that are 0 (iOS9) in some cases.
|
||||
// Given we are only comparing two time stamps with equality (!==),
|
||||
// we are safe from the resolution differences. If the time stamp is 0
|
||||
// we bail-out of preventing the flush, which can affect semantics,
|
||||
// such as if an earlier flush removes or adds event listeners that
|
||||
// are fired in the subsequent flush. However, this is the same
|
||||
// behaviour as we had before this change, so the risks are low.
|
||||
if (
|
||||
!isInsideEventHandler &&
|
||||
(!enableFlareAPI ||
|
||||
(timeStamp === 0 || lastFlushedEventTimeStamp !== timeStamp))
|
||||
) {
|
||||
lastFlushedEventTimeStamp = timeStamp;
|
||||
flushDiscreteUpdatesImpl();
|
||||
}
|
||||
export function interactiveUpdates(fn, a, b) {
|
||||
return _interactiveUpdatesImpl(fn, a, b);
|
||||
}
|
||||
|
||||
export function flushInteractiveUpdates() {
|
||||
return _flushInteractiveUpdatesImpl();
|
||||
}
|
||||
|
||||
export function setBatchingImplementation(
|
||||
_batchedUpdatesImpl,
|
||||
_discreteUpdatesImpl,
|
||||
_flushDiscreteUpdatesImpl,
|
||||
_batchedEventUpdatesImpl,
|
||||
batchedUpdatesImpl,
|
||||
interactiveUpdatesImpl,
|
||||
flushInteractiveUpdatesImpl,
|
||||
) {
|
||||
batchedUpdatesImpl = _batchedUpdatesImpl;
|
||||
discreteUpdatesImpl = _discreteUpdatesImpl;
|
||||
flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl;
|
||||
batchedEventUpdatesImpl = _batchedEventUpdatesImpl;
|
||||
_batchedUpdatesImpl = batchedUpdatesImpl;
|
||||
_interactiveUpdatesImpl = interactiveUpdatesImpl;
|
||||
_flushInteractiveUpdatesImpl = flushInteractiveUpdatesImpl;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export type DispatchConfig = {
|
||||
@@ -19,7 +18,7 @@ export type DispatchConfig = {
|
||||
captured: string,
|
||||
},
|
||||
registrationName?: string,
|
||||
eventPriority: EventPriority,
|
||||
isInteractive?: boolean,
|
||||
};
|
||||
|
||||
export type ReactSyntheticEvent = {
|
||||
|
||||
@@ -129,7 +129,7 @@ function recordTouchMove(touch: Touch): void {
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
} else {
|
||||
console.warn(
|
||||
console.error(
|
||||
'Cannot record touch move without a touch start.\n' + 'Touch Move: %s\n',
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
@@ -150,7 +150,7 @@ function recordTouchEnd(touch: Touch): void {
|
||||
touchRecord.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
} else {
|
||||
console.warn(
|
||||
console.error(
|
||||
'Cannot record touch end without a touch start.\n' + 'Touch End: %s\n',
|
||||
'Touch Bank: %s',
|
||||
printTouch(touch),
|
||||
|
||||
@@ -20,7 +20,7 @@ export type RNTopLevelEventType =
|
||||
|
||||
export opaque type DOMTopLevelEventType = string;
|
||||
|
||||
// Do not use the below two methods directly!
|
||||
// Do not uses the below two methods directly!
|
||||
// Instead use constants exported from DOMTopLevelEventTypes in ReactDOM.
|
||||
// (It is the only module that is allowed to access these methods.)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
const {HostComponent} = require('shared/ReactWorkTags');
|
||||
|
||||
let EventBatching;
|
||||
let EventPluginHub;
|
||||
let EventPluginUtils;
|
||||
let ResponderEventPlugin;
|
||||
|
||||
@@ -321,7 +321,7 @@ const run = function(config, hierarchyConfig, nativeEventConfig) {
|
||||
// At this point the negotiation events have been dispatched as part of the
|
||||
// extraction process, but not the side effectful events. Below, we dispatch
|
||||
// side effectful events.
|
||||
EventBatching.runEventsInBatch(extractedEvents);
|
||||
EventPluginHub.runEventsInBatch(extractedEvents);
|
||||
|
||||
// Ensure that every event that declared an `order`, was actually dispatched.
|
||||
expect('number of events dispatched:' + runData.dispatchCount).toBe(
|
||||
@@ -403,7 +403,7 @@ describe('ResponderEventPlugin', () => {
|
||||
jest.resetModules();
|
||||
|
||||
const ReactDOMUnstableNativeDependencies = require('react-dom/unstable-native-dependencies');
|
||||
EventBatching = require('events/EventBatching');
|
||||
EventPluginHub = require('events/EventPluginHub');
|
||||
EventPluginUtils = require('events/EventPluginUtils');
|
||||
ResponderEventPlugin =
|
||||
ReactDOMUnstableNativeDependencies.ResponderEventPlugin;
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let accumulate;
|
||||
|
||||
describe('accumulate', () => {
|
||||
beforeEach(() => {
|
||||
accumulate = require('events/accumulate').default;
|
||||
});
|
||||
|
||||
it('throws if the second item is null', () => {
|
||||
expect(function() {
|
||||
accumulate([], null);
|
||||
}).toThrowError(
|
||||
'accumulate(...): Accumulated items must not be null or undefined.',
|
||||
);
|
||||
});
|
||||
|
||||
it('return second item if first item is null', () => {
|
||||
const a = [];
|
||||
expect(accumulate(null, a)).toBe(a);
|
||||
});
|
||||
|
||||
it('return concatenation of items if first item is an array', () => {
|
||||
const a = ['hello'];
|
||||
const b = 'world';
|
||||
expect(accumulate(a, b)).toEqual(['hello', 'world']);
|
||||
});
|
||||
|
||||
it('return concatenation of items if second item is an array', () => {
|
||||
const a = 'hello';
|
||||
const b = ['world'];
|
||||
expect(accumulate(a, b)).toEqual(['hello', 'world']);
|
||||
});
|
||||
|
||||
it('return an array containing both items if neither item is an array', () => {
|
||||
const a = 'hello';
|
||||
const b = 'world';
|
||||
expect(accumulate(a, b)).toEqual(['hello', 'world']);
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ function accumulate<T>(
|
||||
): T | Array<T> {
|
||||
invariant(
|
||||
next != null,
|
||||
'accumulate(...): Accumulated items must not be null or undefined.',
|
||||
'accumulate(...): Accumulated items must be not be null or undefined.',
|
||||
);
|
||||
|
||||
if (current == null) {
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
showErrorDialog: jest.fn(),
|
||||
};
|
||||
export * from './src/JestMockScheduler';
|
||||
@@ -1,3 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('scheduler/unstable_mock');
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/jest-mock-scheduler.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/jest-mock-scheduler.development.js');
|
||||
}
|
||||
|
||||
61
packages/jest-mock-scheduler/src/JestMockScheduler.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
|
||||
// Math.pow(2, 30) - 1
|
||||
// 0b111111111111111111111111111111
|
||||
const maxSigned31BitInt = 1073741823;
|
||||
|
||||
export function mockRestore() {
|
||||
delete global._schedMock;
|
||||
}
|
||||
|
||||
let callback = null;
|
||||
let currentTime = -1;
|
||||
|
||||
function flushCallback(didTimeout, ms) {
|
||||
if (callback !== null) {
|
||||
let cb = callback;
|
||||
callback = null;
|
||||
try {
|
||||
currentTime = ms;
|
||||
cb(didTimeout);
|
||||
} finally {
|
||||
currentTime = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requestHostCallback(cb, ms) {
|
||||
if (currentTime !== -1) {
|
||||
// Protect against re-entrancy.
|
||||
setTimeout(requestHostCallback, 0, cb, ms);
|
||||
} else {
|
||||
callback = cb;
|
||||
setTimeout(flushCallback, ms, true, ms);
|
||||
setTimeout(flushCallback, maxSigned31BitInt, false, maxSigned31BitInt);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelHostCallback() {
|
||||
callback = null;
|
||||
}
|
||||
|
||||
function shouldYieldToHost() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getCurrentTime() {
|
||||
return currentTime === -1 ? 0 : currentTime;
|
||||
}
|
||||
|
||||
global._schedMock = [
|
||||
requestHostCallback,
|
||||
cancelHostCallback,
|
||||
shouldYieldToHost,
|
||||
getCurrentTime,
|
||||
];
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jest-react",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.2",
|
||||
"description": "Jest matchers and utilities for testing React components.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -26,8 +26,7 @@ function captureAssertion(fn) {
|
||||
}
|
||||
|
||||
function assertYieldsWereCleared(root) {
|
||||
const Scheduler = root._Scheduler;
|
||||
const actualYields = Scheduler.unstable_clearYields();
|
||||
const actualYields = root.unstable_clearYields();
|
||||
invariant(
|
||||
actualYields.length === 0,
|
||||
'Log of yielded values is not empty. ' +
|
||||
@@ -35,6 +34,54 @@ function assertYieldsWereCleared(root) {
|
||||
);
|
||||
}
|
||||
|
||||
export function unstable_toFlushAndYield(root, expectedYields) {
|
||||
assertYieldsWereCleared(root);
|
||||
const actualYields = root.unstable_flushAll();
|
||||
return captureAssertion(() => {
|
||||
expect(actualYields).toEqual(expectedYields);
|
||||
});
|
||||
}
|
||||
|
||||
export function unstable_toFlushAndYieldThrough(root, expectedYields) {
|
||||
assertYieldsWereCleared(root);
|
||||
const actualYields = root.unstable_flushNumberOfYields(expectedYields.length);
|
||||
return captureAssertion(() => {
|
||||
expect(actualYields).toEqual(expectedYields);
|
||||
});
|
||||
}
|
||||
|
||||
export function unstable_toFlushWithoutYielding(root) {
|
||||
return unstable_toFlushAndYield(root, []);
|
||||
}
|
||||
|
||||
export function unstable_toHaveYielded(ReactTestRenderer, expectedYields) {
|
||||
return captureAssertion(() => {
|
||||
if (
|
||||
ReactTestRenderer === null ||
|
||||
typeof ReactTestRenderer !== 'object' ||
|
||||
typeof ReactTestRenderer.unstable_setNowImplementation !== 'function'
|
||||
) {
|
||||
invariant(
|
||||
false,
|
||||
'The matcher `unstable_toHaveYielded` expects an instance of React Test ' +
|
||||
'Renderer.\n\nTry: ' +
|
||||
'expect(ReactTestRenderer).unstable_toHaveYielded(expectedYields)',
|
||||
);
|
||||
}
|
||||
const actualYields = ReactTestRenderer.unstable_clearYields();
|
||||
expect(actualYields).toEqual(expectedYields);
|
||||
});
|
||||
}
|
||||
|
||||
export function unstable_toFlushAndThrow(root, ...rest) {
|
||||
assertYieldsWereCleared(root);
|
||||
return captureAssertion(() => {
|
||||
expect(() => {
|
||||
root.unstable_flushAll();
|
||||
}).toThrow(...rest);
|
||||
});
|
||||
}
|
||||
|
||||
export function unstable_toMatchRenderedOutput(root, expectedJSX) {
|
||||
assertYieldsWereCleared(root);
|
||||
const actualJSON = root.toJSON();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-art",
|
||||
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
|
||||
"version": "16.8.6",
|
||||
"version": "16.8.2",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,7 +27,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.14.0"
|
||||
"scheduler": "^0.13.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
3
packages/react-art/src/ReactART.js
vendored
@@ -7,7 +7,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
import {LegacyRoot} from 'shared/ReactRootTags';
|
||||
import {
|
||||
createContainer,
|
||||
updateContainer,
|
||||
@@ -66,7 +65,7 @@ class Surface extends React.Component {
|
||||
|
||||
this._surface = Mode.Surface(+width, +height, this._tagRef);
|
||||
|
||||
this._mountNode = createContainer(this._surface, LegacyRoot, false);
|
||||
this._mountNode = createContainer(this._surface);
|
||||
updateContainer(this.props.children, this._mountNode, this);
|
||||
}
|
||||
|
||||
|
||||
56
packages/react-art/src/ReactARTHostConfig.js
vendored
@@ -5,15 +5,21 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
unstable_scheduleCallback as scheduleDeferredCallback,
|
||||
unstable_cancelCallback as cancelDeferredCallback,
|
||||
} from 'scheduler';
|
||||
export {
|
||||
unstable_now as now,
|
||||
unstable_scheduleCallback as scheduleDeferredCallback,
|
||||
unstable_shouldYield as shouldYield,
|
||||
unstable_cancelCallback as cancelDeferredCallback,
|
||||
} from 'scheduler';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
|
||||
import type {
|
||||
ReactEventResponder,
|
||||
ReactEventResponderInstance,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
@@ -335,6 +341,8 @@ export function getChildHostContext() {
|
||||
export const scheduleTimeout = setTimeout;
|
||||
export const cancelTimeout = clearTimeout;
|
||||
export const noTimeout = -1;
|
||||
export const schedulePassiveEffects = scheduleDeferredCallback;
|
||||
export const cancelPassiveEffects = cancelDeferredCallback;
|
||||
|
||||
export function shouldSetTextContent(type, props) {
|
||||
return (
|
||||
@@ -345,9 +353,6 @@ export function shouldSetTextContent(type, props) {
|
||||
// The ART renderer is secondary to the React DOM renderer.
|
||||
export const isPrimaryRenderer = false;
|
||||
|
||||
// The ART renderer shouldn't trigger missing act() warnings
|
||||
export const warnsIfNotActing = false;
|
||||
|
||||
export const supportsMutation = true;
|
||||
|
||||
export function appendChild(parentInstance, child) {
|
||||
@@ -425,40 +430,3 @@ export function unhideInstance(instance, props) {
|
||||
export function unhideTextInstance(textInstance, text): void {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function mountResponderInstance(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
responderInstance: ReactEventResponderInstance<any, any>,
|
||||
props: Object,
|
||||
state: Object,
|
||||
instance: Object,
|
||||
rootContainerInstance: Object,
|
||||
) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function unmountResponderInstance(
|
||||
responderInstance: ReactEventResponderInstance<any, any>,
|
||||
): void {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function getFundamentalComponentInstance(fundamentalInstance) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function mountFundamentalComponent(fundamentalInstance) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function shouldUpdateFundamentalComponent(fundamentalInstance) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function updateFundamentalComponent(fundamentalInstance) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function unmountFundamentalComponent(fundamentalInstance) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ const Wedge = require('react-art/Wedge');
|
||||
// Isolate the noop renderer
|
||||
jest.resetModules();
|
||||
const ReactNoop = require('react-noop-renderer');
|
||||
const Scheduler = require('scheduler');
|
||||
|
||||
let Group;
|
||||
let Shape;
|
||||
@@ -359,7 +358,7 @@ describe('ReactART', () => {
|
||||
const CurrentRendererContext = React.createContext(null);
|
||||
|
||||
function Yield(props) {
|
||||
Scheduler.unstable_yieldValue(props.value);
|
||||
ReactNoop.yield(props.value);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -386,7 +385,7 @@ describe('ReactART', () => {
|
||||
</CurrentRendererContext.Provider>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A']);
|
||||
ReactNoop.flushThrough(['A']);
|
||||
|
||||
ReactDOM.render(
|
||||
<Surface>
|
||||
@@ -401,7 +400,7 @@ describe('ReactART', () => {
|
||||
expect(ops).toEqual([null, 'ART']);
|
||||
|
||||
ops = [];
|
||||
expect(Scheduler).toFlushAndYield(['B', 'C']);
|
||||
expect(ReactNoop.flush()).toEqual(['B', 'C']);
|
||||
|
||||
expect(ops).toEqual(['Test']);
|
||||
});
|
||||
|
||||
13
packages/react-cache/src/LRU.js
vendored
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* Copyright (c) 2014-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.
|
||||
@@ -7,14 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as Scheduler from 'scheduler';
|
||||
|
||||
// Intentionally not named imports because Rollup would
|
||||
// use dynamic dispatch for CommonJS interop named imports.
|
||||
const {
|
||||
unstable_scheduleCallback: scheduleCallback,
|
||||
unstable_IdlePriority: IdlePriority,
|
||||
} = Scheduler;
|
||||
import {unstable_scheduleCallback as scheduleCallback} from 'scheduler';
|
||||
|
||||
type Entry<T> = {|
|
||||
value: T,
|
||||
@@ -37,7 +30,7 @@ export function createLRU<T>(limit: number) {
|
||||
// The cache size exceeds the limit. Schedule a callback to delete the
|
||||
// least recently used entries.
|
||||
cleanUpIsScheduled = true;
|
||||
scheduleCallback(IdlePriority, cleanUp);
|
||||
scheduleCallback(cleanUp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* 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.
|
||||
@@ -14,15 +14,55 @@ let createResource;
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let Suspense;
|
||||
let TextResource;
|
||||
let textResourceShouldFail;
|
||||
let flushScheduledWork;
|
||||
let evictLRU;
|
||||
|
||||
describe('ReactCache', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
let currentPriorityLevel = 3;
|
||||
|
||||
jest.mock('scheduler', () => {
|
||||
let callbacks = [];
|
||||
return {
|
||||
unstable_scheduleCallback(callback) {
|
||||
const callbackIndex = callbacks.length;
|
||||
callbacks.push(callback);
|
||||
return {callbackIndex};
|
||||
},
|
||||
flushScheduledWork() {
|
||||
while (callbacks.length) {
|
||||
const callback = callbacks.pop();
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
unstable_ImmediatePriority: 1,
|
||||
unstable_UserBlockingPriority: 2,
|
||||
unstable_NormalPriority: 3,
|
||||
unstable_LowPriority: 4,
|
||||
unstable_IdlePriority: 5,
|
||||
|
||||
unstable_runWithPriority(priorityLevel, fn) {
|
||||
const prevPriorityLevel = currentPriorityLevel;
|
||||
currentPriorityLevel = priorityLevel;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
currentPriorityLevel = prevPriorityLevel;
|
||||
}
|
||||
},
|
||||
|
||||
unstable_getCurrentPriorityLevel() {
|
||||
return currentPriorityLevel;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||
@@ -31,7 +71,8 @@ describe('ReactCache', () => {
|
||||
ReactCache = require('react-cache');
|
||||
createResource = ReactCache.unstable_createResource;
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
flushScheduledWork = require('scheduler').flushScheduledWork;
|
||||
evictLRU = flushScheduledWork;
|
||||
|
||||
TextResource = createResource(([text, ms = 0]) => {
|
||||
let listeners = null;
|
||||
@@ -45,12 +86,16 @@ describe('ReactCache', () => {
|
||||
listeners = [{resolve, reject}];
|
||||
setTimeout(() => {
|
||||
if (textResourceShouldFail) {
|
||||
Scheduler.unstable_yieldValue(`Promise rejected [${text}]`);
|
||||
ReactTestRenderer.unstable_yield(
|
||||
`Promise rejected [${text}]`,
|
||||
);
|
||||
status = 'rejected';
|
||||
value = new Error('Failed to load: ' + text);
|
||||
listeners.forEach(listener => listener.reject(value));
|
||||
} else {
|
||||
Scheduler.unstable_yieldValue(`Promise resolved [${text}]`);
|
||||
ReactTestRenderer.unstable_yield(
|
||||
`Promise resolved [${text}]`,
|
||||
);
|
||||
status = 'resolved';
|
||||
value = text;
|
||||
listeners.forEach(listener => listener.resolve(value));
|
||||
@@ -78,7 +123,7 @@ describe('ReactCache', () => {
|
||||
});
|
||||
|
||||
function Text(props) {
|
||||
Scheduler.unstable_yieldValue(props.text);
|
||||
ReactTestRenderer.unstable_yield(props.text);
|
||||
return props.text;
|
||||
}
|
||||
|
||||
@@ -86,13 +131,13 @@ describe('ReactCache', () => {
|
||||
const text = props.text;
|
||||
try {
|
||||
TextResource.read([props.text, props.ms]);
|
||||
Scheduler.unstable_yieldValue(text);
|
||||
ReactTestRenderer.unstable_yield(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
|
||||
ReactTestRenderer.unstable_yield(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.unstable_yieldValue(`Error! [${text}]`);
|
||||
ReactTestRenderer.unstable_yield(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
@@ -107,15 +152,15 @@ describe('ReactCache', () => {
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestRenderer.create(<App />, {
|
||||
const root = ReactTestRenderer.create(<App />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [Hi]', 'Loading...']);
|
||||
expect(root).toFlushAndYield(['Suspend! [Hi]', 'Loading...']);
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
|
||||
expect(Scheduler).toFlushAndYield(['Hi']);
|
||||
expect(ReactTestRenderer).toHaveYielded(['Promise resolved [Hi]']);
|
||||
expect(root).toFlushAndYield(['Hi']);
|
||||
});
|
||||
|
||||
it('throws an error on the subsequent read if the promise is rejected', async () => {
|
||||
@@ -131,19 +176,19 @@ describe('ReactCache', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [Hi]', 'Loading...']);
|
||||
expect(root).toFlushAndYield(['Suspend! [Hi]', 'Loading...']);
|
||||
|
||||
textResourceShouldFail = true;
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(Scheduler).toHaveYielded(['Promise rejected [Hi]']);
|
||||
expect(ReactTestRenderer).toHaveYielded(['Promise rejected [Hi]']);
|
||||
|
||||
expect(Scheduler).toFlushAndThrow('Failed to load: Hi');
|
||||
expect(Scheduler).toHaveYielded(['Error! [Hi]', 'Error! [Hi]']);
|
||||
expect(root).toFlushAndThrow('Failed to load: Hi');
|
||||
expect(ReactTestRenderer).toHaveYielded(['Error! [Hi]', 'Error! [Hi]']);
|
||||
|
||||
// Should throw again on a subsequent read
|
||||
root.update(<App />);
|
||||
expect(Scheduler).toFlushAndThrow('Failed to load: Hi');
|
||||
expect(Scheduler).toHaveYielded(['Error! [Hi]', 'Error! [Hi]']);
|
||||
expect(root).toFlushAndThrow('Failed to load: Hi');
|
||||
expect(ReactTestRenderer).toHaveYielded(['Error! [Hi]', 'Error! [Hi]']);
|
||||
});
|
||||
|
||||
it('warns if non-primitive key is passed to a resource without a hash function', () => {
|
||||
@@ -156,11 +201,11 @@ describe('ReactCache', () => {
|
||||
});
|
||||
|
||||
function App() {
|
||||
Scheduler.unstable_yieldValue('App');
|
||||
ReactTestRenderer.unstable_yield('App');
|
||||
return BadTextResource.read(['Hi', 100]);
|
||||
}
|
||||
|
||||
ReactTestRenderer.create(
|
||||
const root = ReactTestRenderer.create(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<App />
|
||||
</Suspense>,
|
||||
@@ -171,7 +216,7 @@ describe('ReactCache', () => {
|
||||
|
||||
if (__DEV__) {
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushAndYield(['App', 'Loading...']);
|
||||
expect(root).toFlushAndYield(['App', 'Loading...']);
|
||||
}).toWarnDev(
|
||||
[
|
||||
'Invalid key type. Expected a string, number, symbol, or ' +
|
||||
@@ -182,7 +227,7 @@ describe('ReactCache', () => {
|
||||
{withoutStack: true},
|
||||
);
|
||||
} else {
|
||||
expect(Scheduler).toFlushAndYield(['App', 'Loading...']);
|
||||
expect(root).toFlushAndYield(['App', 'Loading...']);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -200,19 +245,19 @@ describe('ReactCache', () => {
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
expect(root).toFlushAndYield([
|
||||
'Suspend! [1]',
|
||||
'Suspend! [2]',
|
||||
'Suspend! [3]',
|
||||
'Loading...',
|
||||
]);
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [1]',
|
||||
'Promise resolved [2]',
|
||||
'Promise resolved [3]',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield([1, 2, 3]);
|
||||
expect(root).toFlushAndYield([1, 2, 3]);
|
||||
expect(root).toMatchRenderedOutput('123');
|
||||
|
||||
// Render 1, 4, 5
|
||||
@@ -224,22 +269,24 @@ describe('ReactCache', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
expect(root).toFlushAndYield([
|
||||
1,
|
||||
'Suspend! [4]',
|
||||
'Suspend! [5]',
|
||||
'Loading...',
|
||||
]);
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [4]',
|
||||
'Promise resolved [5]',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield([1, 4, 5]);
|
||||
expect(root).toFlushAndYield([1, 4, 5]);
|
||||
expect(root).toMatchRenderedOutput('145');
|
||||
|
||||
// We've now rendered values 1, 2, 3, 4, 5, over our limit of 3. The least
|
||||
// recently used values are 2 and 3. They should have been evicted.
|
||||
// recently used values are 2 and 3. They will be evicted during the
|
||||
// next sweep.
|
||||
evictLRU();
|
||||
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
@@ -249,7 +296,7 @@ describe('ReactCache', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
expect(root).toFlushAndYield([
|
||||
// 1 is still cached
|
||||
1,
|
||||
// 2 and 3 suspend because they were evicted from the cache
|
||||
@@ -258,11 +305,11 @@ describe('ReactCache', () => {
|
||||
'Loading...',
|
||||
]);
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [2]',
|
||||
'Promise resolved [3]',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield([1, 2, 3]);
|
||||
expect(root).toFlushAndYield([1, 2, 3]);
|
||||
expect(root).toMatchRenderedOutput('123');
|
||||
});
|
||||
|
||||
@@ -283,14 +330,14 @@ describe('ReactCache', () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Loading...']);
|
||||
expect(root).toFlushAndYield(['Loading...']);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
expect(ReactTestRenderer).toHaveYielded([
|
||||
'Promise resolved [B]',
|
||||
'Promise resolved [A]',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Result']);
|
||||
expect(root).toFlushAndYield(['Result']);
|
||||
expect(root).toMatchRenderedOutput('Result');
|
||||
});
|
||||
|
||||
@@ -321,13 +368,13 @@ describe('ReactCache', () => {
|
||||
const text = props.text;
|
||||
try {
|
||||
const actualText = BadTextResource.read([props.text, props.ms]);
|
||||
Scheduler.unstable_yieldValue(actualText);
|
||||
ReactTestRenderer.unstable_yield(actualText);
|
||||
return actualText;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
|
||||
ReactTestRenderer.unstable_yield(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.unstable_yieldValue(`Error! [${text}]`);
|
||||
ReactTestRenderer.unstable_yield(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
@@ -342,7 +389,7 @@ describe('ReactCache', () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [Hi]', 'Loading...']);
|
||||
expect(root).toFlushAndYield(['Suspend! [Hi]', 'Loading...']);
|
||||
|
||||
resolveThenable('Hi');
|
||||
// This thenable improperly resolves twice. We should not update the
|
||||
@@ -358,8 +405,8 @@ describe('ReactCache', () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
expect(Scheduler).toFlushAndYield(['Hi']);
|
||||
expect(ReactTestRenderer).toHaveYielded([]);
|
||||
expect(root).toFlushAndYield(['Hi']);
|
||||
expect(root).toMatchRenderedOutput('Hi');
|
||||
});
|
||||
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {
|
||||
ReactContext,
|
||||
ReactProviderType,
|
||||
ReactEventResponder,
|
||||
ReactEventResponderListener,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {Hook} from 'react-reconciler/src/ReactFiberHooks';
|
||||
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactFiberHooks';
|
||||
@@ -220,22 +215,6 @@ function useMemo<T>(
|
||||
return value;
|
||||
}
|
||||
|
||||
function useResponder(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
listenerProps: Object,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
// Don't put the actual event responder object in, just its displayName
|
||||
const value = {
|
||||
responder: responder.displayName || 'EventResponder',
|
||||
props: listenerProps,
|
||||
};
|
||||
hookLog.push({primitive: 'Responder', stackError: new Error(), value});
|
||||
return {
|
||||
responder,
|
||||
props: listenerProps,
|
||||
};
|
||||
}
|
||||
|
||||
const Dispatcher: DispatcherType = {
|
||||
readContext,
|
||||
useCallback,
|
||||
@@ -248,14 +227,11 @@ const Dispatcher: DispatcherType = {
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
useResponder,
|
||||
};
|
||||
|
||||
// Inspect
|
||||
|
||||
type HooksNode = {
|
||||
id: number | null,
|
||||
isStateEditable: boolean,
|
||||
name: string,
|
||||
value: mixed,
|
||||
subHooks: Array<HooksNode>,
|
||||
@@ -397,7 +373,6 @@ function buildTree(rootStack, readHookLog): HooksTree {
|
||||
let rootChildren = [];
|
||||
let prevStack = null;
|
||||
let levelChildren = rootChildren;
|
||||
let nativeHookID = 0;
|
||||
let stackOfChildren = [];
|
||||
for (let i = 0; i < readHookLog.length; i++) {
|
||||
let hook = readHookLog[i];
|
||||
@@ -428,8 +403,6 @@ function buildTree(rootStack, readHookLog): HooksTree {
|
||||
for (let j = stack.length - commonSteps - 1; j >= 1; j--) {
|
||||
let children = [];
|
||||
levelChildren.push({
|
||||
id: null,
|
||||
isStateEditable: false,
|
||||
name: parseCustomHookName(stack[j - 1].functionName),
|
||||
value: undefined,
|
||||
subHooks: children,
|
||||
@@ -439,22 +412,8 @@ function buildTree(rootStack, readHookLog): HooksTree {
|
||||
}
|
||||
prevStack = stack;
|
||||
}
|
||||
const {primitive} = hook;
|
||||
|
||||
// For now, the "id" of stateful hooks is just the stateful hook index.
|
||||
// Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
|
||||
const id =
|
||||
primitive === 'Context' || primitive === 'DebugValue'
|
||||
? null
|
||||
: nativeHookID++;
|
||||
|
||||
// For the time being, only State and Reducer hooks support runtime overrides.
|
||||
const isStateEditable = primitive === 'Reducer' || primitive === 'State';
|
||||
|
||||
levelChildren.push({
|
||||
id,
|
||||
isStateEditable,
|
||||
name: primitive,
|
||||
name: hook.primitive,
|
||||
value: hook.value,
|
||||
subHooks: [],
|
||||
});
|
||||
|
||||
@@ -1,293 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('React hooks DevTools integration', () => {
|
||||
let React;
|
||||
let ReactDebugTools;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let act;
|
||||
let overrideHookState;
|
||||
let scheduleUpdate;
|
||||
let setSuspenseHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
inject: injected => {
|
||||
overrideHookState = injected.overrideHookState;
|
||||
scheduleUpdate = injected.scheduleUpdate;
|
||||
setSuspenseHandler = injected.setSuspenseHandler;
|
||||
},
|
||||
supportsFiber: true,
|
||||
onCommitFiberRoot: () => {},
|
||||
onCommitFiberUnmount: () => {},
|
||||
};
|
||||
|
||||
jest.resetModules();
|
||||
|
||||
React = require('react');
|
||||
ReactDebugTools = require('react-debug-tools');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
act = ReactTestRenderer.act;
|
||||
});
|
||||
|
||||
it('should support editing useState hooks', () => {
|
||||
let setCountFn;
|
||||
|
||||
function MyComponent() {
|
||||
const [count, setCount] = React.useState(0);
|
||||
setCountFn = setCount;
|
||||
return <div>count:{count}</div>;
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(<MyComponent />);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count:', '0'],
|
||||
});
|
||||
|
||||
const fiber = renderer.root.findByType(MyComponent)._currentFiber();
|
||||
const tree = ReactDebugTools.inspectHooksOfFiber(fiber);
|
||||
const stateHook = tree[0];
|
||||
expect(stateHook.isStateEditable).toBe(true);
|
||||
|
||||
if (__DEV__) {
|
||||
overrideHookState(fiber, stateHook.id, [], 10);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count:', '10'],
|
||||
});
|
||||
|
||||
act(() => setCountFn(count => count + 1));
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count:', '11'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should support editable useReducer hooks', () => {
|
||||
const initialData = {foo: 'abc', bar: 123};
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'swap':
|
||||
return {foo: state.bar, bar: state.foo};
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
let dispatchFn;
|
||||
function MyComponent() {
|
||||
const [state, dispatch] = React.useReducer(reducer, initialData);
|
||||
dispatchFn = dispatch;
|
||||
return (
|
||||
<div>
|
||||
foo:{state.foo}, bar:{state.bar}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(<MyComponent />);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['foo:', 'abc', ', bar:', '123'],
|
||||
});
|
||||
|
||||
const fiber = renderer.root.findByType(MyComponent)._currentFiber();
|
||||
const tree = ReactDebugTools.inspectHooksOfFiber(fiber);
|
||||
const reducerHook = tree[0];
|
||||
expect(reducerHook.isStateEditable).toBe(true);
|
||||
|
||||
if (__DEV__) {
|
||||
overrideHookState(fiber, reducerHook.id, ['foo'], 'def');
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['foo:', 'def', ', bar:', '123'],
|
||||
});
|
||||
|
||||
act(() => dispatchFn({type: 'swap'}));
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['foo:', '123', ', bar:', 'def'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// This test case is based on an open source bug report:
|
||||
// facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
|
||||
it('should handle interleaved stateful hooks (e.g. useState) and non-stateful hooks (e.g. useContext)', () => {
|
||||
const MyContext = React.createContext(1);
|
||||
|
||||
let setStateFn;
|
||||
function useCustomHook() {
|
||||
const context = React.useContext(MyContext);
|
||||
const [state, setState] = React.useState({count: context});
|
||||
React.useDebugValue(state.count);
|
||||
setStateFn = setState;
|
||||
return state.count;
|
||||
}
|
||||
|
||||
function MyComponent() {
|
||||
const count = useCustomHook();
|
||||
return <div>count:{count}</div>;
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(<MyComponent />);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count:', '1'],
|
||||
});
|
||||
|
||||
const fiber = renderer.root.findByType(MyComponent)._currentFiber();
|
||||
const tree = ReactDebugTools.inspectHooksOfFiber(fiber);
|
||||
const stateHook = tree[0].subHooks[1];
|
||||
expect(stateHook.isStateEditable).toBe(true);
|
||||
|
||||
if (__DEV__) {
|
||||
overrideHookState(fiber, stateHook.id, ['count'], 10);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count:', '10'],
|
||||
});
|
||||
|
||||
act(() => setStateFn(state => ({count: state.count + 1})));
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count:', '11'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should support overriding suspense in sync mode', () => {
|
||||
if (__DEV__) {
|
||||
// Lock the first render
|
||||
setSuspenseHandler(() => true);
|
||||
}
|
||||
|
||||
function MyComponent() {
|
||||
return 'Done';
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<div>
|
||||
<React.Suspense fallback={'Loading'}>
|
||||
<MyComponent />
|
||||
</React.Suspense>
|
||||
</div>,
|
||||
);
|
||||
const fiber = renderer.root._currentFiber().child;
|
||||
if (__DEV__) {
|
||||
// First render was locked
|
||||
expect(renderer.toJSON().children).toEqual(['Loading']);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Loading']);
|
||||
|
||||
// Release the lock
|
||||
setSuspenseHandler(() => false);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
|
||||
// Lock again
|
||||
setSuspenseHandler(() => true);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Loading']);
|
||||
|
||||
// Release the lock again
|
||||
setSuspenseHandler(() => false);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
|
||||
// Ensure it checks specific fibers.
|
||||
setSuspenseHandler(f => f === fiber || f === fiber.alternate);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Loading']);
|
||||
setSuspenseHandler(f => f !== fiber && f !== fiber.alternate);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
} else {
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support overriding suspense in concurrent mode', () => {
|
||||
if (__DEV__) {
|
||||
// Lock the first render
|
||||
setSuspenseHandler(() => true);
|
||||
}
|
||||
|
||||
function MyComponent() {
|
||||
return 'Done';
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(
|
||||
<div>
|
||||
<React.Suspense fallback={'Loading'}>
|
||||
<MyComponent />
|
||||
</React.Suspense>
|
||||
</div>,
|
||||
{unstable_isConcurrent: true},
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
// Ensure we timeout any suspense time.
|
||||
jest.advanceTimersByTime(1000);
|
||||
const fiber = renderer.root._currentFiber().child;
|
||||
if (__DEV__) {
|
||||
// First render was locked
|
||||
expect(renderer.toJSON().children).toEqual(['Loading']);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Loading']);
|
||||
|
||||
// Release the lock
|
||||
setSuspenseHandler(() => false);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
|
||||
// Lock again
|
||||
setSuspenseHandler(() => true);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Loading']);
|
||||
|
||||
// Release the lock again
|
||||
setSuspenseHandler(() => false);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
|
||||
// Ensure it checks specific fibers.
|
||||
setSuspenseHandler(f => f === fiber || f === fiber.alternate);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Loading']);
|
||||
setSuspenseHandler(f => f !== fiber && f !== fiber.alternate);
|
||||
scheduleUpdate(fiber); // Re-render
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
} else {
|
||||
expect(renderer.toJSON().children).toEqual(['Done']);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactDebugTools;
|
||||
|
||||
describe('ReactHooksInspection', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDebugTools = require('react-debug-tools');
|
||||
});
|
||||
|
||||
it('should inspect a simple useResponder hook', () => {
|
||||
const TestResponder = React.unstable_createResponder('TestResponder', {});
|
||||
|
||||
function Foo(props) {
|
||||
const listener = React.unstable_useResponder(TestResponder, {
|
||||
preventDefault: false,
|
||||
});
|
||||
return <div listeners={listener}>Hello world</div>;
|
||||
}
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 0,
|
||||
name: 'Responder',
|
||||
value: {props: {preventDefault: false}, responder: 'TestResponder'},
|
||||
subHooks: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -28,8 +28,6 @@ describe('ReactHooksInspection', () => {
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'hello world',
|
||||
subHooks: [],
|
||||
@@ -50,14 +48,10 @@ describe('ReactHooksInspection', () => {
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Custom',
|
||||
value: __DEV__ ? 'custom hook label' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'hello world',
|
||||
subHooks: [],
|
||||
@@ -86,21 +80,15 @@ describe('ReactHooksInspection', () => {
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Custom',
|
||||
value: undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
subHooks: [],
|
||||
value: 'hello',
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 1,
|
||||
name: 'Effect',
|
||||
subHooks: [],
|
||||
value: effect,
|
||||
@@ -108,21 +96,15 @@ describe('ReactHooksInspection', () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Custom',
|
||||
value: undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 2,
|
||||
name: 'State',
|
||||
value: 'world',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 3,
|
||||
name: 'Effect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
@@ -161,27 +143,19 @@ describe('ReactHooksInspection', () => {
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Bar',
|
||||
value: undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Custom',
|
||||
value: undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'Reducer',
|
||||
value: 'hello',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 1,
|
||||
name: 'Effect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
@@ -189,8 +163,6 @@ describe('ReactHooksInspection', () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 2,
|
||||
name: 'LayoutEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
@@ -198,33 +170,23 @@ describe('ReactHooksInspection', () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Baz',
|
||||
value: undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 3,
|
||||
name: 'LayoutEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Custom',
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 4,
|
||||
name: 'Reducer',
|
||||
subHooks: [],
|
||||
value: 'world',
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 5,
|
||||
name: 'Effect',
|
||||
subHooks: [],
|
||||
value: effect,
|
||||
@@ -246,8 +208,6 @@ describe('ReactHooksInspection', () => {
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Context',
|
||||
value: 'default',
|
||||
subHooks: [],
|
||||
@@ -279,12 +239,7 @@ describe('ReactHooksInspection', () => {
|
||||
expect(() => {
|
||||
ReactDebugTools.inspectHooks(Foo, {}, FakeDispatcherRef);
|
||||
}).toThrow(
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
|
||||
' one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
'Hooks can only be called inside the body of a function component.',
|
||||
);
|
||||
|
||||
expect(getterCalls).toBe(1);
|
||||
@@ -315,19 +270,9 @@ describe('ReactHooksInspection', () => {
|
||||
let tree = ReactDebugTools.inspectHooks(Foo, {});
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Custom',
|
||||
value: __DEV__ ? 'bar:123' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
subHooks: [],
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', subHooks: [], value: 0}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
let React;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let ReactDebugTools;
|
||||
let act;
|
||||
|
||||
@@ -21,7 +20,6 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
act = ReactTestRenderer.act;
|
||||
ReactDebugTools = require('react-debug-tools');
|
||||
});
|
||||
@@ -42,20 +40,8 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'hello',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'State',
|
||||
value: 'world',
|
||||
subHooks: [],
|
||||
},
|
||||
{name: 'State', value: 'hello', subHooks: []},
|
||||
{name: 'State', value: 'world', subHooks: []},
|
||||
]);
|
||||
|
||||
let {
|
||||
@@ -69,20 +55,8 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'Hi',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'State',
|
||||
value: 'world',
|
||||
subHooks: [],
|
||||
},
|
||||
{name: 'State', value: 'Hi', subHooks: []},
|
||||
{name: 'State', value: 'world', subHooks: []},
|
||||
]);
|
||||
|
||||
act(() => setStateB('world!'));
|
||||
@@ -91,20 +65,8 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'Hi',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'State',
|
||||
value: 'world!',
|
||||
subHooks: [],
|
||||
},
|
||||
{name: 'State', value: 'Hi', subHooks: []},
|
||||
{name: 'State', value: 'world!', subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -146,10 +108,7 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let renderer;
|
||||
act(() => {
|
||||
renderer = ReactTestRenderer.create(<Foo prop="prop" />);
|
||||
});
|
||||
let renderer = ReactTestRenderer.create(<Foo prop="prop" />);
|
||||
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
|
||||
@@ -157,56 +116,14 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'a',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'Reducer',
|
||||
value: 'b',
|
||||
subHooks: [],
|
||||
},
|
||||
{isStateEditable: false, id: 2, name: 'Ref', value: 'c', subHooks: []},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 3,
|
||||
name: 'LayoutEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 4,
|
||||
name: 'Effect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 5,
|
||||
name: 'ImperativeHandle',
|
||||
value: outsideRef.current,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 6,
|
||||
name: 'Memo',
|
||||
value: 'ab',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 7,
|
||||
name: 'Callback',
|
||||
value: updateStates,
|
||||
subHooks: [],
|
||||
},
|
||||
{name: 'State', value: 'a', subHooks: []},
|
||||
{name: 'Reducer', value: 'b', subHooks: []},
|
||||
{name: 'Ref', value: 'c', subHooks: []},
|
||||
{name: 'LayoutEffect', value: effect, subHooks: []},
|
||||
{name: 'Effect', value: effect, subHooks: []},
|
||||
{name: 'ImperativeHandle', value: outsideRef.current, subHooks: []},
|
||||
{name: 'Memo', value: 'ab', subHooks: []},
|
||||
{name: 'Callback', value: updateStates, subHooks: []},
|
||||
]);
|
||||
|
||||
updateStates();
|
||||
@@ -215,56 +132,14 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'A',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'Reducer',
|
||||
value: 'B',
|
||||
subHooks: [],
|
||||
},
|
||||
{isStateEditable: false, id: 2, name: 'Ref', value: 'C', subHooks: []},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 3,
|
||||
name: 'LayoutEffect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 4,
|
||||
name: 'Effect',
|
||||
value: effect,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 5,
|
||||
name: 'ImperativeHandle',
|
||||
value: outsideRef.current,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 6,
|
||||
name: 'Memo',
|
||||
value: 'Ab',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 7,
|
||||
name: 'Callback',
|
||||
value: updateStates,
|
||||
subHooks: [],
|
||||
},
|
||||
{name: 'State', value: 'A', subHooks: []},
|
||||
{name: 'Reducer', value: 'B', subHooks: []},
|
||||
{name: 'Ref', value: 'C', subHooks: []},
|
||||
{name: 'LayoutEffect', value: effect, subHooks: []},
|
||||
{name: 'Effect', value: effect, subHooks: []},
|
||||
{name: 'ImperativeHandle', value: outsideRef.current, subHooks: []},
|
||||
{name: 'Memo', value: 'Ab', subHooks: []},
|
||||
{name: 'Callback', value: updateStates, subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -283,8 +158,6 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Context',
|
||||
value: 'contextual',
|
||||
subHooks: [],
|
||||
@@ -304,13 +177,7 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
let childFiber = renderer.root.findByType(Foo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: 0,
|
||||
name: 'ImperativeHandle',
|
||||
value: obj,
|
||||
subHooks: [],
|
||||
},
|
||||
{name: 'ImperativeHandle', value: obj, subHooks: []},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -324,15 +191,7 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
// TODO: Test renderer findByType is broken for memo. Have to search for the inner.
|
||||
let childFiber = renderer.root.findByType(InnerFoo)._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'hello',
|
||||
subHooks: [],
|
||||
},
|
||||
]);
|
||||
expect(tree).toEqual([{name: 'State', value: 'hello', subHooks: []}]);
|
||||
});
|
||||
|
||||
it('should inspect custom hooks', () => {
|
||||
@@ -349,19 +208,9 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Custom',
|
||||
value: undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'hello',
|
||||
subHooks: [],
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', value: 'hello', subHooks: []}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -389,56 +238,24 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'LabeledValue',
|
||||
value: __DEV__ ? 'custom label a' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'a',
|
||||
subHooks: [],
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', value: 'a', subHooks: []}],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'State',
|
||||
value: 'b',
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Anonymous',
|
||||
value: undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 2,
|
||||
name: 'State',
|
||||
value: 'c',
|
||||
subHooks: [],
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', value: 'c', subHooks: []}],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'LabeledValue',
|
||||
value: __DEV__ ? 'custom label d' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 3,
|
||||
name: 'State',
|
||||
value: 'd',
|
||||
subHooks: [],
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', value: 'd', subHooks: []}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -461,25 +278,13 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Outer',
|
||||
value: __DEV__ ? 'outer' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Inner',
|
||||
value: __DEV__ ? 'inner' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 0,
|
||||
subHooks: [],
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', value: 0, subHooks: []}],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -508,49 +313,19 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'SingleLabelCustom',
|
||||
value: __DEV__ ? 'single one' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 0,
|
||||
subHooks: [],
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', value: 0, subHooks: []}],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'MultiLabelCustom',
|
||||
value: __DEV__ ? ['one', 'two', 'three'] : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 1,
|
||||
name: 'State',
|
||||
value: 0,
|
||||
subHooks: [],
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', value: 0, subHooks: []}],
|
||||
},
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'SingleLabelCustom',
|
||||
value: __DEV__ ? 'single two' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 2,
|
||||
name: 'State',
|
||||
value: 0,
|
||||
subHooks: [],
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', value: 0, subHooks: []}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -580,19 +355,9 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Custom',
|
||||
value: __DEV__ ? 'bar:123' : undefined,
|
||||
subHooks: [
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
subHooks: [],
|
||||
value: 0,
|
||||
},
|
||||
],
|
||||
subHooks: [{name: 'State', subHooks: [], value: 0}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -623,19 +388,9 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
|
||||
await LazyFoo;
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
let childFiber = renderer.root._currentFiber();
|
||||
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: 'def',
|
||||
subHooks: [],
|
||||
},
|
||||
]);
|
||||
expect(tree).toEqual([{name: 'State', value: 'def', subHooks: []}]);
|
||||
});
|
||||
|
||||
it('should support an injected dispatcher', () => {
|
||||
@@ -664,12 +419,7 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
expect(() => {
|
||||
ReactDebugTools.inspectHooksOfFiber(childFiber, FakeDispatcherRef);
|
||||
}).toThrow(
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
|
||||
' one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
'Hooks can only be called inside the body of a function component.',
|
||||
);
|
||||
|
||||
expect(getterCalls).toBe(1);
|
||||
@@ -677,54 +427,4 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
expect(setterCalls[0]).not.toBe(initial);
|
||||
expect(setterCalls[1]).toBe(initial);
|
||||
});
|
||||
|
||||
// This test case is based on an open source bug report:
|
||||
// facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
|
||||
it('should properly advance the current hook for useContext', () => {
|
||||
const MyContext = React.createContext(1);
|
||||
|
||||
let incrementCount;
|
||||
|
||||
function Foo(props) {
|
||||
const context = React.useContext(MyContext);
|
||||
const [data, setData] = React.useState({count: context});
|
||||
|
||||
incrementCount = () => setData(({count}) => ({count: count + 1}));
|
||||
|
||||
return <div>count: {data.count}</div>;
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(<Foo />);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count: ', '1'],
|
||||
});
|
||||
|
||||
act(incrementCount);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count: ', '2'],
|
||||
});
|
||||
|
||||
const childFiber = renderer.root._currentFiber();
|
||||
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{
|
||||
isStateEditable: false,
|
||||
id: null,
|
||||
name: 'Context',
|
||||
value: 1,
|
||||
subHooks: [],
|
||||
},
|
||||
{
|
||||
isStateEditable: true,
|
||||
id: 0,
|
||||
name: 'State',
|
||||
value: {count: 2},
|
||||
subHooks: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "16.8.6",
|
||||
"version": "16.8.2",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.14.0"
|
||||
"scheduler": "^0.13.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -57,15 +57,6 @@ describe('CSSPropertyOperations', () => {
|
||||
expect(html).toContain('"-ms-transition:none;-moz-transition:none"');
|
||||
});
|
||||
|
||||
it('should not hyphenate custom CSS property', () => {
|
||||
const styles = {
|
||||
'--someColor': '#000000',
|
||||
};
|
||||
const div = <div style={styles} />;
|
||||
const html = ReactDOMServer.renderToString(div);
|
||||
expect(html).toContain('"--someColor:#000000"');
|
||||
});
|
||||
|
||||
it('should set style attribute when styles exist', () => {
|
||||
const styles = {
|
||||
backgroundColor: '#000',
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactFeatureFlags;
|
||||
|
||||
describe('ReactComponentLifeCycle', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.warnAboutDeprecatedLifecycles = true;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
// TODO (RFC #6) Merge this back into ReactComponentLifeCycles-test once
|
||||
// the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
|
||||
it('warns about deprecated unsafe lifecycles', function() {
|
||||
class MyComponent extends React.Component {
|
||||
componentWillMount() {}
|
||||
componentWillReceiveProps() {}
|
||||
componentWillUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(<MyComponent x={1} />, container),
|
||||
).toLowPriorityWarnDev(
|
||||
[
|
||||
'componentWillMount is deprecated and will be removed in the next major version. ' +
|
||||
'Use componentDidMount instead. As a temporary workaround, ' +
|
||||
'you can rename to UNSAFE_componentWillMount.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
'componentWillReceiveProps is deprecated and will be removed in the next major version. ' +
|
||||
'Use static getDerivedStateFromProps instead.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
'componentWillUpdate is deprecated and will be removed in the next major version. ' +
|
||||
'Use componentDidUpdate instead. As a temporary workaround, ' +
|
||||
'you can rename to UNSAFE_componentWillUpdate.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// Dedupe check (update and instantiate new
|
||||
ReactDOM.render(<MyComponent x={2} />, container);
|
||||
ReactDOM.render(<MyComponent key="new" x={1} />, container);
|
||||
});
|
||||
|
||||
describe('react-lifecycles-compat', () => {
|
||||
const {polyfill} = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
return null;
|
||||
}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -702,17 +702,8 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => {
|
||||
expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toLowPriorityWarnDev(
|
||||
[
|
||||
'componentWillMount has been renamed',
|
||||
'componentWillReceiveProps has been renamed',
|
||||
'componentWillUpdate has been renamed',
|
||||
],
|
||||
expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -739,19 +730,8 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => {
|
||||
expect(() =>
|
||||
ReactDOM.render(<Component value={1} />, container),
|
||||
).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toLowPriorityWarnDev(
|
||||
[
|
||||
'componentWillMount has been renamed',
|
||||
'componentWillReceiveProps has been renamed',
|
||||
'componentWillUpdate has been renamed',
|
||||
],
|
||||
expect(() => ReactDOM.render(<Component value={1} />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
ReactDOM.render(<Component value={2} />, container);
|
||||
@@ -801,24 +781,14 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() =>
|
||||
ReactDOM.render(<AllLegacyLifecycles />, container),
|
||||
).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillReceiveProps\n' +
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toLowPriorityWarnDev(
|
||||
[
|
||||
'componentWillMount has been renamed',
|
||||
'componentWillUpdate has been renamed',
|
||||
],
|
||||
expect(() => ReactDOM.render(<AllLegacyLifecycles />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillReceiveProps\n' +
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
@@ -854,21 +824,15 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() =>
|
||||
ReactDOM.render(<WillMountAndUpdate />, container),
|
||||
).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toLowPriorityWarnDev(['componentWillMount has been renamed'], {
|
||||
withoutStack: true,
|
||||
});
|
||||
expect(() => ReactDOM.render(<WillMountAndUpdate />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
class WillReceiveProps extends React.Component {
|
||||
state = {};
|
||||
@@ -881,18 +845,14 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => ReactDOM.render(<WillReceiveProps />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillReceiveProps\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toLowPriorityWarnDev(['componentWillReceiveProps has been renamed'], {
|
||||
withoutStack: true,
|
||||
});
|
||||
expect(() => ReactDOM.render(<WillReceiveProps />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillReceiveProps\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', () => {
|
||||
@@ -910,24 +870,14 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() =>
|
||||
ReactDOM.render(<AllLegacyLifecycles />, container),
|
||||
).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'AllLegacyLifecycles uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillReceiveProps\n' +
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toLowPriorityWarnDev(
|
||||
[
|
||||
'componentWillMount has been renamed',
|
||||
'componentWillUpdate has been renamed',
|
||||
],
|
||||
expect(() => ReactDOM.render(<AllLegacyLifecycles />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'AllLegacyLifecycles uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillReceiveProps\n' +
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
@@ -961,21 +911,15 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() =>
|
||||
ReactDOM.render(<WillMountAndUpdate />, container),
|
||||
).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillMountAndUpdate uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toLowPriorityWarnDev(['componentWillMount has been renamed'], {
|
||||
withoutStack: true,
|
||||
});
|
||||
expect(() => ReactDOM.render(<WillMountAndUpdate />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillMountAndUpdate uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
class WillReceiveProps extends React.Component {
|
||||
state = {};
|
||||
@@ -987,18 +931,14 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => ReactDOM.render(<WillReceiveProps />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillReceiveProps uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillReceiveProps\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
}).toLowPriorityWarnDev(['componentWillReceiveProps has been renamed'], {
|
||||
withoutStack: true,
|
||||
});
|
||||
expect(() => ReactDOM.render(<WillReceiveProps />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillReceiveProps uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillReceiveProps\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
it('calls effects on module-pattern component', function() {
|
||||
@@ -1037,16 +977,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div),
|
||||
).toWarnDev(
|
||||
'Warning: The <Parent /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Parent to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Parent.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
{withoutStack: true},
|
||||
);
|
||||
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
|
||||
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
|
||||
|
||||
expect(log).toEqual([
|
||||
@@ -1132,16 +1063,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(<MyComponent foo="bar" />, div),
|
||||
).toLowPriorityWarnDev(
|
||||
[
|
||||
'componentWillMount has been renamed',
|
||||
'componentWillReceiveProps has been renamed',
|
||||
'componentWillUpdate has been renamed',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
ReactDOM.render(<MyComponent foo="bar" />, div);
|
||||
expect(log).toEqual(['componentWillMount', 'UNSAFE_componentWillMount']);
|
||||
|
||||
log.length = 0;
|
||||
@@ -1393,97 +1315,4 @@ describe('ReactComponentLifeCycle', () => {
|
||||
// De-duped
|
||||
ReactDOM.render(<MyComponent />, div);
|
||||
});
|
||||
|
||||
it('warns about deprecated unsafe lifecycles', function() {
|
||||
class MyComponent extends React.Component {
|
||||
componentWillMount() {}
|
||||
componentWillReceiveProps() {}
|
||||
componentWillUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(<MyComponent x={1} />, container),
|
||||
).toLowPriorityWarnDev(
|
||||
[
|
||||
/* eslint-disable max-len */
|
||||
`Warning: componentWillMount has been renamed, and is not recommended for use. See https://fb.me/react-async-component-lifecycle-hooks for details.
|
||||
|
||||
* Move code with side effects to componentDidMount, and set initial state in the constructor.
|
||||
* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
|
||||
|
||||
Please update the following components: MyComponent`,
|
||||
`Warning: componentWillReceiveProps has been renamed, and is not recommended for use. See https://fb.me/react-async-component-lifecycle-hooks for details.
|
||||
|
||||
* Move data fetching code or side effects to componentDidUpdate.
|
||||
* If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state
|
||||
* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
|
||||
|
||||
Please update the following components: MyComponent`,
|
||||
`Warning: componentWillUpdate has been renamed, and is not recommended for use. See https://fb.me/react-async-component-lifecycle-hooks for details.
|
||||
|
||||
* Move data fetching code or side effects to componentDidUpdate.
|
||||
* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
|
||||
|
||||
Please update the following components: MyComponent`,
|
||||
/* eslint-enable max-len */
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// Dedupe check (update and instantiate new)
|
||||
ReactDOM.render(<MyComponent x={2} />, container);
|
||||
ReactDOM.render(<MyComponent key="new" x={1} />, container);
|
||||
});
|
||||
|
||||
describe('react-lifecycles-compat', () => {
|
||||
const {polyfill} = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
return null;
|
||||
}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -118,14 +118,7 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Child test="test" />, el)).toWarnDev(
|
||||
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Child to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Child.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
{withoutStack: true},
|
||||
);
|
||||
ReactDOM.render(<Child test="test" />, el);
|
||||
|
||||
expect(el.textContent).toBe('test');
|
||||
});
|
||||
|
||||
@@ -474,14 +474,7 @@ describe('ReactCompositeComponent-state', () => {
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Child />, el)).toWarnDev(
|
||||
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
|
||||
'Change Child to a class that extends React.Component instead. ' +
|
||||
"If you can't use a class try assigning the prototype on the function as a workaround. " +
|
||||
'`Child.prototype = React.Component.prototype`. ' +
|
||||
"Don't use an arrow function since it cannot be called with `new` by React.",
|
||||
{withoutStack: true},
|
||||
);
|
||||
ReactDOM.render(<Child />, el);
|
||||
|
||||
expect(el.textContent).toBe('count:123');
|
||||
});
|
||||
|
||||
@@ -446,31 +446,4 @@ describe('ReactDOMEventListener', () => {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('should dispatch load for embed elements', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
try {
|
||||
const ref = React.createRef();
|
||||
const handleLoad = jest.fn();
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<embed ref={ref} onLoad={handleLoad} />
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
ref.current.dispatchEvent(
|
||||
new ProgressEvent('load', {
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(handleLoad).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1046,11 +1046,7 @@ describe('ReactDOMFiber', () => {
|
||||
class Click extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
expect(() => {
|
||||
node.click();
|
||||
}).toWarnDev(
|
||||
'Warning: unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering.',
|
||||
);
|
||||
node.click();
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
|
||||
@@ -13,7 +13,8 @@ const React = require('react');
|
||||
let ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
|
||||
let ReactDOM;
|
||||
let Scheduler;
|
||||
|
||||
const ConcurrentMode = React.unstable_ConcurrentMode;
|
||||
|
||||
const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
|
||||
HTMLInputElement.prototype,
|
||||
@@ -24,10 +25,33 @@ describe('ReactDOMFiberAsync', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO pull this into helper method, reduce repetition.
|
||||
// mock the browser APIs which are used in schedule:
|
||||
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
|
||||
// - calling 'window.postMessage' should actually fire postmessage handlers
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return setTimeout(() => {
|
||||
cb(Date.now());
|
||||
});
|
||||
};
|
||||
const originalAddEventListener = global.addEventListener;
|
||||
let postMessageCallback;
|
||||
global.addEventListener = function(eventName, callback, useCapture) {
|
||||
if (eventName === 'message') {
|
||||
postMessageCallback = callback;
|
||||
} else {
|
||||
originalAddEventListener(eventName, callback, useCapture);
|
||||
}
|
||||
};
|
||||
global.postMessage = function(messageKey, targetOrigin) {
|
||||
const postMessageEvent = {source: window, data: messageKey};
|
||||
if (postMessageCallback) {
|
||||
postMessageCallback(postMessageEvent);
|
||||
}
|
||||
};
|
||||
jest.resetModules();
|
||||
container = document.createElement('div');
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
@@ -83,9 +107,12 @@ describe('ReactDOMFiberAsync', () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Counter />);
|
||||
Scheduler.unstable_flushAll();
|
||||
ReactDOM.render(
|
||||
<ConcurrentMode>
|
||||
<Counter />
|
||||
</ConcurrentMode>,
|
||||
container,
|
||||
);
|
||||
expect(asyncValueRef.current.textContent).toBe('');
|
||||
expect(syncValueRef.current.textContent).toBe('');
|
||||
|
||||
@@ -97,34 +124,58 @@ describe('ReactDOMFiberAsync', () => {
|
||||
|
||||
// Should flush both updates now.
|
||||
jest.runAllTimers();
|
||||
Scheduler.unstable_flushAll();
|
||||
expect(asyncValueRef.current.textContent).toBe('hello');
|
||||
expect(syncValueRef.current.textContent).toBe('hello');
|
||||
});
|
||||
|
||||
describe('concurrent mode', () => {
|
||||
describe('with feature flag disabled', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
it('renders synchronously', () => {
|
||||
ReactDOM.render(
|
||||
<ConcurrentMode>
|
||||
<div>Hi</div>
|
||||
</ConcurrentMode>,
|
||||
container,
|
||||
);
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
|
||||
ReactDOM.render(
|
||||
<ConcurrentMode>
|
||||
<div>Bye</div>
|
||||
</ConcurrentMode>,
|
||||
container,
|
||||
);
|
||||
expect(container.textContent).toEqual('Bye');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with feature flag enabled', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
});
|
||||
|
||||
it('top-level updates are concurrent', () => {
|
||||
it('createRoot makes the entire tree async', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
expect(container.textContent).toEqual('');
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
|
||||
root.render(<div>Bye</div>);
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Bye');
|
||||
});
|
||||
|
||||
it('deep updates (setState) are oncurrent', () => {
|
||||
it('updates inside an async tree are async by default', () => {
|
||||
let instance;
|
||||
class Component extends React.Component {
|
||||
state = {step: 0};
|
||||
@@ -137,12 +188,62 @@ describe('ReactDOMFiberAsync', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Component />);
|
||||
expect(container.textContent).toEqual('');
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('0');
|
||||
|
||||
instance.setState({step: 1});
|
||||
expect(container.textContent).toEqual('0');
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
it('ConcurrentMode creates an async subtree', () => {
|
||||
let instance;
|
||||
class Component extends React.Component {
|
||||
state = {step: 0};
|
||||
render() {
|
||||
instance = this;
|
||||
return <div>{this.state.step}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<ConcurrentMode>
|
||||
<Component />
|
||||
</ConcurrentMode>,
|
||||
container,
|
||||
);
|
||||
jest.runAllTimers();
|
||||
|
||||
instance.setState({step: 1});
|
||||
expect(container.textContent).toEqual('0');
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
it('updates inside an async subtree are async by default', () => {
|
||||
let instance;
|
||||
class Child extends React.Component {
|
||||
state = {step: 0};
|
||||
render() {
|
||||
instance = this;
|
||||
return <div>{this.state.step}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<ConcurrentMode>
|
||||
<Child />
|
||||
</ConcurrentMode>
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
jest.runAllTimers();
|
||||
|
||||
instance.setState({step: 1});
|
||||
expect(container.textContent).toEqual('0');
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
@@ -262,9 +363,13 @@ describe('ReactDOMFiberAsync', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Component />);
|
||||
Scheduler.unstable_flushAll();
|
||||
ReactDOM.render(
|
||||
<ConcurrentMode>
|
||||
<Component />
|
||||
</ConcurrentMode>,
|
||||
container,
|
||||
);
|
||||
jest.runAllTimers();
|
||||
|
||||
// Updates are async by default
|
||||
instance.push('A');
|
||||
@@ -287,7 +392,7 @@ describe('ReactDOMFiberAsync', () => {
|
||||
expect(ops).toEqual(['BC']);
|
||||
|
||||
// Flush the async updates
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('ABCD');
|
||||
expect(ops).toEqual(['BC', 'ABCD']);
|
||||
});
|
||||
@@ -303,15 +408,18 @@ describe('ReactDOMFiberAsync', () => {
|
||||
return this.state.counter;
|
||||
}
|
||||
}
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Counter />);
|
||||
Scheduler.unstable_flushAll();
|
||||
ReactDOM.render(
|
||||
<ConcurrentMode>
|
||||
<Counter />
|
||||
</ConcurrentMode>,
|
||||
container,
|
||||
);
|
||||
expect(container.textContent).toEqual('0');
|
||||
|
||||
// Test that a normal update is async
|
||||
inst.increment();
|
||||
expect(container.textContent).toEqual('0');
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
|
||||
let ops = [];
|
||||
@@ -417,7 +525,7 @@ describe('ReactDOMFiberAsync', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
let disableButton = disableButtonRef.current;
|
||||
expect(disableButton.tagName).toBe('BUTTON');
|
||||
@@ -484,7 +592,7 @@ describe('ReactDOMFiberAsync', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
let disableButton = disableButtonRef.current;
|
||||
expect(disableButton.tagName).toBe('BUTTON');
|
||||
@@ -544,7 +652,7 @@ describe('ReactDOMFiberAsync', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
let enableButton = enableButtonRef.current;
|
||||
expect(enableButton.tagName).toBe('BUTTON');
|
||||
@@ -570,35 +678,4 @@ describe('ReactDOMFiberAsync', () => {
|
||||
expect(formSubmitted).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSyncRoot', () => {
|
||||
it('updates flush without yielding in the next event', () => {
|
||||
const root = ReactDOM.unstable_createSyncRoot(container);
|
||||
|
||||
function Text(props) {
|
||||
Scheduler.unstable_yieldValue(props.text);
|
||||
return props.text;
|
||||
}
|
||||
|
||||
root.render(
|
||||
<React.Fragment>
|
||||
<Text text="A" />
|
||||
<Text text="B" />
|
||||
<Text text="C" />
|
||||
</React.Fragment>,
|
||||
);
|
||||
|
||||
// Nothing should have rendered yet
|
||||
expect(container.textContent).toEqual('');
|
||||
|
||||
// Everything should render immediately in the next event
|
||||
expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
|
||||
expect(container.textContent).toEqual('ABC');
|
||||
});
|
||||
|
||||
it('does not support createBatch', () => {
|
||||
const root = ReactDOM.unstable_createSyncRoot(container);
|
||||
expect(root.createBatch).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* 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.
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let Scheduler;
|
||||
|
||||
describe('ReactDOMHooks', () => {
|
||||
let container;
|
||||
@@ -21,7 +20,6 @@ describe('ReactDOMHooks', () => {
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
@@ -57,7 +55,7 @@ describe('ReactDOMHooks', () => {
|
||||
expect(container.textContent).toBe('1');
|
||||
expect(container2.textContent).toBe('');
|
||||
expect(container3.textContent).toBe('');
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toBe('1');
|
||||
expect(container2.textContent).toBe('2');
|
||||
expect(container3.textContent).toBe('3');
|
||||
@@ -66,12 +64,48 @@ describe('ReactDOMHooks', () => {
|
||||
expect(container.textContent).toBe('2');
|
||||
expect(container2.textContent).toBe('2'); // Not flushed yet
|
||||
expect(container3.textContent).toBe('3'); // Not flushed yet
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toBe('2');
|
||||
expect(container2.textContent).toBe('4');
|
||||
expect(container3.textContent).toBe('6');
|
||||
});
|
||||
|
||||
it('can batch synchronous work inside effects with other work', () => {
|
||||
let otherContainer = document.createElement('div');
|
||||
|
||||
let calledA = false;
|
||||
function A() {
|
||||
calledA = true;
|
||||
return 'A';
|
||||
}
|
||||
|
||||
let calledB = false;
|
||||
function B() {
|
||||
calledB = true;
|
||||
return 'B';
|
||||
}
|
||||
|
||||
let _set;
|
||||
function Foo() {
|
||||
_set = React.useState(0)[1];
|
||||
React.useEffect(() => {
|
||||
ReactDOM.render(<A />, otherContainer);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Foo />, container);
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
_set(0); // Forces the effect to be flushed
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
ReactDOM.render(<B />, otherContainer);
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
});
|
||||
expect(otherContainer.textContent).toBe('B');
|
||||
expect(calledA).toBe(false); // It was in a batch
|
||||
expect(calledB).toBe(true);
|
||||
});
|
||||
|
||||
it('should not bail out when an update is scheduled from within an event handler', () => {
|
||||
const {createRef, useCallback, useState} = React;
|
||||
|
||||
@@ -105,7 +139,7 @@ describe('ReactDOMHooks', () => {
|
||||
expect(labelRef.current.innerHTML).toBe('abc');
|
||||
});
|
||||
|
||||
it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', () => {
|
||||
it('should not bail out when an update is scheduled from within an event handler in ConcurrentMode', () => {
|
||||
const {createRef, useCallback, useState} = React;
|
||||
|
||||
const Example = ({inputRef, labelRef}) => {
|
||||
@@ -126,16 +160,20 @@ describe('ReactDOMHooks', () => {
|
||||
const labelRef = createRef();
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
|
||||
root.render(
|
||||
<React.unstable_ConcurrentMode>
|
||||
<Example inputRef={inputRef} labelRef={labelRef} />
|
||||
</React.unstable_ConcurrentMode>,
|
||||
);
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
inputRef.current.value = 'abc';
|
||||
inputRef.current.dispatchEvent(
|
||||
new Event('input', {bubbles: true, cancelable: true}),
|
||||
);
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(labelRef.current.innerHTML).toBe('abc');
|
||||
});
|
||||
|
||||
@@ -12,46 +12,86 @@
|
||||
let React = require('react');
|
||||
let ReactDOM = require('react-dom');
|
||||
let ReactDOMServer = require('react-dom/server');
|
||||
let Scheduler = require('scheduler');
|
||||
let ConcurrentMode = React.unstable_ConcurrentMode;
|
||||
|
||||
describe('ReactDOMRoot', () => {
|
||||
let container;
|
||||
|
||||
let advanceCurrentTime;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
container = document.createElement('div');
|
||||
// TODO pull this into helper method, reduce repetition.
|
||||
// mock the browser APIs which are used in schedule:
|
||||
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
|
||||
// - calling 'window.postMessage' should actually fire postmessage handlers
|
||||
// - must allow artificially changing time returned by Date.now
|
||||
// Performance.now is not supported in the test environment
|
||||
const originalDateNow = Date.now;
|
||||
let advancedTime = null;
|
||||
global.Date.now = function() {
|
||||
if (advancedTime) {
|
||||
return originalDateNow() + advancedTime;
|
||||
}
|
||||
return originalDateNow();
|
||||
};
|
||||
advanceCurrentTime = function(amount) {
|
||||
advancedTime = amount;
|
||||
};
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return setTimeout(() => {
|
||||
cb(Date.now());
|
||||
});
|
||||
};
|
||||
const originalAddEventListener = global.addEventListener;
|
||||
let postMessageCallback;
|
||||
global.addEventListener = function(eventName, callback, useCapture) {
|
||||
if (eventName === 'message') {
|
||||
postMessageCallback = callback;
|
||||
} else {
|
||||
originalAddEventListener(eventName, callback, useCapture);
|
||||
}
|
||||
};
|
||||
global.postMessage = function(messageKey, targetOrigin) {
|
||||
const postMessageEvent = {source: window, data: messageKey};
|
||||
if (postMessageCallback) {
|
||||
postMessageCallback(postMessageEvent);
|
||||
}
|
||||
};
|
||||
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
Scheduler = require('scheduler');
|
||||
ConcurrentMode = React.unstable_ConcurrentMode;
|
||||
});
|
||||
|
||||
it('renders children', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
});
|
||||
|
||||
it('unmounts children', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
root.unmount();
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('');
|
||||
});
|
||||
|
||||
it('`root.render` returns a thenable work object', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const work = root.render('Hi');
|
||||
const work = root.render(<ConcurrentMode>Hi</ConcurrentMode>);
|
||||
let ops = [];
|
||||
work.then(() => {
|
||||
ops.push('inside callback: ' + container.textContent);
|
||||
});
|
||||
ops.push('before committing: ' + container.textContent);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
ops.push('after committing: ' + container.textContent);
|
||||
expect(ops).toEqual([
|
||||
'before committing: ',
|
||||
@@ -63,8 +103,8 @@ describe('ReactDOMRoot', () => {
|
||||
|
||||
it('resolves `work.then` callback synchronously if the work already committed', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const work = root.render('Hi');
|
||||
Scheduler.unstable_flushAll();
|
||||
const work = root.render(<ConcurrentMode>Hi</ConcurrentMode>);
|
||||
jest.runAllTimers();
|
||||
let ops = [];
|
||||
work.then(() => {
|
||||
ops.push('inside callback');
|
||||
@@ -92,7 +132,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Accepts `hydrate` option
|
||||
const container2 = document.createElement('div');
|
||||
@@ -103,7 +143,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
expect(() => Scheduler.unstable_flushAll()).toWarnDev('Extra attributes', {
|
||||
expect(jest.runAllTimers).toWarnDev('Extra attributes', {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
@@ -117,7 +157,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span>d</span>
|
||||
</div>,
|
||||
);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('abcd');
|
||||
root.render(
|
||||
<div>
|
||||
@@ -125,7 +165,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span>c</span>
|
||||
</div>,
|
||||
);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('abdc');
|
||||
});
|
||||
|
||||
@@ -155,9 +195,13 @@ describe('ReactDOMRoot', () => {
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
batch.render(<App />);
|
||||
batch.render(
|
||||
<ConcurrentMode>
|
||||
<App />
|
||||
</ConcurrentMode>,
|
||||
);
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Hasn't updated yet
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -186,7 +230,7 @@ describe('ReactDOMRoot', () => {
|
||||
const batch = root.createBatch();
|
||||
batch.render(<Foo>Hi</Foo>);
|
||||
// Flush all async work.
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
// Root should complete without committing.
|
||||
expect(ops).toEqual(['Foo']);
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -202,9 +246,9 @@ describe('ReactDOMRoot', () => {
|
||||
it('can wait for a batch to finish', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
batch.render('Foo');
|
||||
batch.render(<ConcurrentMode>Foo</ConcurrentMode>);
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Hasn't updated yet
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -242,9 +286,9 @@ describe('ReactDOMRoot', () => {
|
||||
|
||||
it('can commit an empty batch', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(1);
|
||||
root.render(<ConcurrentMode>1</ConcurrentMode>);
|
||||
|
||||
Scheduler.unstable_advanceTime(2000);
|
||||
advanceCurrentTime(2000);
|
||||
// This batch has a later expiration time than the earlier update.
|
||||
const batch = root.createBatch();
|
||||
|
||||
@@ -252,7 +296,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch.commit();
|
||||
expect(container.textContent).toEqual('');
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
@@ -279,7 +323,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch1.render(1);
|
||||
|
||||
// This batch has a later expiration time
|
||||
Scheduler.unstable_advanceTime(2000);
|
||||
advanceCurrentTime(2000);
|
||||
const batch2 = root.createBatch();
|
||||
batch2.render(2);
|
||||
|
||||
@@ -298,7 +342,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch1.render(1);
|
||||
|
||||
// This batch has a later expiration time
|
||||
Scheduler.unstable_advanceTime(2000);
|
||||
advanceCurrentTime(2000);
|
||||
const batch2 = root.createBatch();
|
||||
batch2.render(2);
|
||||
|
||||
@@ -308,7 +352,7 @@ describe('ReactDOMRoot', () => {
|
||||
expect(container.textContent).toEqual('2');
|
||||
|
||||
batch1.commit();
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
@@ -334,7 +378,7 @@ describe('ReactDOMRoot', () => {
|
||||
it('warns when rendering with legacy API into createRoot() container', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
expect(() => {
|
||||
ReactDOM.render(<div>Bye</div>, container);
|
||||
@@ -349,7 +393,7 @@ describe('ReactDOMRoot', () => {
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
// This works now but we could disallow it:
|
||||
expect(container.textContent).toEqual('Bye');
|
||||
});
|
||||
@@ -357,7 +401,7 @@ describe('ReactDOMRoot', () => {
|
||||
it('warns when hydrating with legacy API into createRoot() container', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
expect(() => {
|
||||
ReactDOM.hydrate(<div>Hi</div>, container);
|
||||
@@ -377,7 +421,7 @@ describe('ReactDOMRoot', () => {
|
||||
it('warns when unmounting with legacy API (no previous content)', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
let unmounted = false;
|
||||
expect(() => {
|
||||
@@ -393,10 +437,10 @@ describe('ReactDOMRoot', () => {
|
||||
{withoutStack: true},
|
||||
);
|
||||
expect(unmounted).toBe(false);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
root.unmount();
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('');
|
||||
});
|
||||
|
||||
@@ -406,17 +450,17 @@ describe('ReactDOMRoot', () => {
|
||||
// The rest is the same as test above.
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
let unmounted = false;
|
||||
expect(() => {
|
||||
unmounted = ReactDOM.unmountComponentAtNode(container);
|
||||
}).toWarnDev('Did you mean to call root.unmount()?', {withoutStack: true});
|
||||
expect(unmounted).toBe(false);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
root.unmount();
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('');
|
||||
});
|
||||
|
||||
|
||||
@@ -362,32 +362,6 @@ describe('ReactDOMSelect', () => {
|
||||
expect(node.options[2].selected).toBe(true); // gorilla
|
||||
});
|
||||
|
||||
it('does not select an item when size is initially set to greater than 1', () => {
|
||||
const stub = (
|
||||
<select size="2">
|
||||
<option value="monkey">A monkey!</option>
|
||||
<option value="giraffe">A giraffe!</option>
|
||||
<option value="gorilla">A gorilla!</option>
|
||||
</select>
|
||||
);
|
||||
const container = document.createElement('div');
|
||||
const select = ReactDOM.render(stub, container);
|
||||
|
||||
expect(select.options[0].selected).toBe(false);
|
||||
expect(select.options[1].selected).toBe(false);
|
||||
expect(select.options[2].selected).toBe(false);
|
||||
|
||||
// Note: There is an inconsistency between JSDOM and Chrome where
|
||||
// Chrome reports an empty string when no value is selected for a
|
||||
// single-select with a size greater than 0. JSDOM reports the first
|
||||
// value
|
||||
//
|
||||
// This assertion exists only for clarity of JSDOM behavior:
|
||||
expect(select.value).toBe('monkey'); // "" in Chrome
|
||||
// Despite this, the selection index is correct:
|
||||
expect(select.selectedIndex).toBe(-1);
|
||||
});
|
||||
|
||||
it('should remember value when switching to uncontrolled', () => {
|
||||
let stub = (
|
||||
<select value={'giraffe'} onChange={noop}>
|
||||
|
||||