🔍

Fluent UI のダイアログのスナップショット テストがうまくいかない場合の対応方法

2024/12/05に公開

はじめに

Fluent UI のダイアログを含むコンポーネントのスナップショット テストを実施しようとするとスナップショットを取れないことがあります。

問題点

まずはどのような動作をするか確認します。

コンポーネント

公式ドキュメントのサンプルそのままです。

import React from 'react';

import {
  Button,
  Dialog,
  DialogActions,
  DialogBody,
  DialogContent,
  DialogSurface,
  DialogTitle,
  DialogTrigger
} from '@fluentui/react-components';

function App() {

  return (
    <Dialog>
      <DialogTrigger disableButtonEnhancement>
        <Button>Open dialog</Button>
      </DialogTrigger>
      <DialogSurface>
        <DialogBody>
          <DialogTitle>Dialog title</DialogTitle>
          <DialogContent>
            Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam
            exercitationem cumque repellendus eaque est dolor eius expedita
            nulla ullam? Tenetur reprehenderit aut voluptatum impedit voluptates
            in natus iure cumque eaque?
          </DialogContent>
          <DialogActions>
            <DialogTrigger disableButtonEnhancement>
              <Button appearance="secondary">Close</Button>
            </DialogTrigger>
            <Button appearance="primary">Do Something</Button>
          </DialogActions>
        </DialogBody>
      </DialogSurface>
    </Dialog>
  );

}

テスト コード

ボタンをクリックしてダイアログを表示させてからスナップショットを取得します。

import React from 'react';

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import App from './App';

it('should create a shapshot', async () => {
  const user = userEvent.setup();
  const { asFragment } = render(<App />);
  await user.click(screen.getByRole('button', { name: /Open dialog/i }));
  expect(asFragment()).toMatchSnapshot();
});

スナップショット

ダイアログの部分がレンダリングされていません。

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should create a shapshot 1`] = `
<DocumentFragment>
  <button
    class="fui-Button r1alrhcs"
    data-tabster="{"restorer":{"type":1}}"
    type="button"
  >
    Open dialog
  </button>
</DocumentFragment>
`;

原因

ダイアログは記述した場所にレンダリングされるのではなく、特別な div 要素の中にレンダリングされます。Jest の場合はその要素がないため、ダイアログはレンダリングされません。

対応方法

ダイアログのコンポーネントの一部である DialogSurface には mountNode プロパティがあります。そこに要素を指定することで任意の場所にダイアログを表示できるようになります。

実際にコードを修正してみます。

コンポーネント

App コンポーネントは mountNode プロパティを受け取るようにします。

+ interface AppProps {
+   mountNode?: HTMLElement;
+ }

- function App() {
+ function App({ mountNode }: AppProps) {

    return (
      <Dialog>
        <DialogTrigger disableButtonEnhancement>
          <Button>Open dialog</Button>
        </DialogTrigger>
-       <DialogSurface>
+       <DialogSurface mountNode={mountNode}>
        ...

テスト コード

新しい div 要素を作成します。作成した要素を App コンポーネントの mountNode プロパティに渡します。合わせて render メソッドのパラメーターにも渡すようにします。

  it('should create a shapshot', async () => {
    const user = userEvent.setup();
-   const { asFragment } = render(<App />);
+   const container = document.body.appendChild(document.createElement('div'));
+   const { asFragment } = render(<App mountNode={container} />, { container });
    await user.click(screen.getByRole('button', { name: /Open dialog/i }));
    expect(asFragment()).toMatchSnapshot();
  });

スナップショット

ダイアログがレンダリングされていることを確認できます。

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should create a shapshot 1`] = `
<DocumentFragment>
  <button
    class="fui-Button r1alrhcs"
    data-tabster="{"restorer":{"type":1}}"
    type="button"
  >
    Open dialog
  </button>
  <div
    aria-hidden="true"
    class="fui-DialogSurface__backdrop rsptlh5"
  />
  <div
    aria-labelledby="dialog-title-r1"
    aria-modal="true"
    class="fui-DialogSurface rsmdyd3"
    data-tabster="{"restorer":{"type":0},"modalizer":{"id":"modal-r0","isOthersAccessible":false,"isTrapped":true}}"
    role="dialog"
    tabindex="-1"
  >
    <i
      aria-hidden="true"
      data-tabster-dummy=""
      role="none"
      style="position: fixed; height: 1px; width: 1px; opacity: 0.001; z-index: -1;"
      tabindex="0"
    />
    <div
      class="fui-DialogBody r1h3qql9"
    >
      <h2
        class="fui-DialogTitle rxjm636 ___mh25s00_kwrsjh0 fsyjsko"
        id="dialog-title-r1"
      >
        Dialog title
      </h2>
      <div
        class="fui-DialogContent r1v5zwsm"
      >
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam exercitationem cumque repellendus eaque est dolor eius expedita nulla ullam? Tenetur reprehenderit aut voluptatum impedit voluptates in natus iure cumque eaque?
      </div>
      <div
        class="fui-DialogActions rhfpeu0 ___pj8zjl0_1y84z0u f1a7i8kp fd46tj4 fsyjsko f1f41i0t f1jaqex3 f2ao6jk"
      >
        <button
          class="fui-Button r1alrhcs"
          data-tabster="{"restorer":{"type":1}}"
          type="button"
        >
          Close
        </button>
        <button
          class="fui-Button r1alrhcs ___1akj6hk_ih97uj0 ffp7eso f1p3nwhy f11589ue f1q5o8ev f1pdflbu f1phragk f15wkkf3 f1s2uweq fr80ssc f1ukrpxl fecsdlb f1rq72xc fnp9lpt f1h0usnq fs4ktlq f16h9ulv fx2bmrt f1d6v5y2 f1rirnrt f1uu00uk fkvaka8 f1ux7til f9a0qzu f1lkg8j3 fkc42ay fq7113v ff1wgvm fiob0tu f1j6scgf f1x4h75k f4xjyn1 fbgcvur f1ks1yx8 f1o6qegi fcnxywj fmxjhhp f9ddjv3 f17t0x8g f194v5ow f1qgg65p fk7jm04 fhgccpy f32wu9k fu5nqqq f13prjl2 f1czftr5 f1nl83rv f12k37oa fr96u23"
          type="button"
        >
          Do Something
        </button>
      </div>
    </div>
    <i
      aria-hidden="true"
      data-tabster-dummy=""
      role="none"
      style="position: fixed; height: 1px; width: 1px; opacity: 0.001; z-index: -1;"
      tabindex="0"
    />
  </div>
  <span
    hidden=""
  />
</DocumentFragment>
`;

おわりに

ダイアログについて説明しましたがドロワー (Drawer コンポーネント) も同様です。

Discussion