mirror of
https://github.com/sotashimozono/obsidian-remote-ssh.git
synced 2026-07-22 06:52:07 +00:00
1 commit
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1e638471a4 |
test(plugin): Phase C.M7 — FakeFileExplorer test double for UI-reflect (0.4.45)
Pure-helper module; production code: untouched. Lays the foundation
for M8 (`assertSyncReflect`) and M9 (E2E matrix) — the slice of
Phase C that brings UI-reflect (the T5 wedge of the sync-latency
sequence) under automated assertion.
What ships:
- `plugin/tests/helpers/FakeFileExplorer.ts` (new)
A test double that listens to the same `vault.trigger(...)` event
stream Obsidian's File Explorer + MetadataCache + plugins
subscribe to, and maintains a small synthetic "what does this
vault look like after the events that have fired so far" model.
M9 will use it as the **T5 observation point**: once
`awaitReflect(path, event)` resolves, an Obsidian-faithful
downstream consumer would also have observed the change.
Surface:
- `attach(vault: VaultLike) → dispose`: subscribes to all four
vault events (create/modify/delete/rename) via `vault.on(...)`,
returns a disposer that calls `vault.offref(...)` for each
registration. `VaultLike` is a structural type
(`Pick<Vault,'on'|'offref'>`) so per-test stubs only have to
expose those two methods — no real `obsidian.Vault` runtime
required.
- `observe(event, ...args)`: direct event injection for tests
that don't even want a Vault stub. Mirrors the
`vault.trigger(event, ...args)` arg shape so a FakeVault's
`trigger` can forward straight here.
- `snapshot() → { paths[], mtimes }`: deterministic
sorted-paths view of the current model. Folders are present
in `paths` but absent from `mtimes` (mirroring the real
vault, where TFolder has no `stat`).
- `awaitReflect(path, event, timeoutMs?)`: resolves with
`{ atMs }` (the `performance.now()` from when the underlying
event fired — that's what M9 will feed into PerfAggregator's
S.paint bucket; late-arriving awaits don't inflate latency).
History-walks first so an event observed BEFORE
`awaitReflect` was called still resolves it (eliminates a
class of listener-race flakes); times out with a descriptive
error including the recent history tail.
- `reset()`: drop state and reject pending awaits — for
`afterEach`-style hygiene.
Folder semantics intentionally mirror VaultModelBuilder:
- delete on a folder also drops descendants from the snapshot
(vault.trigger fires once for the folder itself; descendants
are silently orphaned in the production fileMap and we don't
want the FE to lie about what File Explorer would show).
- rename on a folder rewrites every descendant path under the
new prefix.
history is bounded (cap 1024 entries) so a long-running suite
doesn't grow it unbounded; the cap matters in M9 where each test
may observe dozens of events.
- `plugin/tests/FakeFileExplorer.test.ts` (new, 20 tests):
observe / snapshot:
- empty start
- create adds path + records mtime
- create on a folder records path with no mtime
- modify updates mtime
- delete removes path + mtime
- folder delete removes descendants (depth ≥ 2)
- rename moves path, preserves mtime when no new stat
- rename uses the new file's stat when present
- folder rename rewrites descendants
awaitReflect:
- resolves from history when event already fired
- second await for same event doesn't double-consume
- resolves a pending waiter when event arrives later
- waiter matches its event even when others fire first
- times out with descriptive error
- timeout leaves no leaked waiter (later observe doesn't crash)
attach():
- subscribes to all four events (verified via FakeVault's
listenerCount)
- disposer detaches all four
- post-dispose, vault events no longer reflected
reset():
- drops state
- rejects pending waiters
Inline `FakeVault` helper (~30 lines) covers `on`/`offref`/
`trigger`/`listenerCount`. Not extracted — it's the only consumer
in the unit suite; M9 will likely pull a similar shape into its
own integration helper.
Verification:
- `npx vitest run` — 35 files / 474 tests green (was 454, +20 new)
- `npx tsc --noEmit -p tsconfig.json` — clean
manifest/package/versions bumped 0.4.44 → 0.4.45.
Roadmap context: M7 is the prerequisite for M8 (assertSyncReflect)
and M9 (E2E matrix). After M8 lands, the assertion vocabulary
is "writer mutation → reader notification → FakeFileExplorer
reflects path/event"; M9 fills out the case matrix
(create / modify / delete / rename / large / binary / nested /
conflict / disconnect-reconnect).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|