同じサイトを2タブで開き、片方で localStorage.clear() を実行したとき、もう片方のReactの表示も空になるでしょうか。
use-local-storage-state の固定した2つのソースで確認しました。元版では、実際の保存値が消え、ブラウザの storage イベントも届いているのに、2つのフックの表示が古いまま残りました。既存の修正PRでは、同じテストが再読み込み前に成功します。
持ち帰れるものは、ブラウザが生成したイベントを使う3本のPlaywrightテストです。別のテスト用CLIやAPIキーは使いません。
同じ3テストで比較した結果
2026-10-20、Chromium、React 18.3.1、1 worker、retry 0、skip 0で各版を1回ずつ実行しました。
| テスト | 元版 799291ab
|
修正PR f53a57a5
|
|---|---|---|
別タブの setItem で完全なレコードが更新され、もう1キーが残る |
PASS | PASS |
同じタブ内のiframeが sessionStorage.clear() をしてもlocalStorageの値と表示が残る |
PASS | PASS |
別タブの localStorage.clear() 後、再読み込み前に両方の表示が空になる |
FAIL | PASS |
実行時間はテスト全体で元版5.734秒、修正PR3.673秒でした。速度比較を目的とした反復測定ではありません。
修正と元のjsdomテストは feritcemkahramanさんのPR #86 によるものです。今回追加したのは、実ブラウザのテスト、Reactの利用側fixture、測定記録です。測定時点ではPRは未マージでした。
見落としやすいのは key: null
HTML Standardの clear() の手順 は、保存値を消去した後、キー・変更前の値・変更後の値を null として通知するよう定めています。MDNの storage イベントの説明 にある通り、localStorage と sessionStorage ではイベントを共有する範囲も異なります。
今回の実ブラウザでは、別タブのボタンから本物の localStorage.clear() を実行し、読み取り側で次を確認しました。
{
key: null,
oldValue: null,
newValue: null,
isTrusted: true,
area: 'local'
}
area はfixtureが event.storageArea と localStorage / sessionStorage を照合して記録する独自の項目です。isTrusted を手動で作ったり、dispatchEvent() で通知を代用したりはしていません。
元版では、保存値を getItem すると2キーとも null。しかし、画面には合成データのタスクと設定が残りました。
修正PRでは、同じ消去操作の後、再読み込み前に表示も空になります。
消去操作自体は意図通りに動いています。検出している問題は、消去後の保存値とReactの表示の不一致です。
再読み込みで直っても、途中の失敗を消さない
元版も、再読み込みすれば空の表示に戻ります。最後の表示だけを確認すると、途中で古い値を見せていたことが分からなくなります。
そこで、実テストでは再読み込み前の正常系アサーションを残し、その成否を再読み込み後まで保持します。以下は 公開したspec の中心部分の抜粋です。env は2つの実ページ、empty は両フックが空の期待値です。
let staleAssertion;
try {
await expect(env.reader.getByTestId('hook-state'))
.toHaveText(JSON.stringify(empty), { timeout: 2000 });
} catch (error) {
staleAssertion = error;
}
// この間に保存値・表示・実イベントを記録し、画像も保存する
await env.reader.reload();
await expect(env.reader.getByTestId('hook-state'))
.toHaveText(JSON.stringify(empty));
if (staleAssertion) throw staleAssertion;
元版は2本の対照テストがPASSし、この正常系アサーションがFAILして終了コード1になります。修正PRは同じアサーションで3本ともPASSします。元版の失敗を隠すための test.fail() は使っていません。
自分で動かす
テスト一式のZIP を別の検証フォルダへ展開してください。Node 22以上を使います。ZIPには上流ソースや node_modules は含めていません。
npm ci --ignore-scripts --no-audit --no-fund
npx playwright install chromium
READMEの手順 で固定ソースを取得した後、次の2コマンドを別々に実行します。最初の終了コード1は、元版の不一致を検出した結果です。
STORAGE_CLEAR_SOURCE_DIR=./upstream-base npm test
STORAGE_CLEAR_SOURCE_DIR=./upstream-pr npm test
上に示したテスト時間には、ブラウザ本体の初回ダウンロード時間は含めていません。他の端末での成功や導入時間までは確認していません。
既存のPlaywright環境へ取り込む場合は、specと consumer.mjs を一緒に置きます。React / ReactDOM / esbuildが必要です。ほかのフックに適用するときは、フックのimport、ソース確認用のハッシュ、キー、利用側の操作、完全な期待レコードを対象に合わせます。
この結果で確認した範囲
確認したのはローカルChromium、2キー、defaultValue なしの組み合わせです。sessionStorage側の対照テストは、localStorageの値と表示が残ることを確認しています。内部のコールバックが一切発火しないことまでは計測していません。
既存のjsdomテストに、ブラウザ生成のイベントと実際の2タブ・iframeの条件を追加する検証です。Firefox、WebKit、SSR、容量超過、すべてのデフォルト値やserializer、上流の全テストを網羅した結果ではありません。
ソースを変更せずに比較し、各版の全17 Git blobも実行前後で照合しました。テスト本体が照合するのはフックのハッシュです。全ソースの照合は今回の測定作業で別途行っています。
測定記録とソース一式 に、チェックポイント、固定したバージョン、未加工の画像を残しています。同じリポジトリには、保存・復元の境界を確認するテストも公開しています。
検証と執筆はAI支援で行い、実行記録とソースに照合しました。上流作者による本テストの実行や、製品全体の信頼性を確認したという主張は含みません。

