Skip to content

Commit 3952b3c

Browse files
committed
fix(activate): wire .catch on void-prefixed postMessageToWebview sites
The five bare `void` prefixes in registerCommands.ts satisfy no-floating-promises but rely on ClineProvider.postMessageToWebview having its own try/catch — an implicit contract that a future change could break without notice. Matching #253's pattern, each void site now also installs a .catch arm that logs to outputChannel. Added a parameterized test asserting the .catch arm runs and logs when postMessageToWebview rejects, so a future regression in the implicit-swallow contract is caught at the test boundary.
1 parent ae4c66d commit 3952b3c

2 files changed

Lines changed: 39 additions & 5 deletions

File tree

src/activate/__tests__/registerCommands.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,28 @@ describe("registerCommands handlers", () => {
246246

247247
expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
248248
})
249+
250+
// Representative coverage for the .catch arm on all five void-prefixed
251+
// postMessageToWebview sites in registerCommands.ts (settingsButtonClicked
252+
// posts twice, plus historyButtonClicked, marketplaceButtonClicked, and
253+
// acceptInput). Each handler is synchronous, so the .catch arm runs on a
254+
// microtask; awaiting Promise.resolve() flushes it before we assert.
255+
it.each([
256+
{ command: "zoo-code.settingsButtonClicked", expectedCalls: 2 },
257+
{ command: "zoo-code.historyButtonClicked", expectedCalls: 1 },
258+
{ command: "zoo-code.marketplaceButtonClicked", expectedCalls: 1 },
259+
{ command: "zoo-code.acceptInput", expectedCalls: 1 },
260+
])("$command logs to outputChannel when postMessageToWebview rejects", async ({ command, expectedCalls }) => {
261+
const boom = new Error("boom")
262+
mockVisibleProvider.postMessageToWebview.mockReset()
263+
mockVisibleProvider.postMessageToWebview.mockRejectedValue(boom)
264+
265+
handlers[command]()
266+
267+
// Flush microtasks so the chained .catch arm runs.
268+
await new Promise((resolve) => setImmediate(resolve))
269+
270+
expect(mockOutputChannel.appendLine).toHaveBeenCalledTimes(expectedCalls)
271+
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(`postMessageToWebview failed: ${boom}`)
272+
})
249273
})

src/activate/registerCommands.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,13 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
103103

104104
TelemetryService.instance.captureTitleButtonClicked("settings")
105105

106-
void visibleProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
106+
void visibleProvider
107+
.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
108+
.catch((error) => outputChannel.appendLine(`postMessageToWebview failed: ${error}`))
107109
// Also explicitly post the visibility message to trigger scroll reliably
108-
void visibleProvider.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
110+
void visibleProvider
111+
.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
112+
.catch((error) => outputChannel.appendLine(`postMessageToWebview failed: ${error}`))
109113
},
110114
historyButtonClicked: () => {
111115
const visibleProvider = getVisibleProviderOrLog(outputChannel)
@@ -116,12 +120,16 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
116120

117121
TelemetryService.instance.captureTitleButtonClicked("history")
118122

119-
void visibleProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
123+
void visibleProvider
124+
.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
125+
.catch((error) => outputChannel.appendLine(`postMessageToWebview failed: ${error}`))
120126
},
121127
marketplaceButtonClicked: () => {
122128
const visibleProvider = getVisibleProviderOrLog(outputChannel)
123129
if (!visibleProvider) return
124-
void visibleProvider.postMessageToWebview({ type: "action", action: "marketplaceButtonClicked" })
130+
void visibleProvider
131+
.postMessageToWebview({ type: "action", action: "marketplaceButtonClicked" })
132+
.catch((error) => outputChannel.appendLine(`postMessageToWebview failed: ${error}`))
125133
},
126134
newTask: handleNewTask,
127135
setCustomStoragePath: async () => {
@@ -170,7 +178,9 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
170178
return
171179
}
172180

173-
void visibleProvider.postMessageToWebview({ type: "acceptInput" })
181+
void visibleProvider
182+
.postMessageToWebview({ type: "acceptInput" })
183+
.catch((error) => outputChannel.appendLine(`postMessageToWebview failed: ${error}`))
174184
},
175185
toggleAutoApprove: async () => {
176186
const visibleProvider = getVisibleProviderOrLog(outputChannel)

0 commit comments

Comments
 (0)