Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the 'alwaysUseRequestIdleCallbackPolyfill' feature flag #12648

Merged
merged 3 commits into from
Apr 23, 2018

Conversation

flarnie
Copy link
Contributor

@flarnie flarnie commented Apr 19, 2018

Edit:

When we ran the tests, it became clear that tests were relying on the old version using rIC and the way that either our tests or Jest was polyfilling rIC.
In the new version of this scheduler, we rely on browser APIs which either aren't fully mocked or aren't mocked at all in the tests by default, so the tests failed.
This PR now updates all relevant tests to mock out the browser APIs used by ReactScheduler, and also adds an initial test for ReactScheduler to verify it works in the Jest test environment with the mocked browser APIs.
A follow-up PR will pull the mocking logic into a reusable helper


what is the change?:
Removes the feature flag 'alwaysUseRequestIdleCallbackPolyfill', such
that we always use the polyfill for requestIdleCallback.

why make this change?:
We have been testing this feature flag at 100% for some time internally,
and determined it works better for React than the native implementation.
Looks like RN was overriding the flag to use the native when possible,
but since no RN products are using 'async' mode it should be safe to
switch this flag over for RN as well.

test plan:
We have already been testing this internally for some time.

issue:
internal task t28128480

**what is the change?:**
Removes the feature flag 'alwaysUseRequestIdleCallbackPolyfill', such
that we **always** use the polyfill for requestIdleCallback.

**why make this change?:**
We have been testing this feature flag at 100% for some time internally,
and determined it works better for React than the native implementation.
Looks like RN was overriding the flag to use the native when possible,
but since no RN products are using 'async' mode it should be safe to
switch this flag over for RN as well.

**test plan:**
We have already been testing this internally for some time.

**issue:**
internal task t28128480
gaearon
gaearon previously approved these changes Apr 19, 2018
bvaughn
bvaughn previously approved these changes Apr 19, 2018
@pull-bot
Copy link

pull-bot commented Apr 19, 2018

ReactDOM: size: -0.1%, gzip: 0.0%

Details of bundled changes.

Comparing: 999b656...c65cc2f

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js -0.1% -0.1% 412.73 KB 412.36 KB 89.59 KB 89.52 KB UMD_DEV
react-art.production.min.js -0.1% 🔺+0.1% 90.48 KB 90.42 KB 27.62 KB 27.64 KB UMD_PROD
react-art.development.js -0.1% -0.1% 338.56 KB 338.19 KB 70.81 KB 70.74 KB NODE_DEV
react-art.production.min.js -0.1% 0.0% 55.04 KB 54.97 KB 16.87 KB 16.88 KB NODE_PROD
ReactART-dev.js -0.1% -0.1% 346.81 KB 346.39 KB 70.42 KB 70.34 KB FB_WWW_DEV
ReactART-prod.js -0.2% -0.3% 167.61 KB 167.25 KB 27.75 KB 27.68 KB FB_WWW_PROD

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js -0.0% -0.0% 609.63 KB 609.44 KB 140.53 KB 140.49 KB UMD_DEV
react-dom.production.min.js -0.1% 0.0% 100.2 KB 100.11 KB 31.97 KB 31.98 KB UMD_PROD
react-dom.development.js -0.0% -0.0% 594 KB 593.81 KB 136.37 KB 136.33 KB NODE_DEV
react-dom.production.min.js -0.1% 0.0% 98.65 KB 98.56 KB 31.1 KB 31.1 KB NODE_PROD
react-dom-server.browser.development.js -0.0% -0.0% 101.33 KB 101.33 KB 26.47 KB 26.47 KB UMD_DEV
react-dom-server.browser.development.js -0.0% -0.0% 90.63 KB 90.63 KB 24.21 KB 24.21 KB NODE_DEV
react-dom-server.node.development.js -0.0% -0.0% 92.55 KB 92.55 KB 24.76 KB 24.75 KB NODE_DEV
ReactDOM-dev.js -0.0% -0.0% 618.41 KB 618.18 KB 139.14 KB 139.1 KB FB_WWW_DEV
ReactDOM-prod.js -0.1% -0.2% 284.94 KB 284.52 KB 52.3 KB 52.21 KB FB_WWW_PROD
ReactDOMServer-dev.js -0.1% -0.1% 94.19 KB 94.1 KB 24.05 KB 24.02 KB FB_WWW_DEV

react-scheduler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-scheduler.development.js -11.0% -11.1% 12.07 KB 10.75 KB 4.17 KB 3.71 KB UMD_DEV
react-scheduler.production.min.js -5.3% -0.8% 1.83 KB 1.73 KB 913 B 906 B UMD_PROD
react-scheduler.development.js -11.2% -11.1% 11.88 KB 10.55 KB 4.11 KB 3.66 KB NODE_DEV
react-scheduler.production.min.js -5.7% -0.3% 1.91 KB 1.8 KB 927 B 924 B NODE_PROD

Generated by 🚫 dangerJS

@flarnie
Copy link
Contributor Author

flarnie commented Apr 19, 2018

Surprised to find that this does seem to cause tests to fail. So the first step in this project is to fix the current polyfill so that our tests pass.

@sebmarkbage
Copy link
Collaborator

RN uses ReactNativeFrameScheduling so I don't think this should matter for Fabric (which is or going to be async). 

@acdlite
Copy link
Collaborator

acdlite commented Apr 19, 2018

@flarnie It might not be a bug in the polyfill. Our tests have been running using these mocks:

global.requestAnimationFrame = function(callback) {
  setTimeout(callback);
};

global.requestIdleCallback = function(callback) {
  return setTimeout(() => {
    callback({
      timeRemaining() {
        return Infinity;
      },
    });
  });
};

which of course is not how the polyfill works :D

@acdlite
Copy link
Collaborator

acdlite commented Apr 19, 2018

There are also test modules like this one that mock requestIdleCallback so we can lazily flush rendering work:

// Override requestIdleCallback
scheduledCallback = null;
flush = function(units = Infinity) {
if (scheduledCallback !== null) {
let didStop = false;
while (scheduledCallback !== null && !didStop) {
const cb = scheduledCallback;
scheduledCallback = null;
cb({
timeRemaining() {
if (units > 0) {
return 999;
}
didStop = true;
return 0;
},
});
units--;
}
}
};
global.performance = {
now() {
return now;
},
};
global.requestIdleCallback = function(cb) {
scheduledCallback = cb;
};
now = 0;
expire = function(ms) {
now += ms;
};
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
AsyncMode = React.unstable_AsyncMode;

These will also need to be updated.

I don't like mocking, generally, but seems like we need the ability to mock the Scheduler module in some cases.

@flarnie
Copy link
Contributor Author

flarnie commented Apr 20, 2018

The failing test passes locally.

screen shot 2018-04-20 at 9 39 42 am

Looking into it more now.

@sebmarkbage
Copy link
Collaborator

If we mock the Scheduler, we can't run these as production tests ever which seems unfortunate. We should opt to mock the DOM environment if possible.

@flarnie
Copy link
Contributor Author

flarnie commented Apr 20, 2018

I could write mocks for rAF and postMessage, but we still need some way to trigger a 'flush' of those. Similar to our other tests for async, we need a way to control simulated time passing.

"we can't run these as production tests ever" - can you explain that more?

@gaearon
Copy link
Collaborator

gaearon commented Apr 20, 2018

@flarnie

The bundle tests are failing. You can reproduce by doing yarn build and then running yarn test-build ReactDOM-root.

We run the subset of tests on bundles themselves. This is beneficial to catch regression in our build process. As a last resort you can rename a test to -test.internal.js to opt out of bundle testing, but it would be better not to. Instead you could expose a fake method on the window global if necessary for testing. This would work with bundles too.

@flarnie
Copy link
Contributor Author

flarnie commented Apr 20, 2018

Ah - I think I understand now what you meant @sebmarkbage by "we can't run these as production tests ever". Let me verify;

The test which uses the bundle fails because you can't mock out dependencies which are already rolled up into a bundle. Calling jest.mock and jest.resetModules is not effective in that case. Running test "as production tests" means testing a production bundle.

Let me know if I'm missing something. I think I can make this work with some clever mocking of rAF/postMessage and writing a 'flush' helper that coordinates with the mocks.

**what is the change?:**
- In all tests where we previously mocked rIC or relied on native
mocking which no longer works, we are now mocking rAF and postMessage.
- Also adds a basic initial test for ReactScheduler.
NOTE -> we do plan to write headless browser tests for ReactScheduler!
This is just an initial test, to verify that it works with the mocked
out browser APIs as expected.

**why make this change?:**
We need to mock out the browser APIs more completely for the new
'ReactScheduler' to work in our tests. Many tests are depending on it,
since it's used at a low level.

By mocking the browser APIs rather than the 'react-scheduler' module, we
enable testing the production bundles. This approach is trading
isolation for accuracy. These tests will be closer to a real use.

**test plan:**
run the tests :)

**issue:**
internal task T28128480
@flarnie flarnie force-pushed the removeFlagForRICPolyfilling branch from b0f1903 to c65cc2f Compare April 21, 2018 00:17
@flarnie
Copy link
Contributor Author

flarnie commented Apr 21, 2018

Added a new approach - just mocking browser APIs. This is a significant change, would appreciate a review of the latest commit.

Thanks @sebmarkbage @acdlite @gaearon for the input so far.

@flarnie flarnie dismissed stale reviews from gaearon and bvaughn April 21, 2018 00:21

added new logic, latest commit needs review

@flarnie
Copy link
Contributor Author

flarnie commented Apr 23, 2018

pingy_dinghy

We're mocking the browser APIs instead of the module, so we can keep running tests on the production bundles. ☀️

@acdlite
Copy link
Collaborator

acdlite commented Apr 23, 2018

I don't understand the argument for mocking the browser APIs. That only seems useful if the mocks replicate the browser exactly. That's not how these mocks are written — if you ran these tests in the browser, without the mocks, the tests would fail. So then what's the point?

The reason I thought we should mock the scheduler APIs is so you can write async tests that lazily flush work without worrying about the actual details of how the work is flushed in a real environment. The same reason we have ReactNoop.

Copy link
Collaborator

@acdlite acdlite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh ok :D

@sebmarkbage
Copy link
Collaborator

@acdlite made a good point. If we treat the Scheduler as an API external to React, then that is the public API boundary. It is mockable even with bundled code. However, we should make sure it works with UMD bundles where the Scheduler is external too.

@flarnie
Copy link
Contributor Author

flarnie commented Apr 23, 2018

@acdlite made a good point. If we treat the Scheduler as an API external to React, then that is the public API boundary. It is mockable even with bundled code. However, we should make sure it works with UMD bundles where the Scheduler is external too.

Sounds good, I'd like to tackle that in a follow-up PR. Sounds like these are the action items:
[ ] Bring back the manual mock of ReactScheduler and get the test to pull it in, even when using the production React bundle.
[ ] Manually test the UMD bundles to make sure they work with the external ReactScheduler module.

Going to find one of you in person tomorrow to talk through this.

@flarnie flarnie merged commit 1e3cd33 into facebook:master Apr 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants