Skip to content

Commit 9ab5356

Browse files
test(activate): give the focusInput silence assertions teeth, drop the clobber guard
Row registerCommands.spec.ts:518: the two no-target focusInput cases proved only that no provider received a post, so a build that logs an error instead of posting passed. Both cases now also assert the output channel stayed silent, using not.toHaveBeenCalled() rather than a message match. Control run recorded before this commit: adding one outputChannel.appendLine at the top of the focusInput try block turns that subset from 5 passed into 2 failed, which is the evidence that the new assertions can fail. The probe was reverted, so only the assertions remain. Row registerCommands.ts:43: the comment claimed the extension host no longer reads getPanel() directly while ClineProvider.ts imports it (line 127) and calls it (line 1200) in the tab-view disposal check. The wording now names that reader. A grep of src at this head shows only those two lines plus the test file. Row registerCommands.ts:355: the clobber guard on pendingTabPanelCreation is deleted rather than suppressed. The entry guard hands the in-flight promise to every overlapping caller, so between storing the slot and this finally block nothing else can write the slot and the equality check always held. Clearing unconditionally removes the branch, its mutant, and the Stryker directive. Gates on this worktree head: focusInput subset 5 passed, open-in-editor subset 17 passed, whole registerCommands.spec.ts 62 passed, tsc -p tsconfig.json clean, eslint --prune-suppressions --max-warnings=0 clean on both files with eslint-suppressions.json left untouched, prettier --check clean.
1 parent e4b3151 commit 9ab5356

2 files changed

Lines changed: 15 additions & 12 deletions

File tree

‎src/activate/__tests__/registerCommands.spec.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,10 @@ describe("registerCommands handlers", () => {
480480
it("focusInput does not post when no sidebar panel is tracked", async () => {
481481
await handlers["zoo-code.focusInput"]()
482482

483+
// Silence is part of the contract here: with no panel tracked the command must exit
484+
// quietly, so a focus attempt that fails and gets logged is a failure, not a pass.
483485
expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
486+
expect(mockOutputChannel.appendLine).not.toHaveBeenCalled()
484487
})
485488

486489
it("focusInput posts to the tab provider when a tab panel is tracked alongside the sidebar", async () => {
@@ -515,6 +518,9 @@ describe("registerCommands handlers", () => {
515518
expect(ClineProvider.getInstanceForView as Mock).toHaveBeenCalledWith(tabPanel)
516519
expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
517520
expect(mockVisibleProvider.postMessageToWebview).not.toHaveBeenCalled()
521+
// And silent: focusPanel selected the tab, the target lookup found no live provider,
522+
// and that is not an error worth a log line.
523+
expect(mockOutputChannel.appendLine).not.toHaveBeenCalled()
518524
})
519525

520526
it("setPanel keeps independent refs: clearing only the tab ref re-enables the sidebar post", async () => {

‎src/activate/registerCommands.ts‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ let pendingTabPanelCreation: Promise<ClineProvider> | undefined
4040
/**
4141
* Get the currently active panel
4242
* @returns WebviewPanel或WebviewView
43-
* @internal Test-only seam: the extension host no longer reads this directly.
43+
* @internal Test-only seam. Aside from the tab-view disposal check in ClineProvider.ts,
44+
* which compares getPanel() with the disposing view before clearing the tab ref, nothing
45+
* outside the test suite reads this.
4446
*/
4547
export function getPanel(): vscode.WebviewPanel | vscode.WebviewView | undefined {
4648
return tabPanel || sidebarPanel
@@ -342,17 +344,12 @@ export const openClineInNewTab = async ({ context, outputChannel, webviewFocusTr
342344
return await creation
343345
} finally {
344346
// Clear once settled (success or failure) so the next call starts
345-
// fresh: the reuse path in createTabPanelUnlocked then takes over
346-
// for the tracked panel. Guard the clear so this settlement cannot
347-
// clobber a replacement already stored in the slot. That clobber is
348-
// unreachable in single-threaded settlement order: while the slot
349-
// holds this in-flight creation, every other caller receives that
350-
// same promise (guard above), so no replacement can be stored before
351-
// this finally block runs — the equality check pins the invariant.
352-
// Stryker disable next-line ConditionalExpression: defensive clobber guard, unreachable per the ordering argument above.
353-
if (pendingTabPanelCreation === creation) {
354-
pendingTabPanelCreation = undefined
355-
}
347+
// fresh: the reuse path in createTabPanelUnlocked then takes over for the
348+
// tracked panel. The clear is unconditional because no other caller can
349+
// store a replacement while this creation is in flight: the entry guard
350+
// above hands every overlapping call this same promise, so this finally
351+
// block is the only writer between storing the slot and settling it.
352+
pendingTabPanelCreation = undefined
356353
}
357354
}
358355

0 commit comments

Comments
 (0)