fix: P1 issue 対応 (#392 PNG ダウンロード失敗の UI 通知 / #386 QrCode a11y) - #434
Conversation
旧実装は戻り型 void で img.onerror 時に何もせず silent failure。Promise 化して reject を `JanCode.downloadPng` の catch から ErrorMessage 表示に 接続する。並列実装 svgContentToPngBlob / downloadPngFromSvgContent と同じ pattern (Gs1Databar の downloadPng 参考) で API 内部の不一致を解消する。 unit / E2E の陽性対照 (img.onerror 強制発火で reject / ErrorMessage 表示) を追加。旧 void 実装に当てると await 不可 / ErrorMessage 非表示で fail する設計とした (test-gates 鉄則: 検知能力ゼロで green を防ぐ)。 Closes #392
QR コードは dangerouslySetInnerHTML で SVG 挿入されるが、コンテナの aria-live も SVG <title> も無く、スクリーンリーダーが生成完了・図の意味を 読み取れなかった。 - プレビューコンテナに role="status" aria-live="polite" を付与 (Gs1Databar / JanCode の status 領域と同形) - generateQrSvg に <title> 注入 + role="img" / aria-label を追加。 text は XML エスケープして XSS 二次防衛線も兼ねる - E2E 2 ケース追加: <title> テキスト assert と <script> 注入で実体参照化 されることの陽性対照 (旧実装に当てれば fail する) Closes #386
🖼️ Visual Regression Test 結果
|
レビュー (#434)P1 issue 2 件 (#392 silent failure / #386 a11y) を 1 PR に同梱した修正。陽性対照を意識した unit / E2E が両方追加されていて、CLAUDE.md「ガード / バリデータには陽性対照必須」ルールにきれいに沿っている点が良い。 🔴 Should fix —
|
PR #434 レビュー指摘対応 (Should fix): aria-label="QRコード" を併用していると ARIA Accessible Name and Description Computation 4.3.1 で <title> が name 計算から除外され、URL 等の本文が SR で読み上げられない。 - generateQrSvg から aria-label 注入を撤去し、role="img" のみ付与 - E2E を強化: aria-label 属性が存在しないこと / <title> が first child で あること / textContent に URL 本文が含まれることを 3 段 assert (旧 aria-label="QRコード" 実装に当てると属性存在チェックで fail する陽性対照)
… に伝播 PR #434 レビュー指摘対応: img.onload 内で drawImage / toDataURL が throw すると (canvas tainted で SecurityError 等) Promise には reject されず unhandled error として漏れ、 caller の catch に届かない silent failure 経路が残っていた。 - try/catch で onload 全体を包み、catch で reject、finally で URL.revokeObjectURL を呼ぶことでリーク回避も両立 - 陽性対照 unit を追加: canvas.toDataURL を throw する stub で Promise が reject されることを assert (try/catch なし実装には unhandled error として漏れて fail する設計) - imgInstance のデフォルト初期化を dead code として削除し、コメントで意図を残す
|
レビューありがとうございます。Should fix と Consider をそれぞれ対応しました。 🔴 #1
|
再レビュー (#434, sha
|
| 観点 | 前回 | 今回 |
|---|---|---|
| accessible name に URL が読まれる | 🔴 | ✅ |
| Promise reject 経路の網羅 | 🟡 | ✅ |
| 陽性対照テストの規約準拠 | ✅ | ✅ (さらに強化) |
| XSS 二次防衛 | ✅ | ✅ |
| live region 発火頻度 | 🟡 | 🟢 (issue 化推奨、本 PR 外) |
LGTM 🎉
Generated by Claude Code
サマリー
P1 タグ open issue 7 件のうち、独立かつ単一 PR で完結できる以下 2 件をまとめて対応。
downloadPngFromSvgElementの silent failure を解消。Promise 化しJanCode.downloadPngの catch からErrorMessageに接続。QrCodeプレビューにrole="status" aria-live="polite"と SVG<title>を付与。変更内容
#392:
downloadPngFromSvgElementPromise 化src/utils/download.ts— 戻り型void→Promise<void>、img.onerrorでreject(new Error('PNG への変換に失敗しました'))、img.onloadの末尾でresolve()。並列実装svgContentToPngBlobと同じ pattern。src/components/tools/JanCode.tsx—Gs1Databar.downloadPngpattern を踏襲しdownloadErrorstate +await/try-catch+<ErrorMessage variant="block">を配置。SVG ダウンロードも try/catch で包む。#386: QrCode a11y
src/components/tools/QrCode.tsx— プレビューコンテナにrole="status" aria-live="polite"。generateQrSvgで生成 SVG に<title>QRコード: <text></title>とrole="img"/aria-label="QRコード"を注入。<title>内テキストは XML エスケープして XSS 二次防衛線も兼ねる。test-gates 陽性対照
両 issue ともガード/検知系として、旧実装に当てれば fail する設計のテストを併設:
Image.onerror強制発火でPromise.reject('PNG への変換に失敗しました')を assert (src/utils/__tests__/download.test.ts)。旧void実装にはawaitできず必ず fail。page.evaluateでwindow.Imageを必ずonerror発火する stub に差し替え、role="alert"のErrorMessage表示を assert (tests/e2e/jan-code.spec.ts)。旧実装は silent failure で alert 出ず。<script>alert(1)</script>を入力 →containerHtmlに<script>が含まれ生<script>タグが DOM に無いことを assert (tests/e2e/qr-code.spec.ts)。escape を外せば fail。検証
node_modules/.bin/astro check→ 0 errors / 0 warnings / 0 hintsnpm run test→ 1246 件 pass (build artifact 依存 meta テストのため事前にnpm run build実施)npm run test:e2e -- jan-code.spec.ts qr-code.spec.ts a11y-live-region.spec.ts→ 22 件 passnpm run format:check→ All files use Prettier code styleTest plan
<title>を DevTools で確認(手動)Closes #392
Closes #386
Generated by Claude Code