diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsAct.js b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js index 369981f18a..77fd1779ee 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtilsAct.js +++ b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js @@ -126,8 +126,9 @@ export function unstable_concurrentAct(scope: () => Thenable | void) { } function flushActWork(resolve, reject) { - // TODO: Run timers to flush suspended fallbacks - // jest.runOnlyPendingTimers(); + // Flush suspended fallbacks + // $FlowFixMe: Flow doesn't know about global Jest object + jest.runOnlyPendingTimers(); enqueueTask(() => { try { const didFlushWork = Scheduler.unstable_flushAllWithoutAsserting(); diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index f04c5479da..386d040e90 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -1175,8 +1175,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { } function flushActWork(resolve, reject) { - // TODO: Run timers to flush suspended fallbacks - // jest.runOnlyPendingTimers(); + // Flush suspended fallbacks + // $FlowFixMe: Flow doesn't know about global Jest object + jest.runOnlyPendingTimers(); enqueueTask(() => { try { const didFlushWork = Scheduler.unstable_flushAllWithoutAsserting(); diff --git a/packages/react-reconciler/src/__tests__/ReactBlocks-test.js b/packages/react-reconciler/src/__tests__/ReactBlocks-test.js index 0d28a33599..18971c18a0 100644 --- a/packages/react-reconciler/src/__tests__/ReactBlocks-test.js +++ b/packages/react-reconciler/src/__tests__/ReactBlocks-test.js @@ -14,6 +14,7 @@ let useState; let Suspense; let block; let readString; +let resolvePromises; let Scheduler; describe('ReactBlocks', () => { @@ -28,15 +29,16 @@ describe('ReactBlocks', () => { useState = React.useState; Suspense = React.Suspense; const cache = new Map(); + let unresolved = []; readString = function(text) { let entry = cache.get(text); if (!entry) { entry = { promise: new Promise(resolve => { - setTimeout(() => { + unresolved.push(() => { entry.resolved = true; resolve(); - }, 100); + }); }), resolved: false, }; @@ -47,6 +49,12 @@ describe('ReactBlocks', () => { } return text; }; + + resolvePromises = () => { + const res = unresolved; + unresolved = []; + res.forEach(r => r()); + }; }); // @gate experimental @@ -144,7 +152,7 @@ describe('ReactBlocks', () => { expect(ReactNoop).toMatchRenderedOutput('Loading...'); await ReactNoop.act(async () => { - jest.advanceTimersByTime(1000); + resolvePromises(); }); expect(ReactNoop).toMatchRenderedOutput(Name: Sebastian); @@ -291,7 +299,7 @@ describe('ReactBlocks', () => { ReactNoop.render(); }); await ReactNoop.act(async () => { - jest.advanceTimersByTime(1000); + resolvePromises(); }); expect(ReactNoop).toMatchRenderedOutput(Name: Sebastian); }); @@ -336,7 +344,7 @@ describe('ReactBlocks', () => { }); await ReactNoop.act(async () => { _setSuspend(false); - jest.advanceTimersByTime(1000); + resolvePromises(); }); expect(ReactNoop).toMatchRenderedOutput(Sebastian); }); diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js index 13d38e992c..cbcdf8b4f6 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js @@ -575,7 +575,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { {shouldSuspend ? ( - + ) : ( )} @@ -2595,7 +2595,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { } return ( }> - + ); } @@ -2616,8 +2616,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { }); // Later we load the data. - Scheduler.unstable_advanceTime(5000); - await advanceTimers(5000); + await resolveText('A'); expect(Scheduler).toHaveYielded(['Promise resolved [A]']); expect(Scheduler).toFlushAndYield(['A']); expect(ReactNoop.getChildren()).toEqual([span('A')]); @@ -2635,8 +2634,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { }); // Later we load the data. - Scheduler.unstable_advanceTime(3000); - await advanceTimers(3000); + await resolveText('B'); expect(Scheduler).toHaveYielded(['Promise resolved [B]']); expect(Scheduler).toFlushAndYield(['B']); expect(ReactNoop.getChildren()).toEqual([span('B')]); @@ -2754,12 +2752,14 @@ describe('ReactSuspenseWithNoopRenderer', () => { ); }); + // TODO: This test is specifically about avoided commits that suspend for a + // JND. We may remove this behavior. it("suspended commit remains suspended even if there's another update at same expiration", async () => { // Regression test function App({text}) { return ( - + ); } @@ -2768,34 +2768,28 @@ describe('ReactSuspenseWithNoopRenderer', () => { await ReactNoop.act(async () => { root.render(); }); + expect(Scheduler).toHaveYielded(['Suspend! [Initial]']); // Resolve initial render await ReactNoop.act(async () => { - Scheduler.unstable_advanceTime(2000); - await advanceTimers(2000); + await resolveText('Initial'); }); - expect(Scheduler).toHaveYielded([ - 'Suspend! [Initial]', - 'Promise resolved [Initial]', - 'Initial', - ]); + expect(Scheduler).toHaveYielded(['Promise resolved [Initial]', 'Initial']); expect(root).toMatchRenderedOutput(); - // Update. Since showing a fallback would hide content that's already - // visible, it should suspend for a bit without committing. await ReactNoop.act(async () => { + // Update. Since showing a fallback would hide content that's already + // visible, it should suspend for a JND without committing. root.render(); - expect(Scheduler).toFlushAndYield(['Suspend! [First update]']); + // Should not display a fallback expect(root).toMatchRenderedOutput(); - }); - // Update again. This should also suspend for a bit. - await ReactNoop.act(async () => { + // Update again. This should also suspend for a JND. root.render(); - expect(Scheduler).toFlushAndYield(['Suspend! [Second update]']); + // Should not display a fallback expect(root).toMatchRenderedOutput(); }); @@ -3989,9 +3983,6 @@ describe('ReactSuspenseWithNoopRenderer', () => { await ReactNoop.act(async () => { root.render(); }); - // TODO: `act` should have already flushed the placeholder, so this - // runAllTimers call should be unnecessary. - jest.runAllTimers(); expect(Scheduler).toHaveYielded(['Suspend! [Async]', 'Loading...']); expect(root).toMatchRenderedOutput( <> diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 616e38faaa..899f4c6542 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -684,8 +684,9 @@ function unstable_concurrentAct(scope: () => Thenable | void) { } function flushActWork(resolve, reject) { - // TODO: Run timers to flush suspended fallbacks - // jest.runOnlyPendingTimers(); + // Flush suspended fallbacks + // $FlowFixMe: Flow doesn't know about global Jest object + jest.runOnlyPendingTimers(); enqueueTask(() => { try { const didFlushWork = Scheduler.unstable_flushAllWithoutAsserting(); diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js index 4eae89839a..9f1d74176a 100644 --- a/scripts/rollup/validate/eslintrc.cjs.js +++ b/scripts/rollup/validate/eslintrc.cjs.js @@ -42,6 +42,7 @@ module.exports = { // jest expect: true, + jest: true, }, parserOptions: { ecmaVersion: 5, diff --git a/scripts/rollup/validate/eslintrc.cjs2015.js b/scripts/rollup/validate/eslintrc.cjs2015.js index 81383a5964..d13058a61c 100644 --- a/scripts/rollup/validate/eslintrc.cjs2015.js +++ b/scripts/rollup/validate/eslintrc.cjs2015.js @@ -42,6 +42,7 @@ module.exports = { // jest expect: true, + jest: true, }, parserOptions: { ecmaVersion: 2015, diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js index 9c9f6b780a..36f4877efd 100644 --- a/scripts/rollup/validate/eslintrc.fb.js +++ b/scripts/rollup/validate/eslintrc.fb.js @@ -36,6 +36,9 @@ module.exports = { // Flight Uint8Array: true, Promise: true, + + // jest + jest: true, }, parserOptions: { ecmaVersion: 5, diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js index 4fb8181ae7..3e7027f351 100644 --- a/scripts/rollup/validate/eslintrc.rn.js +++ b/scripts/rollup/validate/eslintrc.rn.js @@ -31,6 +31,9 @@ module.exports = { ArrayBuffer: true, TaskController: true, + + // jest + jest: true, }, parserOptions: { ecmaVersion: 5, diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js index e3786ffd3b..933c1c9d73 100644 --- a/scripts/rollup/validate/eslintrc.umd.js +++ b/scripts/rollup/validate/eslintrc.umd.js @@ -43,6 +43,9 @@ module.exports = { // Flight Webpack __webpack_chunk_load__: true, __webpack_require__: true, + + // jest + jest: true, }, parserOptions: { ecmaVersion: 5,