Skip to content

test(save): cover the branch where the partial-buffer guard refuses - #445

Merged
PathGao merged 1 commit into
masterfrom
test/cover-the-refusal-branch
Aug 3, 2026
Merged

test(save): cover the branch where the partial-buffer guard refuses#445
PathGao merged 1 commit into
masterfrom
test/cover-the-refusal-branch

Conversation

@PathGao

@PathGao PathGao commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

scripts/truncatedBufferGuard.test.ts exists for one claim: a buffer that holds only the first 50KB of a large file is never written back over that file. Every test in it drove the guard's success path — open a partial buffer, complete it, watch the whole document reach disk.

Deleting the verdict check from toggleTaskCheckbox, so it edits and saves whatever ensureFullContent leaves behind:

-		if (!(await ensureFullContent(tab.id))) return false;
+		await ensureFullContent(tab.id);

565 tests, 565 passing. The file whose entire purpose is that guard had no test for the guard refusing.

What false actually means

ensureFullContent re-reads the file behind a partial buffer and returns whether the buffer is whole afterwards. Three states produce false, and they are not variations of one thing:

state reports? how it is reached
the tab is gone no handleDetach / moveTabToWindow hold an id; the tab can close while the call is in flight
the buffer already carries edits no replacing it would trade the user's typing for the file's tail, so it is left alone
the re-read failed onError an unplugged drive, a network volume that went away, a file replaced under us

The second state is a trap and not merely a refusal: once a partial buffer is dirty it can never be completed again, so a writer that edits it first has permanently disarmed the recovery path. That is why the tests assert the buffer is left byte-identical, not merely that no write happened.

Every call site checks the result. All six — toggleTaskCheckbox in documentSession, and handleFrontMatterEdit, handleFrontMatterListChange, toggleSplitView, handleDetach, moveTabToWindow in MarkdownViewer.svelte — stop on false; the five in the component also raise toast.partialDocument. No live bug, a coverage gap.

saveContent and saveContentAs do not consult it. They carry the same refusal as their own isTruncated backstop, which is the last line of defence if an entry point is ever missed, so they are driven into it here too.

Tests

Six added to truncatedBufferGuard.test.ts, all calling into the real documentSession and the real TabManager over the stubbed Tauri bridge that file already sets up. No new source-text assertions — the existing three at the bottom of the file, for wiring node --test cannot import from a .svelte file, are untouched.

test what it pins
a re-read that fails leaves the buffer partial, still flagged, and reported the failing branch returns false, does not half-fill the buffer, keeps isTruncated, and says so once
completing the buffer of a tab that is gone is refused, not assumed "no such tab" is not "nothing to do" — and nothing is read for it
a task checkbox is not toggled into a buffer that could not be completed the audit's defect: the toggle reports failure, writes nothing, and leaves the buffer clean
a task checkbox is not toggled into a partial buffer that already carries edits the silent state — the false return is the whole signal, and the typing survives
Save As refuses to copy a partially loaded document a short copy is a NEW file that nothing marks as short; the dialog does not even open
answering "Save" to the close dialog cannot flush a partial buffer the one path where a refused save loses data: canCloseTab must stay false and the tab must stay open

The close-dialog test needed the harness's askClose to be settable; it still answers discard by default, so no existing test changed behaviour.

Both recent changes to this file are load-bearing here and neither is fought. #438 serialises saves per tab: the truncated refusal returns before writeExclusively, so it never enters the queue and a later save on the same tab still writes — completing a partial buffer … unblocks saving, already in the file, is what covers that ordering. #439 made isLossySaveRefused ask the tab as well as the memory: a partial-buffer refusal never touches lossySaveWarnedTabs and never reads as a lossy refusal, so the auto-save timer stays free to report a real failure on the same tab.

Mutation check

Each defect injected into src/lib/sessions/documentSession.svelte.ts, scripts/truncatedBufferGuard.test.ts run, the source restored. 17 tests in the file.

injected defect result first red test
toggleTaskCheckbox ignores the verdict (the audit's defect) RED (2) a task checkbox is not toggled into a buffer that could not be completed
saveContentAs drops its isTruncated guard RED (1) Save As refuses to copy a partially loaded document
saveContent drops its isTruncated guard RED (2) saving refuses to write a partial buffer over the file
ensureFullContent treats a failed re-read as success RED (2) a re-read that fails leaves the buffer partial, still flagged, and reported
ensureFullContent reports the failed re-read to nobody RED (2) a re-read that fails leaves the buffer partial, still flagged, and reported
ensureFullContent completes a dirty partial buffer anyway RED (2) a partial buffer that already carries edits is never silently discarded
ensureFullContent answers true for a tab that is gone RED (1) completing the buffer of a tab that is gone is refused, not assumed
canCloseTab closes on a refused save RED (1) answering "Save" to the close dialog cannot flush a partial buffer

8 injected, 8 caught. Rows 3 and 6 are caught first by tests that were already there — those two are not vacuous, they were simply the only refusals anything reached.

Messages name the behaviour, not the line:

the toggle must report failure so the checkbox springs back
a partial buffer must never reach save_file_content
and the edits it carries must not be traded for the file's tail
a tab whose save was refused must stay open
the user is told why the document cannot be edited

npm test 565 → 571 passing, npm run check 637 files / 0 errors, npm run build clean.

Not covered

  • The five refusal sites in MarkdownViewer.sveltehandleFrontMatterEdit, handleFrontMatterListChange, toggleSplitView, handleDetach, moveTabToWindow. Each checks the verdict and raises toast.partialDocument, and each is still asserted only as source text, because node --test cannot import a .svelte file (test(scroll-sync): cover the split-view mapping by running it #442's finding: 21 components, no executable coverage). The grep tests prove ensureFullContent is mentioned in those bodies, not that the early return fires. Closing this is the same extraction test(scroll-sync): cover the split-view mapping by running it #442 did for the scroll-sync math, and it is a bigger lift here: these handlers touch the DOM and component state, so it is a behaviour-moving change rather than a mechanical one.
  • The dirty-partial refusal is silent to the user. States 1 and 2 report nothing; only the failed re-read raises a toast. In practice the caller compensates — the checkbox springs back, the front-matter handlers toast on false themselves — so the user does see something. It is still a gap between "the user was told" and "the UI happened to react", and it is why the checkbox tests assert the return value rather than a message.
  • markTabContentUnavailable's tab. isTruncated also means "the file could not be read at all", set by the failed-load path with an empty buffer. Every writer refuses it for the same reason, and nothing here distinguishes the two producers of the flag; documentLoadFailure.test.ts owns that side.
  • No concurrency between a refusal and a live write. fix(save): let one write per tab be in flight at a time #438's queue is exercised by oneWritePerTabInFlight.test.ts, and the refusals here are all reached with nothing in flight. A refusal arriving while a save for the same tab is draining is a state neither file drives.
  • Nothing pins the backstop to saveContent/saveContentAs specifically. If someone moves the isTruncated check into writeExclusively, these tests still pass — they assert the refusal, not where it lives.

🤖 Generated with Claude Code

`truncatedBufferGuard.test.ts` exists to prove a partially loaded buffer is
never written to disk, but every test in it drove the guard's *success*
path. Deleting the verdict check from `toggleTaskCheckbox` — so it edits and
saves whatever `ensureFullContent` leaves behind — left all 565 tests green.

Six tests, all running the real `documentSession` against the stubbed
backend, put a writer in each state where the buffer stays partial: the tab
is gone, the buffer already carries edits, the re-read failed. Each asserts
the same pair — nothing reached `save_file_content`, and the buffer was left
exactly as it was found — across `saveContent`, `saveContentAs`,
`toggleTaskCheckbox` and the close dialog's "Save".

Eight defects injected on top, eight caught.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao merged commit a500c5a into master Aug 3, 2026
4 checks passed
@PathGao
PathGao deleted the test/cover-the-refusal-branch branch August 3, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant