refactor: e2e mock の pure helper を search-pure.ts に統一 (#209 ③)#279
Merged
Conversation
electron/main/ipc/search.ts の inline pure helper (fuzzyMatch / isEscaped /
collectInlineCodeRanges / findFencedLines / maskRanges / byteCmp / buildLineStarts
/ isInRanges) を electron/main/utils/search-pure.ts に集約し、search.ts は
import に置換。search-pure.test.ts に helper 群のユニットテスト 44 ケースを
追加し、search.test.ts の fuzzyMatch テストは移動。
e2e/helpers/electron-api-mock.ts を addInitScript の script content 経路に
切替、search-pure.ts の named function を `.toString()` で browser scope に
inject する経路 (PURE_HELPERS 配列 + `\`${pure}\\n(${installApiMock})(...)\``) を
確立。installApiMock 内側の inline pure copy (200 行超) を削除し、mock と
本番の drift を物理的に不可能化。tsc の view では file top-level import に
resolve され typecheck 通過、browser 実行時は hoisted function declaration
として bare 参照に resolve される。
electron/main/utils/search-pure-mock-parity.test.ts を新規追加。以下を CI で
lock する:
- search-pure.ts の export 名と mock の EXPECTED_PURE_HELPERS の整合
- pure helper の `.toString()` が `function <name>(...)` 形にシリアライズされる
- mock ソースの PURE_HELPERS 配列 literal に全 entry が含まれる
- mock ソース内に inline pure 定義が復活していない (再 inline detect)
mock ソース: 1004 → 866 行 (-138 行)。sandbox listen EPERM で renderer-only
e2e はローカル実行不可、CI (electron-e2e job) で最終確認する。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ges 高速化
/simplify の 4 agent 並列レビューから 5 finding を採用 (reuse は 0 clean、
skip は E3 = findFencedLines return shape 変更、cost 対効果で見送り):
- Group 1 (S1/A1/A3, S4): PURE_HELPERS 手動 list + EXPECTED_PURE_HELPERS + import
block の 3 重管理を Object.values(searchPure) 派生に集約。mock 側 named import
は installApiMock 内側で bare 参照する identifier のみに絞り (isAsciiOnly は
inject 経由のみで利用されるため named import から外す)、parity test の
「PURE_HELPERS 配列 完全性」regex assertion (S4 の null-check 二重保険含む) を削除。
「search-pure.ts に export した function は自動的に inject される」 contract に。
- Group 2 E1/E4: setup() で毎回呼ばれていた PURE_HELPERS.map(.toString()).join() +
installApiMock.toString() (~750 行の serialize) を SCRIPT_PREFIX/SCRIPT_SUFFIX
として module scope に hoist。setup() は JSON.stringify(payload) の per-call
concat だけになり、e2e spec 起動時の 133 × ~750 行の redundant serialize を排除
- Group 3 E2: maskRanges の `text.split("") + buf.join("")` (1 文字 1 array entry
の allocation) を `text.slice() + " ".repeat()` ベースの parts.join に変更。
workspace 500 files × 10KB の scan で per-file char array allocation が消える。
length preservation と char index 互換は既存テストで pass
- Group 4 S2/S3/A4: search-pure.ts top doc から e2e mock 事情由来の "inject 形の
`const \${fn.name} = \${fn.toString()}`" という誤記を実装 (named function 直並べ) に
合わせて修正、named function 縛りを一般的な `.toString()` シリアライズ経路として
記述。mock 内 installApiMock 内側の中間コメント (304-306 / 326-329) を削除
(file header に既に記述あり)。mockDisplayPath のコメントから "addInitScript 制約
で import 不可" の旧経路言及を "mock は browser scope で走るため node:path は不使用" に置換
parity test: 4 case → 2 case (named function serialization / inline 復活 gate) に。
検証: biome / typecheck (node/web/e2e) / vitest 100 files 2270 pass / build 全 green。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e 掃除
/code-review medium の 6 finding を採用 (correctness bug は 0、全て cleanup / drift gate 強化):
- Finding 2+5: mock の PURE_HELPERS を `Object.values(searchPure).filter(fn)` 派生から
named import と 1:1 対応する explicit list に戻す。「import と PURE_HELPERS の 2 リスト
drift」と「search-pure.ts 全 export を無条件 inject する scope 拡散」を同時解消。真実源は
named import block、PURE_HELPERS はその value projection。`isAsciiOnly` は named import
に戻し PURE_HELPERS の値として consume することで biome noUnusedImports を pass させる。
- Finding 1: search-pure-mock-parity.test.ts の regex を拡張。
- inline 再定義検出を `const|let|var|function <name>` の 4 形全対応に (元は `const` 形のみ、
hoisting により `function` 形の re-inline が gate をすり抜ける穴があった)
- mock ソースから named import と PURE_HELPERS 配列の identifier 列を extract し
等価性を assert する 3 番目 test を追加 (import に載せたが PURE_HELPERS 忘れ → browser
runtime ReferenceError を静的 gate で catch)
- Finding 3: search.ts / mock 双方の `isFenced.some((b) => b) ? maskRanges(...) : text`
guard を削除。新 maskRanges が mask all-false 時に text を identity 返却する契約になった
今、guard は 1 パス無駄なだけで cognitive load を増やしていた。
- Finding 4: maskRanges の `return parts.length === 0 ? text : parts.join("")` の
特別分岐は到達不能 (parts が空になる条件は text.length === 0 のみで、その場合 join も
空 = text と等価) なので `return parts.join("")` に単純化。
- Finding 6: search-pure.ts top doc から PR 番号 `#279` を削除。安定 pure module の doc に
PR 参照が入ると chase コストを maintainer に押し付ける altitude smell。
検証: biome / typecheck (node/web/e2e) / vitest 100 files 2271 pass / build 全 green。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… が壊れる問題
CI (`e2e` job) で backlink-panel / command-palette / search 系 spec が
「検索結果 0 件」で timeout していた regression の修正。root cause は
Playwright 1.61 内蔵 babel が本 repo (`package.json` に `"type": "module"` が
無い) の e2e helper を isModule=false 経路で transform し、
@babel/plugin-transform-modules-commonjs が named import の bare 参照を
`(0, _searchPure.foo)` / `_searchPure.bar` に rewrite することにあった。
`installApiMock.toString()` した body を browser scope に運ぶ経路では
`_searchPure` が undefined なため ReferenceError となり全 search / backlink
handler が壊れる。ローカル vitest / typecheck / build は helper を実行時に
評価しないためこの経路の問題を検出できなかった。
修正: `import { fuzzyMatch, ... }` から `import * as searchPure` +
`const { fuzzyMatch, ... } = searchPure` の destructure に切り替え、
installApiMock 内側の bare 参照を local const にする。babel の
modules-commonjs は module scope の named import は rewrite するが local
const は preserve するため、`.toString()` の中の bare identifier 形が保たれ
SCRIPT_PREFIX で並べた hoisted 関数宣言と正しく resolve できる。Playwright
bundled babel を直接呼んで transform 結果を確認済み (`_searchPure.*` 参照 0 件、
bare 参照 5+ 件)。
parity test の `import { ... }` extractor を `const { ... } = searchPure`
destructure extractor に更新し、同期 gate は継続。regression 予防のため
mock file の doc に「必ず `import * as` + local const destructure の形を保つ」
を明記。
検証: biome / typecheck (node/web/e2e) / vitest 100 files 2271 pass /
build 全 green。CI (`e2e` job) は本 push で再検証する。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ymnao
added a commit
that referenced
this pull request
Jul 5, 2026
- package.json の version を 0.6.0 → 0.7.0 に bump - CHANGELOG.md に v0.7.0 エントリを追加 (#243 〜 #280) - Fixed: FileTree scroll regression (#276)、electron e2e launch teardown timeout 拡大 (#247) - Internal: #209 3 項目 (drift-lock 系 refactor #278/#279/#280)、settings setter 集約 (#273 → #277)、search/workspace/backlink 系 helper 抽出 15 件 - Dependencies: electron 43.0.0 / p-limit 7.3.0 major bump (#275)、Dependabot 7 件 combine (#274)、pnpm-workspace.yaml overrides で @codemirror dual copy を恒久防止 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
#209 項目 ③「e2e mock 純関数化」対応。
electron/main/ipc/search.tsに inline 定義されていた pure helper 群 (8 個) をelectron/main/utils/search-pure.tsに集約し、e2e/helpers/electron-api-mock.tsからも同じ search-pure.ts を.toString()シリアライズ経由で browser scope に inject する経路を確立。mock と本番の pure helper drift を 物理的に不可能 にする。/simplifyと/code-review mediumの findings も同 PR 内で全対応済み。関連 Issue
closes #209 (項目 ③、v0.7.0 parent の 3 項目のうち ② は #278 で対応済、① は別 PR で対応予定)
移行 Stage
変更内容
1. Main 側集約 (commit
56c5c06)electron/main/ipc/search.tsの inline pure helper (fuzzyMatch/isEscaped/collectInlineCodeRanges/findFencedLines/maskRanges/byteCmp/buildLineStarts/isInRanges) をsearch-pure.tsに集約、search.ts は import に置換search-pure.test.tsに helper 群のユニットテスト 44 case を追加、search.test.tsのfuzzyMatchテストは移動e2e/helpers/electron-api-mock.tsをpage.addInitScript({ content: PURE_HELPERS.map(f=>f.toString()).join() + \(${installApiMock})(...)` })` 経路に切替、installApiMock 内側の inline pure copy (200 行超) を削除search-pure-mock-parity.test.tsを新規追加 (drift 検出)2.
/simplify対応 (commit6dd969b)SCRIPT_PREFIX/SCRIPT_SUFFIXを module scope に hoist、setup() の per-call serialize 削減 (~750 行 × 133 spec)maskRangesをtext.split("") + buf.join("")からslice + " ".repeat() + parts.join()に変更、per-file char array allocation を排除3.
/code-review medium対応 (commite7afb12)PURE_HELPERSをObject.values(searchPure)派生から named import と 1:1 対応する explicit list に戻す (2 list drift + wholesale inject scope 拡散を同時解消、真実源は named import block 1 つに)const|let|var|function <name>の 4 形全対応に拡張 (hoisting によりfunction形の re-inline が gate をすり抜ける穴を塞ぐ)isFenced.some((b) => b) ?guard を search.ts / mock 両方で削除 (新 maskRanges が identity 契約になったため冗長)maskRangesの到達不能なparts.length === 0 ? text : ...分岐を削除#279を削除動作確認
pnpm biome check— passpnpm typecheck(node/web/e2e) — passpnpm test— 100 files / 2271 pass / 2 skipped (44 case 追加 + parity 3 case 追加 + fuzzyMatch 4 case 移動)pnpm build(electron-vite build) — passpnpm test:e2e(renderer-only mode) — sandbox 内 listen EPERM でローカル実行不可、CI (e2ejob) で確認pnpm test:e2e:electron(実 Electron mode) — mock 未使用のため回帰リスクは low、CI (electron-e2ejob) で確認Diff scale
6 files changed, +649 -408 lines。うち mock ソースは 1004 → ~735 行 (-270 行)、search.ts inline helper 分は search-pure.ts に集約。