トップページにGitHub草風ヒートマップを実装、テスト18,440件全パス化とKRW単位ラベルの根本修正
トップページに草を生やし、テスト失敗34件を切り分けてKRWラベルを根本修正した
トップページを開くたびに「Nuxt Content を使ったメモ公開サイトです」という説明文が目に入る。見りゃわかる。この文章を消して、代わりに GitHub のコントリビューショングラフ、いわゆる「草」を生やしてもらうことにした。ついでに放置していたユニットテストの失敗34件を全部片付けたら、そのなかから「韓国ウォン建て銘柄の値に $B ラベルが付いている」というデータバグが転がり出てきた。この1件だけはテストを直すのではなく、パイプラインの根本から直させた。
トップページに草を生やす
Claude Code への指示はほぼ口頭のノリのまま。「説明文を消して、その位置に GitHub の草みたいなやつを横幅いっぱいで」。
実装させた内容:
ContributionGraph.vueを新規作成。直近1年・53週分のグリッドに、公開記事1,474本の投稿日を緑の濃淡で表示する- トップページの説明文と、不要になった
.intro pのスタイルを削除 - 記事データの取得は軽量版の composable に切り替え(全フィールドを引かず、ヒートマップに必要な日付だけ取る)
- ユニットテスト(
tests/contribution-graph.test.ts)を追加
最後にブラウザで描画とコンソールを確認させて完了。トップページの一等地に、投稿が続いた週は濃い緑、サボった週は薄いマスが並ぶようになった。自分のブログなのに、他人のGitHubプロフィールを覗くときと同じ目線でマスの濃さを数えてしまう。
テスト失敗34件を「テストが古いのか、コードのバグなのか」で切り分ける
草の実装でテストを回したら、既存のユニットテストが34件落ちていた。「パスしてほしいんで、失敗34件、正しく修正してもらえませんか」と投げて、1件ずつ切り分けさせた。
進め方はまず全失敗の詳細を1つのファイルに落とし、クラスタごとに原因を特定してから修正に入る流れ。途中、design-principles 配下のファイル名をグロブで空振りしていないか確認させたり、tsukumi-slides.vue のキーボードハンドラの挙動を実際に追わせたりと、「テストが間違っている」と断定する前に実装側の裏を取っている。
ここで重要なのは、34件を機械的に「テストを直して緑にする」のではなく、1件ごとに「テストが古いのか、データ・コードのバグなのか」を判定すること。全部テスト側を直せば緑にはなるが、それはバグを仕様として固定する行為になる。
切り分けの結果:
| クラスタ | 件数 | 判定 | 対処 |
|---|---|---|---|
| triple-beat 系 | 18件 | テストが古い | データに追加された「次回決算の予測行」(isForecast: true)を実績とみなさない規約に合わせてテスト側を更新 |
| OGタグ不足 | 5ページ分 | ページ側の不備 | 公開5ページにOGタグを追加 |
| url-migration | 数件 | テストの前提漏れ | standalone コンテンツをテストの対象から除外 |
| KRW銘柄の単位ラベル | — | データ・パイプラインのバグ | テストは触らず根本修正へ(次節) |
triple-beat 系の18件は、先例となる detect-new-earnings.test.ts の規約(予測行は実績として数えない)に揃える形でテストを更新させた。ここまでで146ファイル・18,440件が全パス。
KRWの $B ラベルだけはテストを直さない
切り分けの途中で見つかったのが、earningsDynamics の KRW 銘柄(韓国ウォン建て)の問題。売上が兆ウォンオーダーの銘柄のチャートに、B(ドルビリオン)のラベルがぶら下がっていた。83兆ウォンが「83.3B」として表示される。桁も通貨も嘘になる。
これはテストの期待値を書き換えて済ませる話ではない。「コリアンウォンのドルビリオンラベル問題は、ちゃんと直してほしい。テストで全部担保できるようにしてほしい」と指示して、生成パイプラインから直させた。
修正の構成:
- 生成側:
buildTickerDataFromTursoと生成スクリプトgenerate-earnings-dynamics-data.mjsの両方に、ティッカーごとの通貨単位テーブル(KRW_TICKER_UNITS)を追加。KRW 銘柄の売上は百万ウォン→兆ウォンに換算し、EPS はウォンのまま持つ - 描画側:
EarningsChart.vueに埋まっていた数値整形をapp/utils/earningsDynamics/format.tsの純粋関数に切り出し、単位に応じて整形する - 目盛りの桁合わせ: ウォン建て EPS は数万オーダーになるため、チャート目盛りの丸め単位も KRW 銘柄では桁を合わせて別に持つ
export const formatMetricValue = (unit: MetricUnit, v: number): string => {
switch (unit) {
case '$B': return `$${v.toFixed(1)}B`
case '$': return `$${v.toFixed(2)}`
case '兆W': return `${v.toFixed(2)}兆W`
case '兆ウォン': return `${v.toFixed(1)}兆ウォン`
case 'ウォン': return `${fmtKrwInteger(v)}ウォン`
}
}
テストは3層で担保させた。
- 生成:
buildTickerData.test.tsに KRW 銘柄のケースを追加。Turso の生データから正しい単位でデータが組み上がるか - 整形:
earnings-dynamics-format.test.tsで「KRW 銘柄に $ ラベルを出してはならない」を直接検証 - 仕様固定: USD 銘柄の従来出力が1文字も変わっていないことを固定(修正の巻き添えで既存表示が壊れるのを防ぐ)
データを再生成して全銘柄を更新し、全体スイートで回帰確認。全18,506件がパスして、カバレッジも取得した。生成スクリプト側にも同じ KRW 対応を入れたのは、TS 側だけ直すと次のデータ再生成で同じバグが復活するから。訂正は1箇所で終わらせず、同じロジックを持つ場所すべてに展開する。
学び
- テスト失敗の一括修正は「全部テストを直す」に流れやすい。1件ずつ「テストが古い / コードのバグ」を判定させると、34件のなかから本物のデータバグ(KRW ラベル)が1つ出てきた
- 双子実装(TS の純粋関数と生成スクリプトの inline 実装)があるときは、片方だけ直しても再生成で巻き戻る。両方直して、Vitest 側で仕様を凍結する
- 「既存出力を変えない」こと自体をテストに書いておくと、単位対応のような横断修正でも安心して入れられる
- チャートの数値整形をコンポーネントから純粋関数に切り出したことで、ブラウザを開かずに表示仕様をテストできるようになった