トップページにGitHub草風ヒートマップを実装、テスト18,440件全パス化とKRW単位ラベルの根本修正

開発mdx-playground

トップページに草を生やし、テスト失敗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層で担保させた。

  1. 生成: buildTickerData.test.ts に KRW 銘柄のケースを追加。Turso の生データから正しい単位でデータが組み上がるか
  2. 整形: earnings-dynamics-format.test.ts で「KRW 銘柄に $ ラベルを出してはならない」を直接検証
  3. 仕様固定: USD 銘柄の従来出力が1文字も変わっていないことを固定(修正の巻き添えで既存表示が壊れるのを防ぐ)

データを再生成して全銘柄を更新し、全体スイートで回帰確認。全18,506件がパスして、カバレッジも取得した。生成スクリプト側にも同じ KRW 対応を入れたのは、TS 側だけ直すと次のデータ再生成で同じバグが復活するから。訂正は1箇所で終わらせず、同じロジックを持つ場所すべてに展開する。

学び

  • テスト失敗の一括修正は「全部テストを直す」に流れやすい。1件ずつ「テストが古い / コードのバグ」を判定させると、34件のなかから本物のデータバグ(KRW ラベル)が1つ出てきた
  • 双子実装(TS の純粋関数と生成スクリプトの inline 実装)があるときは、片方だけ直しても再生成で巻き戻る。両方直して、Vitest 側で仕様を凍結する
  • 「既存出力を変えない」こと自体をテストに書いておくと、単位対応のような横断修正でも安心して入れられる
  • チャートの数値整形をコンポーネントから純粋関数に切り出したことで、ブラウザを開かずに表示仕様をテストできるようになった