user-event icon indicating copy to clipboard operation
user-event copied to clipboard

Setting focus gives act() error

Open ysulyma opened this issue 2 years ago • 4 comments

  • @testing-library/react version: 14.0.0
  • Testing Framework and version: Jest 29.5.0
  • DOM Environment: jest-environment-jsdom 29.5.0

What you did:

I have a Material UI Autocomplete, and am using a Radix UI AlertDialog to add new options. In my initial implementation, the Enter key didn't work to open the dialog. After fixing that, I wrote a test to verify that Enter behaves as expected. This resulted in a massive error message about state updates not being wrapped in act().

Relevant code is in https://github.com/ysulyma/testing-library-focus-repro/blob/main/tests/repro.test.tsx

What happened:

act-repro% jest
  console.error
    Warning: An update to ForwardRef(Autocomplete) inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
    
    This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at Autocomplete (/Users/yuri/dev/act-repro/node_modules/@mui/material/node/Autocomplete/Autocomplete.js:381:44)
        at DebugComponent (/Users/yuri/dev/act-repro/tests/repro.test.tsx:21:27)

      at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30)
      at error (node_modules/react-dom/cjs/react-dom.development.js:60:7)
      at warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9)
      at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5)
      at dispatchSetState (node_modules/react-dom/cjs/react-dom.development.js:17527:7)
      at handleBlur (node_modules/@mui/base/node/useAutocomplete/useAutocomplete.js:767:5)
      at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:4164:14)
      at HTMLUnknownElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17)
      at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34)
      at Object.invokeGuardedCallbackDev (node_modules/react-dom/cjs/react-dom.development.js:4213:16)
      at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:4277:31)
      at invokeGuardedCallbackAndCatchFirstError (node_modules/react-dom/cjs/react-dom.development.js:4291:25)
      at executeDispatch (node_modules/react-dom/cjs/react-dom.development.js:9041:3)
      at processDispatchQueueItemsInOrder (node_modules/react-dom/cjs/react-dom.development.js:9073:7)
      at processDispatchQueue (node_modules/react-dom/cjs/react-dom.development.js:9086:5)
      at dispatchEventsForPlugins (node_modules/react-dom/cjs/react-dom.development.js:9097:3)
      at node_modules/react-dom/cjs/react-dom.development.js:9288:12
      at batchedUpdates$1 (node_modules/react-dom/cjs/react-dom.development.js:26140:12)
      at batchedUpdates (node_modules/react-dom/cjs/react-dom.development.js:3991:12)
      at dispatchEventForPluginEventSystem (node_modules/react-dom/cjs/react-dom.development.js:9287:3)
      at dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay (node_modules/react-dom/cjs/react-dom.development.js:6465:5)
      at dispatchEvent (node_modules/react-dom/cjs/react-dom.development.js:6457:5)
      at dispatchDiscreteEvent (node_modules/react-dom/cjs/react-dom.development.js:6430:5)
      at HTMLDivElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3)
      at HTMLInputElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9)
      at Object.exports.fireFocusEventWithTargetAdjustment (node_modules/jsdom/lib/jsdom/living/helpers/focusing.js:103:10)
      at HTMLButtonElementImpl.focus (node_modules/jsdom/lib/jsdom/living/nodes/HTMLOrSVGElement-impl.js:55:16)
      at HTMLButtonElement.focus (node_modules/jsdom/lib/jsdom/living/generated/HTMLElement.js:117:34)
      at focus (node_modules/@radix-ui/react-alert-dialog/dist/packages/react/alert-dialog/src/AlertDialog.tsx:133:34)
      at Object.ourEventHandler [as current] (node_modules/@radix-ui/primitive/dist/packages/core/primitive/src/primitive.tsx:10:14)
      at HTMLDivElement.<anonymous> (node_modules/@radix-ui/react-use-callback-ref/dist/packages/react/use-callback-ref/src/useCallbackRef.tsx:15:44)
      at HTMLDivElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3)
      at HTMLDivElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9)
      at HTMLDivElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17)
      at HTMLDivElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34)
      at dispatchEvent (node_modules/@radix-ui/react-focus-scope/dist/packages/react/focus-scope/src/FocusScope.tsx:112:19)
      at commitHookEffectListMount (node_modules/react-dom/cjs/react-dom.development.js:23150:26)
      at commitPassiveMountOnFiber (node_modules/react-dom/cjs/react-dom.development.js:24931:11)
      at commitPassiveMountEffects_complete (node_modules/react-dom/cjs/react-dom.development.js:24891:9)
      at commitPassiveMountEffects_begin (node_modules/react-dom/cjs/react-dom.development.js:24878:7)
      at commitPassiveMountEffects (node_modules/react-dom/cjs/react-dom.development.js:24866:3)
      at flushPassiveEffectsImpl (node_modules/react-dom/cjs/react-dom.development.js:27039:3)
      at flushPassiveEffects (node_modules/react-dom/cjs/react-dom.development.js:26984:14)
      at commitRootImpl (node_modules/react-dom/cjs/react-dom.development.js:26935:5)
      at commitRoot (node_modules/react-dom/cjs/react-dom.development.js:26682:5)
      at performSyncWorkOnRoot (node_modules/react-dom/cjs/react-dom.development.js:26117:3)
      at flushSyncCallbacks (node_modules/react-dom/cjs/react-dom.development.js:12042:22)
      at flushActQueue (node_modules/react/cjs/react.development.js:2667:24)
      at act (node_modules/react/cjs/react.development.js:2582:11)
      at node_modules/@testing-library/react/dist/act-compat.js:46:25
      at Object.eventWrapper (node_modules/@testing-library/react/dist/pure.js:92:28)
      at Object.wrapEvent (node_modules/@testing-library/user-event/dist/cjs/event/wrapEvent.js:8:28)
      at Object.dispatchEvent (node_modules/@testing-library/user-event/dist/cjs/event/dispatchEvent.js:47:22)
      at Object.dispatchUIEvent (node_modules/@testing-library/user-event/dist/cjs/event/dispatchEvent.js:24:26)
      at KeyboardHost.keydown (node_modules/@testing-library/user-event/dist/cjs/system/keyboard.js:65:38)
      at keyboardAction (node_modules/@testing-library/user-event/dist/cjs/keyboard/index.js:31:35)
      at Object.keyboard (node_modules/@testing-library/user-event/dist/cjs/keyboard/index.js:20:15)
      at Object.type (node_modules/@testing-library/user-event/dist/cjs/utility/type.js:26:5)
      at Object.asyncWrapper (node_modules/@testing-library/react/dist/pure.js:73:22)
# (several hundred similar lines omitted)
 PASS  tests/repro.test.tsx
  issue reproduction
    ✓ should open the modal and not explode (321 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.091 s, estimated 3 s
Ran all test suites.
act-repro% 

Reproduction:

https://github.com/ysulyma/testing-library-focus-repro

Suggested solution:

Notes:

  • it works if I remove the <AlertDialog.Cancel> (which Radix auto-focuses on open). It also breaks if I remove <AlertDialog.Cancel> but add <AlertDialog onOpenAutoFocus={e => e.currentTarget.focus()}>
  • it also works if I leave <AlertDialog.Cancel> as-is but remove await userEvent.type(input, "Dennis");
  • might be related to https://github.com/testing-library/react-testing-library/issues/1051

ysulyma avatar Mar 29 '23 21:03 ysulyma

Moving to user-event for now until we have a repro just using @testing-library/react

eps1lon avatar Apr 10 '23 11:04 eps1lon

@ysulyma Did you ever figure out what the issue was or how to solve it?

Running into this myself and am completely lost

jd-carroll avatar Jan 02 '24 17:01 jd-carroll

@jd-carroll nope I've been doing


export const knownErrors = {
  act: `Warning: An update to %s inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act%s`,
};

/** Run code with `console.error` silenced. */
export async function withSilencedErrors(
  fn: () => unknown,

  /** Specific error messages to ignore. */
  errors: string[],
) {
  const consoleError = console.error;
  console.error = (e: Error | string) => {
    if (typeof e === "string" && errors.includes(e)) return;
    if (e instanceof Error && errors.includes(e.message)) return;

    consoleError(e);
  };
  await fn();
  console.error = consoleError;
}

ysulyma avatar Jan 04 '24 00:01 ysulyma

Wow, umm yeah, I can definitely relate to that (feel like I was getting close to something like that).

Fortunately, I was able to solve my issue. Take a look at this thread where I provide a lot more detail: https://github.com/reduxjs/redux-toolkit/issues/4033

Hopefully it is of some value to you / it helps solve your issue.

jd-carroll avatar Jan 04 '24 01:01 jd-carroll

Sadly we don't have the capacity to debug issues with third-party libraries. I hope you could resolve your issue with @jd-carroll 's hint.

If the issue persists and you can break it down to a minimal reproduction, I'll gladly reopen.

ph-fritsche avatar Jan 21 '25 10:01 ph-fritsche