Skip to content

test(storybook): ストーリーを追加し a11y チェックをゲート化する - #36

Merged
kp047i merged 2 commits into
mainfrom
claude/github-access-check-2ma5ba
Aug 16, 2026
Merged

test(storybook): ストーリーを追加し a11y チェックをゲート化する#36
kp047i merged 2 commits into
mainfrom
claude/github-access-check-2ma5ba

Conversation

@kp047i

@kp047i kp047i commented Aug 16, 2026

Copy link
Copy Markdown
Owner

概要

ストーリーが 14 コンポーネント中 4 つにしかなく、@storybook/addon-a11ytest: "todo" で違反を表示するだけで CI では落ちない状態でした。カバー範囲を広げたうえで、アクセシビリティを実際のゲートにします。

見つかった a11y 違反

ゲート化にあたり既存ストーリーで先に測ったところ、本物の違反が 1 件ありました。

FAIL  features/blog/components/BlogCard/BlogCard.stories.tsx > Default

Expected the HTML found at $('.group > span') to have no violations:
  <span>Tech</span>

"Elements must meet minimum color contrast ratio thresholds (color-contrast)"
  Element has insufficient color contrast of 3.85
  (foreground color: #c46445, background color: #fefafa)
  Expected contrast ratio of 4.5:1

BlogCategorytext-primary-700opacity-80 がかかり、合成後の実効色が薄くなって基準を割っていました。パレット自体の問題ではなく不透明度との組み合わせが原因です。

計算した選択肢はこうでした。

指定 合成後 コントラスト比
primary-700 + opacity-80 (現状) #c46445 3.86 ❌
primary-800 + opacity-80 #a75a48 4.83
primary-700 (opacity なし) #b63f18 5.46 ✅

opacity-80 を外すと「ホバーで濃くなる」という意図が失われるため、primary-800 に変更する形を選びました。ダークモードは primary-300 の合成後が 5.23:1 で元から基準を満たしているので触っていません。

変更内容

ストーリーを 7 件追加

いずれも play 関数でリンク先やアクセシブルネームなど意味のある単位を assert しています。

コンポーネント ストーリー
Header Default / WithActionSlot
Footer Default
BlogTag Default / MediumSize / UnknownTag
BlogTagList NoTagSelected / TagSelectedWithCategory
BlogCardTag Default
SpeakerDeck Default / DefaultTitle
ReactAriaExampleTable Default / Paginated

BlogTagUnknownTag は「TAGS に無いタグは key をそのまま表示する」挙動、BlogTagListTagSelectedWithCategory は「タグを切り替えてもカテゴリが保たれる」クエリ構築を確認しています。

a11y をゲート化

.storybook/preview.tsxa11y.test"todo" から "error" に変更しました。以降は違反があると pnpm test:storybook が落ちます。

ストーリーを追加しなかったもの

  • ClientLayoutuseViewPort() を呼んで children を返すだけで、検証対象の UI がありません
  • ContentLinkCardfetch を行う非同期サーバーコンポーネントで、モックなしではストーリーにできません
  • ReactAriaExampleStepper — どこからも import されていません。react-aria-slots.mdx に同名の実装がコードフェンス内で載っているだけで、コンポーネント本体は未使用です

最後のものは削除候補ですが、判断が要るので今回は触っていません。

動作確認

コマンド 結果
pnpm lint
pnpm lint:css
pnpm format:check
pnpm typecheck
pnpm build
pnpm build-storybook
pnpm test:unit ✅ 12 テスト
pnpm test:storybook 11 ファイル / 19 テスト (変更前は 4 ファイル / 6 テスト)
pnpm test:e2e ✅ 5 passed / 1 skipped

test:storybook は 2 回連続で通ることを確認しています。


🤖 Generated with Claude Code

https://claude.ai/code/session_01LKrW7WAzi6MW9KAkLA4mnr


Generated by Claude Code

ストーリーが 14 コンポーネント中 4 つにしか無く、addon-a11y も
test: "todo" で違反を表示するだけで CI では落ちない状態だった。

- Header / Footer / BlogTag / BlogTagList / BlogCardTag / SpeakerDeck /
  ReactAriaExampleTable にストーリーを追加。いずれも play 関数で
  リンク先やアクセシブルネームなど意味のある単位を assert する
- addon-a11y を test: "error" に変更。違反が CI で落ちるようになる
- BlogCategory の文字色を primary-700 から primary-800 に変更。
  opacity-80 と合成すると 3.86:1 でコントラスト比 4.5:1 を満たさなかった。
  primary-800 なら合成後も 4.83:1 になる。ダークモードは primary-300 の
  合成後が 5.23:1 で元から基準を満たしているため変更しない

ストーリーを追加しなかったものと理由:

- ClientLayout: useViewPort を呼んで children を返すだけで検証対象の UI が無い
- ContentLinkCard: fetch を行う非同期サーバーコンポーネントで、
  モックなしではストーリーにできない
- ReactAriaExampleStepper: どこからも import されていない。記事中の
  コードフェンスに同名の実装が載っているだけで、実際には未使用

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKrW7WAzi6MW9KAkLA4mnr
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
kappy-dev Ready Ready Preview Aug 16, 2026 12:57pm

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

これはブログ記事に埋め込んでるやつだから不要です

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

削除しました (e43744f)。

「記事に埋め込むためのコンポーネントにはストーリーを書かない」という基準を AGENTS.md にも追記しています。SpeakerDeck / ContentLinkCard / ReactAriaExampleTable を例として挙げました。同じものがまた追加されるのを防ぐためです。


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

このテストもあるべき?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

同じ基準に当てはまるので、こちらも削除しました (e43744f)。SpeakerDeckmdx-components.tsx に登録されて記事から使われるだけで、サイトの UI には出てきません。

一点だけ捨てたものを書き残しておきます。このストーリーは iframe の title がアクセシブルネームになること、title を渡さなくても既定値でネームが空にならないことを確認していました。a11y のチェックが効く箇所ではあります。

とはいえ props が 3 つだけの薄いラッパーで、壊れるとしたら記事の表示で気づくと思うので、基準を優先して消す判断で良いと考えています。もし残したければ言ってください。


Generated by Claude Code

ReactAriaExampleTable と SpeakerDeck はサイトの UI ではなく記事に埋め込む
ためのコンポーネントなので、Storybook に並べる意味が薄い。

判断基準を AGENTS.md にも書き、同じものが再度追加されないようにした。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKrW7WAzi6MW9KAkLA4mnr
@kp047i
kp047i merged commit 0b398f4 into main Aug 16, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants