Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix: Requireawait act(...)#1214

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
eps1lon wants to merge1 commit intotesting-library:main
base:main
Choose a base branch
Loading
fromeps1lon:fix/microtask-missing-act

Conversation

eps1lon
Copy link
Member

@eps1loneps1lon commentedMay 22, 2023
edited
Loading

This is a WIP. Changes are described as they are discovered.

Codemod:https://github.com/eps1lon/codemod-missing-await-act
eslint-plugin-testing-library update:testing-library/eslint-plugin-testing-library#984

TODO:

  • update reference docs (and write FAQ for "How to test loading states"

fixes to missing act warnings

Sometimes you wanted to assert on a given commit of the component without caring about scheduled tasks. This always worked since these scheduled tasks were on the macrotask queue (e.g.setTimeout) because we automatically unmounted the component after the test ended.
However, when these tasks where on the microtasks queue (e.g. a promise that just resolved), you would get a missing act warning between the test ending and our automatic cleanup running.

The only solution would've been to add an explicit unmount toeach test.

Now that we requireawait act(...) these issues will no longer persist since we'll also flush the state updates on the microtask queue. If you want to continue asserting on the intermediate states, you can useglobalThis.IS_REACT_ACT_ENVIRONMENT = false. Keep in mind though that while this flag is set, you have to manually wait for every state update to be applied e.g. by usingfindBy orwaitFor.

Timer changes after module initialization

Drops support for changing timers after module initialization.
This affects tests that changed the timer implementation (i.e. fake vs real timers) after modules were imported. For example, in the following test, updates are no longer flushed.

import{render,screen}from"@testing-library/react";import{useState}from"react";jest.useFakeTimers;test("updates",async()=>{functionComponent(){const[state,setState]=useState("initial");useEffect(()=>{consttimeoutID=setTimeout(()=>{setState("committed");},50);return()=>{clearTimeout(timeoutID);};},[]);returnstate;}const{ container}=awaitrender(<Component/>);// will timeoutawaitwaitFor(()=>{expect(container.textContent).toEqual("committed");});});

Instead, you have to decide on timersbefore you import modules. In Jest, you can choose timers at the config level, or keep usingjest.useFaketimers() but call it before anyimport orrequire.

This only ever worked because we cheated a bit and flipped silently to an act environment duringwaitFor. However, since we now flush a bit more when usingIS_REACT_ACT_ENVIRONMENT, we need to offer APIs to assert on intermediate states e.g. when your component initially displays a loading spinner that goes away in the next microtask. You can do this by simply settingglobalThis.IS_REACT_ACT_ENVIRONMENT = false and continue to use existing APIs. React will fallback to standard (real or fake) timers and not the act queue.

Full explainer will follow. Change will be accompanied by a codemod:https://github.com/eps1lon/codemod-missing-await-act

Fixes#1385

atshakil, KevinBon, samtsai, carolinapowers, Janpot, tpict, and tkrotoff reacted with rocket emoji
@codesandbox-ci
Copy link

codesandbox-cibot commentedMay 22, 2023
edited
Loading

This pull request is automatically built and testable inCodeSandbox.

To see build info of the built libraries, clickhere or the icon next to each commit SHA.

Latest deployment of this branch, based on commit81bd3f6:

SandboxSource
react-testing-library-examplesConfiguration

@eps1loneps1lonforce-pushed thefix/microtask-missing-act branch 2 times, most recently from5337ab0 to686a5baCompareMay 22, 2023 15:10
@codecov
Copy link

codecovbot commentedMay 22, 2023
edited
Loading

Codecov Report

Merging#1214 (f676301) intoalpha (c04b8f0) willnot change coverage.
The diff coverage is100.00%.

@@            Coverage Diff            @@##             alpha     #1214   +/-   ##=========================================  Coverage   100.00%   100.00%           =========================================  Files            4         4             Lines          192       155   -37       Branches        38        30    -8     =========================================- Hits           192       155   -37
FlagCoverage Δ
canary100.00% <100.00%> (ø)
experimental100.00% <100.00%> (ø)
latest100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown.Click here to find out more.

FilesCoverage Δ
src/act-compat.js100.00% <100.00%> (ø)
src/fire-event.js100.00% <100.00%> (ø)
src/index.js100.00% <100.00%> (ø)
src/pure.js100.00% <100.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times.Learn more

@MatanBobi
Copy link
Member

@eps1lon any chance for an explainer on this one please? AFAIR,async act exists for a few years now, why do we need toawait it now? Thanks!

@kentcdodds
Copy link
Member

I always knew this day would come 😅 Everything we do in tests really should be async.

nickserv, KevinBon, and alexkrolick reacted with thumbs up emoji

@eps1lon
Copy link
MemberAuthor

There's a reason why we should do this now and a React specific reason that why we may need to do it in the future.

For now consider this test:

functionApp(){const[ctr,setCtr]=React.useState(0);asyncfunctionsomeAsyncFunction(){// queue a promises to be sure they all flushawaitnull;setCtr(1);}React.useEffect(()=>{someAsyncFunction();},[]);returnctr;}act(()=>{render(<App/>,container);});expect(container.innerHTML).toEqual("1");

based onhttps://github.com/facebook/react/blob/a21d1475ffd7225a463f2d0c0c9b732c8dd795eb/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js#L476-L493

That test will log missing act warnings unless you unmount the treebefore you exit your test i.e. we'd have to tell people to unmount in each test instead of relying on a singleafterEach(cleanup). That's a footgun we can avoid by requiringawait act().

I'm testing this internally first to see how it behaves across Native and Web and to check we cover most scenarios with a codemod (because that's required to land this now). Once that's done I write out a full explainer.

nickserv reacted with thumbs up emoji

@kentcdodds
Copy link
Member

I'm not sure that example is a good justification honestly. Normally if you're setting state there is some visual indication of what state you just set and I would recommend people to make an assertion based on the UI that changed from that state update which would typically result in requiring a query which is wrapped in act. Can you show me a example where that wouldn't be a solution?

@kentcdodds
Copy link
Member

I'll add that not always will you have some visual indication that some state changed, but the alternative to a visual indication is typically a function call which can be mocked and the assertion can be inside a waitFor which is also wrapped in act.

@eps1lon
Copy link
MemberAuthor

We already don't let people assert between what was committed from render and what was committed fromuseEffect. If that is what we want, we need to go back and redesign act. For now, the "missing act" warning is a huge turnoff we need a solution for that scales.

@kentcdodds
Copy link
Member

True, but when we're talking about something happening asynchronously (even if triggered by useEffect) that is currently possible and understandable. I'm not convinced this is justified.

@MatanBobi
Copy link
Member

MatanBobi commentedJun 18, 2023
edited
Loading

True, but when we're talking about something happening asynchronously (even if triggered by useEffect) that is currently possible and understandable. I'm not convinced this is justified.

Just to see I understood, what we're talking about for the example@eps1lon posted is something like this?

functionApp(){const[ctr,setCtr]=React.useState(0);asyncfunctionsomeAsyncFunction(){// queue a promises to be sure they all flushawaitnull;setCtr(1);}React.useEffect(()=>{someAsyncFunction();},[]);returnctr;}test("Should show 1",()=>{act(()=>{render(<App/>,container);});awaitwaitFor(()=>{expect(container.innerHTML).toEqual("1");})})

Because this seems to me like a reasonable usage ofwaitFor, the problem is that people assume they won't need to because we're wrapping all interactions (including render and unmount) withact that should flush all effects.

@SimpleCookie
Copy link

SimpleCookie commentedAug 3, 2023
edited
Loading

I don't feel like I am really eligible to provide very valuable feedback. But I found this PR trying to wrap my head better aroundact() especially in combination with Promises. We just switched from CRA to Vite and had to add Jest manually (with RTL), while we did we encountered a ton of new bugs/warnings in tests (not in the actual code). Previously we didn't really need to useact(), but now we have to use it everywhere on user interactions such asUserEvent.click(), UserEvent.type etc.

Debugging and understanding these errors is by far the biggest problem I see with testing react apps. They're always really difficult to debug, understand and solve. Sometimes the act-warnings also only appear when you are running multiple tests at once in parallel which makes it all next to impossible.

TLDR:
I like the idea of enforcing correct usage of act, because I'm struggling today.
I encounter theact(...) warnings sometimes when tests run in parallel, but rerunning the test does not always guarantee the warning will appear again. I.e. the warnings feel inconsistent.

Edit: Clarifications

@MatanBobi
Copy link
Member

MatanBobi commentedSep 9, 2023
edited
Loading

@eps1lon do we want to move on with this by pushing it to an alpha branch?

nickserv reacted with thumbs up emoji

@nickserv
Copy link
Member

Agreed, that would also give us time to update DTL or our supported Node versions before a major release (seetesting-library/dom-testing-library#1255).

@eps1lon
Copy link
MemberAuthor

I have a bit of time start of October where I want to revisit this. So testing how good codemod coverage is. Testing react-dom and react-native tests. Checking if we want this in React core and then we advance to alpha.

I don't see this blocking Node.js 18 bump. But I would also not force the Node.js 18 bump unless it is necessary or enables some new feature.

@nickserv
Copy link
Member

nickserv commentedSep 12, 2023
edited
Loading

But I would also not force the Node.js 18 bump unless it is necessary or enables some new feature.

It's necessary for security.Node 16 is intentionally ending support early because its version of OpenSSL is no longer supported, so we should update our Node versions as soon as we can. Fortunately we're already making progress onDTL's major release dropping Node 16 (currently in alpha), so I'd also like to start migrating RTL and our other libraries to use DTL 10 with Node 18+.

If this PR isn't ready by then, I'd still support releasing in another major. By then I might want to make some changes to support#1209 anyway.

@kentcdodds
Copy link
Member

@eps1lon do we want to move on with this by pushing it to an alpha branch?

I'm going to be recording testing videos for EpicWeb.dev starting tomorrow and I would love to use the alpha for these videos to make sure I'm teaching what will be available when EpicWeb.dev is launched. So yeah, an alpha release would be great for me personally :)

nickserv reacted with thumbs up emoji

@nickserv
Copy link
Member

nickserv commentedSep 12, 2023
edited
Loading

@kentcdodds Sounds like a good idea! I suggest we continue working on the other breaking changes for RTL (portingtesting-library/dom-testing-library#1255 and then updating the dependency on DTL) inalpha (we're already using that channel in DTL).@eps1lon can work on this when he's ready, but if@kentcdodds needs a build sooner and the other breaking changes aren't ready, we can merge this into another release branch.

Copy link
Collaborator

@alexkrolickalexkrolick left a comment

Choose a reason for hiding this comment

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

FWIW, making render async and wrapping it with act() by default is also the solution I came up with in my current project (on React 17) to get stable and consistent first assertions in tests

nickserv reacted with thumbs up emoji
@kentcdodds
Copy link
Member

@eps1lon, what are the chances this will actually land in React Testing Library do you think?

I'll be recording videos tomorrow that I would love to not have to re-record. I can use smoke and mirrors to record these videos with asyncrender and stuff, but if you're uncertain this will land by October then maybe I should not do that. So yeah, what do you think?

On my end, like I said I've always felt like this was inevitable we'd end up doing this anyway, so I'm in favor.

nickserv reacted with thumbs up emojiSudoCat reacted with eyes emoji

@eps1loneps1lonforce-pushed thefix/microtask-missing-act branch fromb156f87 tob5cf504CompareOctober 2, 2023 15:20
@eps1loneps1lon changed the base branch frommain toalphaOctober 2, 2023 15:20
: typeof reactAct
exportfunction act<T>(scope: () => T): Promise<T>

export * from '@testing-library/dom'
Copy link
MemberAuthor

@eps1loneps1lonOct 3, 2023
edited
Loading

Choose a reason for hiding this comment

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

Add comment here and tosrc/pure.js explaining why export order is important.

iirc we had a discussion around this with Rollup people and this was actually specified behavior:first export wins not last like you would expect if it would work like object spreading i.e.export * from ...; export { fireEvent } is not equivalent tomodule.exports = { ...dtlExports, fireEvent } Read next comment to understand which export is used.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Found it:#790 (comment)

TL;DR: By spec, the localfireEvent should be preferred. Babel specifically transpiles theexport * so that the localfireEvent is inexports not the one from@testing-library/dom.

So this is a TS quirk we have to work around. I try to create a repro to confirm I didn't hallucinate.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Reverting for now. Local checkout was pointing to the fireEvent from /dom but this may have been the language server having an outdated install. Repros do match spec behavior.

try {
const result = await cb()
// Drain microtask queue.
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Check if we added a test for this.

@eps1loneps1lonforce-pushed thefix/microtask-missing-act branch fromf676301 to4fd9f05CompareSeptember 20, 2024 09:14
@eps1loneps1lon changed the base branch fromalpha tomainSeptember 20, 2024 09:23
@eps1loneps1lonforce-pushed thefix/microtask-missing-act branch 2 times, most recently fromb038f70 todd27bd9CompareSeptember 20, 2024 10:38
@eps1loneps1lon mentioned this pull requestNov 25, 2024
@eps1loneps1lonforce-pushed thefix/microtask-missing-act branch 4 times, most recently from4567a0d to6440220CompareJanuary 16, 2025 00:05
@eps1loneps1lonforce-pushed thefix/microtask-missing-act branch from6440220 to7669700CompareJanuary 27, 2025 19:37
@eps1loneps1lonforce-pushed thefix/microtask-missing-act branch from7669700 to81bd3f6CompareJanuary 29, 2025 18:58
@Codexphere92
Copy link

hi@eps1lon! are there any updates for the status of this PR? I would love to contribute if needed. It looks like this is blocking people from updating to react 19 and there is no clear direction on what needs to be done in the meantime.

tkrotoff, JoeBernardi, SimpleCookie, akellbl4, miketheodorou, dcheng1290, dlindenkreuz, carbonrobot, karmeleon, and zerocewl reacted with thumbs up emojizerocewl and Stainless2k reacted with eyes emoji

@mdjastrzebski
Copy link

@eps1lon any progress on this? Users of React Native TL reported the same issue (callstack/react-native-testing-library#1787) and I wanted to coordinate the API with the RTL.

Seems like introducing asyncrender,fireEvent, etc is the technically sound solution. That would be a breaking change, and it's only needed if tested components use suspense. What do you think about addingrenderAsync,fireEventAsync etc APIs for such cases, and leaving the original sync cases as is?

winghouchan reacted with eyes emoji

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@alexkrolickalexkrolickalexkrolick left review comments

@markmssdmarkmssdmarkmssd approved these changes

At least 1 approving review is required to merge this pull request.

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

render does notawait act() Receiving 'event was not wrapped in act' warning after upgrading to React 18
9 participants
@eps1lon@MatanBobi@kentcdodds@SimpleCookie@nickserv@Codexphere92@mdjastrzebski@alexkrolick@markmssd

[8]ページ先頭

©2009-2025 Movatter.jp