material-components / material-components-web

Modular and customizable Material Design UI components for the web
https://material.io/develop/web
MIT License
17.14k stars 2.14k forks source link

[mdc-dialog] focus-trap-error when testing with testing-library, vitest and jsdom #7631

Open vekunz opened 2 years ago

vekunz commented 2 years ago

Bug report

Background

We are working with MDC on a SvelteKit application. Because there is not a good Material library for Svelte, we create our own Svelte components with the MDC components. We are unit testing our Svelte MDC components with vitest, jsdom, and @testing-library/svelte. The Bug occurs while running the unit tests.

Steps to reproduce

  1. I created a Git repository with the exact setup to reproduce the error https://github.com/vekunz/mdc-dialog-focus-trap-error
  2. Check out the repository and install the dependencies (npm install)
  3. Execute the unit tests with npm run test
  4. See the error in the console

Actual behavior

The test fails with a focus trap error.

Serialized Error: {
  "errors": [
    [Error: FocusTrap: Element must have at least one focusable child.],
  ],
}

Expected behavior

Since the dialog contains a focusable element, there should not occur a focus trap error.

Your Environment:

Software Version(s)
MDC Web 14.0.0
jsdom 19.0.0
Operating System Windows 10

Additional context

The HTML that is used by the tests looks like this.

<div class="mdc-dialog" data-testid="dialog">
  <div class="mdc-dialog__container">
    <div aria-modal="true" class="mdc-dialog__surface" role="alertdialog">
      <div class="mdc-dialog__actions">
        <button class="mdc-button mdc-dialog__button">
          <span class="mdc-button__label">Button text</span>
        </button>
      </div>
    </div>
  </div>
  <div class="mdc-dialog__scrim"/>
</div>
vekunz commented 1 year ago

Since I do not think this bug will get fixed, I reverse-engineered the code of the MDCDialog and came up with a workaround for our unit tests.

First, you need a MockFocusTrap class.

class MockFocusTrap {
  public trapFocus(): void {}
  public releaseFocus(): void {}
}

Then you can set up this mock. We use Vitest, but it should work with other Tests Frameworks also.

vi.mock('@material/dom/focus-trap', () => ({
  FocusTrap: MockFocusTrap
}));