-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[RNMobile] Fix act
warnings that might be triggered after test finishes
#38052
[RNMobile] Fix act
warnings that might be triggered after test finishes
#38052
Conversation
This is a workaround for addressing the act warnings that might caused by store updates executed after the test finishes.
Size Change: 0 B Total Size: 1.13 MB ℹ️ View Unchanged
|
test/native/helpers.js
Outdated
// Some of the store updates that happen upon editor initialization are executed at the end of the current | ||
// Javascript block execution and after the test is finished. In order to prevent "act" warnings due to | ||
// this behavior, we wait for the execution block to be finished before acting on the test. | ||
act( | ||
() => new Promise( ( actResolve ) => setImmediate( actResolve ) ) | ||
).then( () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without full context of the origin issue, this solution comes across as somewhat "mask" for the core issue rather than a fix. That is not to say it is not a valid solution, I just hope to understand the issue itself as well.
Were you able to identify what specific update occurs? That context would be helpful to understanding the proposed fix. 🙇🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I totally understand your point, I'm actually gathering insights for providing a full context, I'll let you know when it's ready 🙇 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dcalhoun I added a Context
section in the PR's description regarding this topic, it's a bit hard to explain as it's related to asynchronicity and store updates, so let me know if you'd like me to expand anything.
Probably, the best way to have a better context is by modifying the code and adding some breakpoints, I referenced where I placed mine for testing (reference) in case it helps out, but feel free to test further in this regard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dcalhoun I added a
Context
section in the PR's description regarding this topic, it's a bit hard to explain as it's related to asynchronicity and store updates, so let me know if you'd like me to expand anything.
Fantastic! Thank you so much for taking the time to document this context. It is immensely helpful for me, and I imagine it will be for future readers as well.
Probably, the best way to have a better context is by modifying the code and adding some breakpoints, I referenced where I placed mine for testing (reference) in case it helps out, but feel free to test further in this regard.
Agreed. I will likely circle back to further explore this subject at some point. Specifically, thanks to the wonderful context you provided, I wonder if we might be able to wrap either the resolver promise and/or a manual tick of fake timers with act
to explicitly await the core asynchronous work. I.e. I question if Promise
+ setImmediate
is the most straightforward way of expressing what we are awaiting.
I do not share that thought to question the current implementation or imply that we should explore it now, but merely "thinking out loud."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 LGTM. Thank you for researching this complex topic and proposing a solution. I left one suggestion for consideration, and an other comment merely for discussion. Neither are blockers to merging this work.
I could likely spend a few more days exploring to better understand the issue myself, but I do not believe that should block the Jest 27 upgrade if you would like to move forward and merge this. 👍🏻
test/native/helpers.js
Outdated
// Some of the store updates that happen upon editor initialization are executed at the end of the current | ||
// Javascript block execution and after the test is finished. In order to prevent "act" warnings due to | ||
// this behavior, we wait for the execution block to be finished before acting on the test. | ||
act( | ||
() => new Promise( ( actResolve ) => setImmediate( actResolve ) ) | ||
).then( () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dcalhoun I added a
Context
section in the PR's description regarding this topic, it's a bit hard to explain as it's related to asynchronicity and store updates, so let me know if you'd like me to expand anything.
Fantastic! Thank you so much for taking the time to document this context. It is immensely helpful for me, and I imagine it will be for future readers as well.
Probably, the best way to have a better context is by modifying the code and adding some breakpoints, I referenced where I placed mine for testing (reference) in case it helps out, but feel free to test further in this regard.
Agreed. I will likely circle back to further explore this subject at some point. Specifically, thanks to the wonderful context you provided, I wonder if we might be able to wrap either the resolver promise and/or a manual tick of fake timers with act
to explicitly await the core asynchronous work. I.e. I question if Promise
+ setImmediate
is the most straightforward way of expressing what we are awaiting.
I do not share that thought to question the current implementation or imply that we should explore it now, but merely "thinking out loud."
Co-authored-by: David Calhoun <[email protected]>
The mobile unit tests began failing after pushing the last commit 😞 . That change only updated a comment so it's unrelated to the failure hence, this might be revealing that the solution provided is not stable. In fact, I restarted the CI job on the first commit that succeed and it also failed, so looks like we might have seen a false positive. However, so far I couldn't reproduce it locally, but in any case, I'll keep investigating the issue further, in case I can introduce a better fix 🔧 . |
I see that the test still fails again. This is a very complex issue as I read the description of the PR. In this particular case, would it help if we warm up the store by triggering the selectors earlier so they are already resolved when calling |
The function is now synchronous as we don't have to wait for any element.
…m:WordPress/gutenberg into rnmobile/fix/act-warnings-rich-text-tests # Conflicts: # test/native/helpers.js
Here are my findings after trying different approaches, in order to assure that we wait for the store updates that occur after the test finish:
UPDATE: This was the option that finally solved the issue 🎊 .
This means that, at least for the case that is failing, we could fix it just by manually unmounting the editor at the end of the test 🔧 . I applied this workaround in af1d8ca. |
This is an interesting solution, thanks @gziolo for the suggestion 🙇. I think it should work since the next time the selectors are called they will use the cached values. In any case, I already provided a workaround in af1d8ca that should solve it, but I'll be more than happy to use this one if we prefer it, I'd like to wait for @dcalhoun input regarding this. |
After checking the latest test output, I noticed that some tests from |
That sounds like a way to go, too. It's a bit strange though, that the React Testing Library expects wrapping |
* Leverage fake timers to resolve store resolvers During editor initialization, asynchronous store resolvers rely upon `setTimeout` to run at the end of the current JavaScript block execution. In order to prevent "act" warnings triggered by updates to the React tree, we leverage fake timers to manually tick and await the resolution of the current block execution before proceeding. * Update RichText test `initializeEditor` usage The refactor of `initializeEditor` to leverage fake timers means this test no longer needs to `await` asynchronous work or manuall `unmount` its subject component. * Refactor usage of `initializeEditor` in tests Now that `initializeEditor` leverages fake timers to resolve asynchronous store resolvers, the tests no longer need to await a resolution of `initializeEditor`. * Fix `act` warnings in Image edit test Retrieving text from the clipboard is an asynchronous action. The component updated React state once the clipboard resolved. This resulted in a state update triggering an `act` warning. Because there is no visible change to the rendered output, e.g. updated text or UI, we must await the resolution of the clipboard promise itself.
Thanks @dcalhoun for solving the issue 🎊, I've just approved and merged the PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 LGTM. Great work collaborating to find a solution. 👏🏻
* Testing: Upgrade Jest to v27 * Fix native test timeouts caused by combining fake timers and setImmediate (#37715) * Upgrade to @testing-library/[email protected] * Clean up fake timer usage after tests This may be unnecessary, but avoid potential issues where fake timers are unexpectedly used and cause breakages in other tests. * Enable combination of modern fake timers and waitFor Previously, `waitFor` would timeout when `jest.useFakeTimers('modern')` was enabled. The 'modern' version is now the default in Jest 27. The Jest preset from `@testing-library/react-native` provides a workaround for a larger issue in React Native and Jest that mutates the global `Promise` object. https://git.io/JSDZI * Remove global enabling of fake timers Enabling fake timers can have negative consequences with `waitFor`, e.g. causing unexpected timeouts. Enabling it globally is far-reaching and this should likely be enabled within individual tests as needed. * Replace jest-jasmine2 with jest-circus The latter is considered the successor to the former. We seemingly do not depend on anything explicitly provided by `jest-jasmine2` and should likely move on from it. * Switch testing environment from jsdom to node Improves speed of test environment setup and fixes a timeout issue when combining `waitFor` and `jest.useFakeTimers('modern')`. It is not yet exactly pinpointed as to _why_ this fixes the timeout issue. It appears to related to `setImmediate` and `setTimeout`. * Remove setImmediate global for testing environment This may be unnecessary if `testEnvironment: 'node'` is retained. However, most tests are currently broken due to missing DOM APIs. * Polyfill required DOM APIs for testing environment Now that `testEnvironment: 'node'` is utilized for the testing environment, we must mirror the app runtime and polyfill the necessary DOM APIs used in the source. The Enzyme configuration removed conflicts with the switch from `testEnvironment: 'jsdom'` to `testEnvironment: 'node'`. Enzyme depends upon `react-dom`, which introduces far more dependencies upon DOM APIs. Currently, all Enzyme-related tests fail and need to be replaced with `@testing-library/react-native`. * Avoid import of react-dom within native file Importing `react-dom` introduces additional dependencies upon the DOM API and is incompatible with `testEnvironment: 'node'`. The `act` utility is available from `@testing-library/react-native`. * Explicitly toggle fake timers in tests This may not be necessary, but may help avoid unexpected issues from lingering fake timers, e.g. timeout errors while using `waitFor`. * Reinstate legacy Jest timers The Jest preset from `@testing-library/react-native` that fixed support for "modern" timers by modifying the polyfilled the global `Promise` resulted in new failures from within React Native itself. Specifically, core React Native components rely upon `.done` from the `promise` package. `.done` is a non-standard method that does not exist on the global `Promise` used by `@testing-library/react-native`'s preset. * Disable erroneously failing test This previously passing test now fails after upgrading `@testing-library/react-native` due to changes in the library. Setting `pointerEvent` to "box-none" or "none" currently erroneously prevents triggering other events unrelated to pressing on the element, e.g. `onTouch*`, `onLayout`. https://git.io/JSHZt * Reinstate jest-jasmine2 to support done callback The Jest team create jest-circus as the predecessor for jest-jasmine2. The former does not support the `done` callback with async/await, and would appear to have no plans to do so. It would likely benefit us to refactor the one current test using `done` away from it, and embrace `jest-circus` to maintain alignment with Jest core. * Refactor native unit tests away from done callback The Jest team created `jest-circus` as the predecessor for `jest-jasmine2`. The former does not support the `done` callback with async/await, and would appear to have no plans to do so. In order to embrace `jest-circus` and maintain alignment with Jest core, the one test using `done` was refactored to avoid it - https://git.io/JSHWU - https://git.io/JSHWk * Remove Enzyme from Editor tests * Remove Enzyme from Paragraph tests * Remove Enzyme from BlockMover tests * Remove Enzyme from BlockEdit test * Remove Enzyme from LinksUI test * Remove Enzyme from ListEdit tests * Remove Enzyme from Platform tests * Remove Enzyme from BlockTypesTab tests * Remove Enzyme from HTMLTextInput tests * Remove Enzyme from ReusableBlockTab tests * Remove Enzyme from MediaUpload tests * Remove Enzyme from BlockMediaUpdateProgress tests * Remove Enzyme from MediaUploadProgress tests * Fix ReferencEerror in Inserter test Usage of `react-test-renderer` caused the following error. Leveraging `@testing-library/react-native` instead resolved it for an unknown reason. ``` ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From packages/block-editor/src/components/inserter/test/index.native.js. 468 | style: listStyle, 469 | safeAreaBottomInset, > 470 | scrollEnabled, | ^ 471 | automaticallyAdjustContentInsets: false, 472 | }; 473 | at Object.get PanResponder [as PanResponder] (node_modules/react-native/index.js:251:12) at BottomSheet.render (packages/components/src/mobile/bottom-sheet/index.native.js:470:39) at finishClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8459:31) at updateClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8409:24) at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9990:16) ``` * Fix ReferencEerror in Verse test Usage of `react-test-renderer` caused the following error. Leveraging `@testing-library/react-native` instead resolved it for an unknown reason. ``` ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From packages/block-editor/src/components/inserter/test/index.native.js. 468 | style: listStyle, 469 | safeAreaBottomInset, > 470 | scrollEnabled, | ^ 471 | automaticallyAdjustContentInsets: false, 472 | }; 473 | at Object.get PanResponder [as PanResponder] (node_modules/react-native/index.js:251:12) at BottomSheet.render (packages/components/src/mobile/bottom-sheet/index.native.js:470:39) at finishClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8459:31) at updateClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8409:24) at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9990:16) ``` * Fix ReferencEerror in Audio test Usage of `react-test-renderer` caused the following error. Leveraging `@testing-library/react-native` instead resolved it for an unknown reason. ``` ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From packages/block-editor/src/components/inserter/test/index.native.js. 468 | style: listStyle, 469 | safeAreaBottomInset, > 470 | scrollEnabled, | ^ 471 | automaticallyAdjustContentInsets: false, 472 | }; 473 | at Object.get PanResponder [as PanResponder] (node_modules/react-native/index.js:251:12) at BottomSheet.render (packages/components/src/mobile/bottom-sheet/index.native.js:470:39) at finishClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8459:31) at updateClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8409:24) at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9990:16) ``` * Fix ReferencEerror in File test Usage of `react-test-renderer` caused the following error. Leveraging `@testing-library/react-native` instead resolved it for an unknown reason. ``` ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From packages/block-editor/src/components/inserter/test/index.native.js. 468 | style: listStyle, 469 | safeAreaBottomInset, > 470 | scrollEnabled, | ^ 471 | automaticallyAdjustContentInsets: false, 472 | }; 473 | at Object.get PanResponder [as PanResponder] (node_modules/react-native/index.js:251:12) at BottomSheet.render (packages/components/src/mobile/bottom-sheet/index.native.js:470:39) at finishClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8459:31) at updateClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8409:24) at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9990:16) ``` * Fix ReferencEerror in Search test Usage of `react-test-renderer` caused the following error. Leveraging `@testing-library/react-native` instead resolved it for an unknown reason. ``` ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From packages/block-editor/src/components/inserter/test/index.native.js. 468 | style: listStyle, 469 | safeAreaBottomInset, > 470 | scrollEnabled, | ^ 471 | automaticallyAdjustContentInsets: false, 472 | }; 473 | at Object.get PanResponder [as PanResponder] (node_modules/react-native/index.js:251:12) at BottomSheet.render (packages/components/src/mobile/bottom-sheet/index.native.js:470:39) at finishClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8459:31) at updateClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8409:24) at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9990:16) ``` * Fix ReferencEerror in Missing test Usage of `react-test-renderer` caused the following error. Leveraging `@testing-library/react-native` instead resolved it for an unknown reason. ``` ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From packages/block-editor/src/components/inserter/test/index.native.js. 468 | style: listStyle, 469 | safeAreaBottomInset, > 470 | scrollEnabled, | ^ 471 | automaticallyAdjustContentInsets: false, 472 | }; 473 | at Object.get PanResponder [as PanResponder] (node_modules/react-native/index.js:251:12) at BottomSheet.render (packages/components/src/mobile/bottom-sheet/index.native.js:470:39) at finishClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8459:31) at updateClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8409:24) at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9990:16) ``` * Upgrade to @testing-library/[email protected] * Add assertions to block type tab tests Improve test clarity with explicit expect assertions. Co-authored-by: Carlos Garcia <[email protected]> * Remove unnecessary abstraction The switch away from Enzyme reduced this abstraction to a single line, so it provides less value now. * Consistently assert media block update progress spinner removal This increases consistency amongst the tests, as most already include this assertion. * Update MediaUpload test to select 'Choose from device' option This selection better aligns with the test description. * Remove duplicative matchMedia global definition The test environment now imports the globals setup file used by the app runtime. That file includes a `matchMedia` global definition as well. * Add assertions to LinkSettings tests Improve test clarity with explicit expect assertions. * Removed shallow renderer usage in tests Shallow rendering components is generally considered a non-optimal approach to testing React components by the React community. This replaces `shallow` with `render` to further test integration of the subject components. * Remove unused import The `React` import was utilized by the now removed `shallow` render implementation. * Remove unnecessary top-level beforeAll usage Jest runs each test file independently, so top-level code will not impact other test files. Since the `(before|after)All` usage in code changed is all top-level, and not within a `describe`, it is superfluous. Co-authored-by: Carlos Garcia <[email protected]> * Fix erroneous react-dom usage in native tests (#37921) * Reinstate react-platform.native.js file This file was removed in the following commit, as it was no longer strictly necessary. b7b62d2#diff-f02069349f238fb47a268bb7fcc03c9768331db18ead0e28d8ecad7bbc05037c However, the removal led to Jest loading the DOM-specific version of the file when mocking Gutenberg modules that depended upon `react-platform.js`. Jest would seemingly load `react-dom` when attempting to auto-mock a module, e.g. `jest.mock( '@wordpress/data/src/components/use-select' );`. The loading of `react-dom` resulted in the following error: ``` TypeError: Cannot use 'in' operator to search for 'WebkitAnimation' in undefined at getVendorPrefixedEventName (node_modules/react-dom/cjs/react-dom.development.js:5011:58) at node_modules/react-dom/cjs/react-dom.development.js:5019:21 at Object.<anonymous> (node_modules/react-dom/cjs/react-dom.development.js:26261:5) at Object.<anonymous> (node_modules/react-dom/index.js:37:20) ``` Reinstating `react-platform.native.js` addresses this issue by ensuring that Jest does not encounter an import of `react-dom`. * Fix false-positive ReactNativeEditor test This test failed when run in isolation. It would appear it was dependent upon the `jest.mock('../setup')` found in sibling tests. After defining a mock for this specific test, it was discovered that the assertion did not await the asynchronous query found within the test. The assertion resulted in a false-positive as the returned query `Promise` technically matches `toBeDefined`. `async`/`await` was added to ensure the test assertion awaits the query result, as well as fixes the following related log warning. ``` A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them. ``` * [RNMobile] Fix `act` warnings that might be triggered after test finishes (#38052) * Fix act warnings after test finishes This is a workaround for addressing the act warnings that might caused by store updates executed after the test finishes. * Update comment of setImmediate workaround Co-authored-by: David Calhoun <[email protected]> * Unmount editor when test finish * Update initialize editor helper The function is now synchronous as we don't have to wait for any element. * Update tests that use initialize editor * Fix act warnings from store resolvers with fake timers (#38077) * Leverage fake timers to resolve store resolvers During editor initialization, asynchronous store resolvers rely upon `setTimeout` to run at the end of the current JavaScript block execution. In order to prevent "act" warnings triggered by updates to the React tree, we leverage fake timers to manually tick and await the resolution of the current block execution before proceeding. * Update RichText test `initializeEditor` usage The refactor of `initializeEditor` to leverage fake timers means this test no longer needs to `await` asynchronous work or manuall `unmount` its subject component. * Refactor usage of `initializeEditor` in tests Now that `initializeEditor` leverages fake timers to resolve asynchronous store resolvers, the tests no longer need to await a resolution of `initializeEditor`. * Fix `act` warnings in Image edit test Retrieving text from the clipboard is an asynchronous action. The component updated React state once the clipboard resolved. This resulted in a state update triggering an `act` warning. Because there is no visible change to the rendered output, e.g. updated text or UI, we must await the resolution of the clipboard promise itself. Co-authored-by: David Calhoun <[email protected]> Co-authored-by: David Calhoun <[email protected]> Co-authored-by: Carlos Garcia <[email protected]>
While working on adding new tests, I bumped again into this issue, I've opened this issue outlining what I found so far. I'll try to figure out if we can provide a new workaround for this case 🔧 . |
Related to #33287 (comment).
Description
The approach for addressing the
act
warnings in this PR is simply to wait for the Javascript block execution to finish when the editor is initialized. This way we assure that no store updates, at least the ones related to the editor initialization, are executed after the test finish potentially leading to logact
warnings and making tests fail when using@wordpress/jest-console
package.Context
When the editor is initialized, specifically the
EditorProvider
component, a set of selectors are called viawithSelect
(example). Some of these selectors, like the referenced on in the examplegetEditorBlocks
, require to be resolved which is done asynchronously, but due to the fact that the API requests are mocked, this results on being executed at the end of execution block because it uses asetTimeout
function with 0 duration.As part of the resolution process, two actions
startResolution
andfinishResolution
are dispatched, which updates the store. Due to this behavior, and since they happen after the test finishes, it leads to theact
warnings that we often identify in the output.I added a breakpoint in the logic in charge of creating and executing the resolvers (reference), in order to provide a step by step explanation within the stack trace:
Click here to display screenshots
How has this been tested?
npm run native test
Screenshots
N/A
Types of changes
Bug fix
Checklist:
*.native.js
files for terms that need renaming or removal).