visibility: hiddenなのにjestに`visible`だと言われた

# 時間のない人向け

**追記も合わせて読んでください**

Styled-Componentを使用してボタンにマウスホバーすると吹き出しが表示されるコンポーネントを作成しました。
しかし、ボタンにホバーしていない時でもjestには吹き出しが`visible`だと言われました。

テストが通る状態は[ReactComponentTests](https://github.com/IamSBStakumi/ReactComponentTests)にある以下の4つのファイルを参照してもらえればいいと思います。
- [Buttons.tsx](https://github.com/IamSBStakumi/ReactComponentTests/blob/main/src/styles/Buttons.tsx)
- [Explanation.tsx](https://github.com/IamSBStakumi/ReactComponentTests/blob/main/src/styles/Explanation.tsx)
- [page.tsx](https://github.com/IamSBStakumi/ReactComponentTests/blob/main/src/app/buttons/page.tsx)
- [buttons.test.tsx](https://github.com/IamSBStakumi/ReactComponentTests/blob/main/src/__tests__/buttons.test.tsx)

結論としてはJestがstyleの変更を検知できないから、応急処置的な対応でテストが通るようにしたということですが、styled-componentで子要素のstyleを変更できることを初めて知ったので記事にしました。

# 起こったこと

ボタンにマウスホバーすると`Explanation`コンポーネントが表示されるよう実装しました。

<details><summary>問題のコード</summary>

```Explanation.tsx
"use client";

import styled from "styled-components";
import { IStyledComponentBase } from "styled-components/dist/types";

const Explanation = styled.span<{
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  $parent: IStyledComponentBase<any, any>;
}>`
  position: absolute;
  right: auto;
  left: 8%;
  z-index: 10;
  visibility: hidden;
  width: 240px;
  padding: 10px;
  margin-top: 10px;
  text-align: center;
  color: #fff;
  background-color: rgb(12 110 183 / 100%);
  border-radius: 5px;

  &::after {
    position: absolute;
    top: -12px;
    right: auto;
    left: 5%;
    content: "";
    border-right: 10px solid transparent;
    border-bottom: 12px solid rgb(12 110 183 / 100%);
    border-left: 10px solid transparent;
  }
  ${(props) => props.$parent}: hover & {
    visibility: visible;
  }
`;

export default Explanation;
```

```Button.tsx
"use client";

import styled from "styled-components";

const Button = styled.button`
  margin: 5px;
  border: solid 1px #ddd
  border-radius: 8px;
  cursor: pointer;
`;

export default Button;

```

```page.tsx
import Button from "@/styles/Buttons";
import Explanation from "@/styles/Explanation";

const Page = () => (
  <main>
    <div>
      <Button>
        テストコンポーネント<Explanation $parent={Button}>説明</Explanation>
      </Button>
    </div>
  </main>
);

export default Page;

```

</details>

`Explanation.tsx`(=吹き出しコンポーネント)で親要素を取得し、親要素にマウスホバーすると自身を表示するよう実装していました。

以下、試したテストコードです。
```buttons.test.tsx
// 一部抜粋
expect(screen.getByText("吹き出しコンポーネント")).toHaveStyle("visibility: hidden")
expect(screen.queryByText("吹き出しコンポーネント")).not.toBeVisible();
```

どちらの場合もテストは失敗し、吹き出しコンポーネントは**見えている**とjestは指摘してきます。
自分で確認すると、ボタンにマウスホバーすると吹き出しは表示され、ボタンから離れると吹き出しは消えるのにjest君にはずっと見えているらしいです。

# 必要な修正

子要素から親要素を参照してStyleを変更するのではなく、親要素から子要素のStyleを変更してやるとテストが成功するようになりました。

<details><summary>修正後のコード</summary>

```Explanation.tsx
const Explanation = styled.span`
  position: absolute;
  right: auto;
  left: 8%;
  z-index: 10;
  display: block;
  visibility: hidden;
  width: 240px;
  padding: 10px;
  margin-top: 20px;
  text-align: center;
  color: #fff;
  background-color: rgb(12 110 183 / 100%);
  border-radius: 5px;

  &::after {
    position: absolute;
    top: -12px;
    right: auto;
    left: 5%;
    content: "";
    border-right: 10px solid transparent;
    border-bottom: 12px solid rgb(12 110 183 / 100%);
    border-left: 10px solid transparent;
  }
** // 親要素の参照を削除 **
`;

export default Explanation;
```

```Button.tsx
"use client";

import styled from "styled-components";

const Button = styled.button`
  margin: 5px;
  border: solid 1px #ddd
  border-radius: 8px;
  cursor: pointer;

  &:hover {
  // 子要素のspanのstyleを指定できる
    > span {
      visibility: visible;
    }
  }
`;

export default Button;

```

```page.tsx
import Button from "@/styles/Buttons";
import Explanation from "@/styles/Explanation";

const Page = () => (
  <main>
    <div>
      <Button>
        // parentを渡す必要がなくなった
        テストコンポーネント<Explanation>説明</Explanation>
      </Button>
    </div>
  </main>
);

export default Page;

```

</details>

<details open><summary>重要な箇所だけ抜粋</summary>

```Explanation.tsx
const Explanation = styled.span`
  visibility: hidden;
`;
```

```Button.tsx
const Button = styled.button`
  &:hover {
    > span {
      visibility: visible;
    }
  }
`;
```

上記のように書けば、子要素にある`span`のvisibilityを変更できます。

</details>

これでjestもマウスホバーしていない時に吹き出しコンポーネントは`hidden`だと納得してくれました。

また、これらの現象は`display`プロパティでも同様に発生/解決できることがわかりました。

# 追記

テストは通りますが、本質的な解決に至っていないことがわかりました。
上記修正を行う前はずっと`visible`だと言われますが、修正後のコードだとずっと`hidden`だと言われます。

どうやらjestでstyleの変更が検知できないようです。
jest-styled-componentというパッケージを発見したので、試してみようと思います。