From c8aa6d4315c3c633c63f3c266622921d4fcb4283 Mon Sep 17 00:00:00 2001 From: ConnorQi01 Date: Fri, 10 Jul 2026 10:05:51 +0800 Subject: [PATCH] Fix StopLogCatMonitor cancellation --- src/extension/commands/stopLogCatMonitor.ts | 15 ++- .../commands/stopLogCatMonitor.test.ts | 123 ++++++++++++++++++ 2 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 test/extension/commands/stopLogCatMonitor.test.ts diff --git a/src/extension/commands/stopLogCatMonitor.ts b/src/extension/commands/stopLogCatMonitor.ts index 3025ee937..82057e1d0 100644 --- a/src/extension/commands/stopLogCatMonitor.ts +++ b/src/extension/commands/stopLogCatMonitor.ts @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. -import assert = require("assert"); import * as vscode from "vscode"; import { ErrorHelper } from "../../common/error/errorHelper"; import { InternalErrorCode } from "../../common/error/internalErrorCode"; +import { LogCatMonitor } from "../android/logCatMonitor"; import { LogCatMonitorManager } from "../android/logCatMonitorManager"; import { OutputChannelLogger } from "../log/OutputChannelLogger"; import { Command } from "./util/command"; @@ -19,11 +19,15 @@ export class StopLogCatMonitor extends Command { async baseFn(): Promise { const monitor = await selectLogCatMonitor(); + if (!monitor) { + return; + } + LogCatMonitorManager.delMonitor(monitor.deviceId); } } -function selectLogCatMonitor() { +function selectLogCatMonitor(): LogCatMonitor | Promise { const logger = OutputChannelLogger.getMainChannel(); const keys = Object.keys(LogCatMonitorManager.logCatMonitorsCache); @@ -35,13 +39,10 @@ function selectLogCatMonitor() { if (keys.length > 1) { return new Promise((res, rej) => { vscode.window.showQuickPick(keys).then(res, rej); - }).then(async selected => { - // #todo!>selectionHandling> + }).then(selected => { if (!selected) { - await new Promise(() => {}); + return undefined; } - - assert(selected, "Selection canceled"); logger.debug(`Command palette: selected LogCat monitor ${selected}`); return LogCatMonitorManager.logCatMonitorsCache[selected]; }); diff --git a/test/extension/commands/stopLogCatMonitor.test.ts b/test/extension/commands/stopLogCatMonitor.test.ts new file mode 100644 index 000000000..b0db3eff4 --- /dev/null +++ b/test/extension/commands/stopLogCatMonitor.test.ts @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +import assert = require("assert"); +import Sinon = require("sinon"); +import proxyquire = require("proxyquire"); +import { InternalErrorCode } from "../../../src/common/error/internalErrorCode"; + +suite("stopLogCatMonitorCommand", function () { + function createMonitor(deviceId: string): any { + return { + deviceId, + dispose: Sinon.stub(), + }; + } + + function createCommandModule( + logCatMonitorsCache: { [key: string]: any }, + showQuickPickStub = Sinon.stub(), + delMonitorStub = Sinon.stub(), + logger = { + info: Sinon.stub(), + error: Sinon.stub(), + warning: Sinon.stub(), + debug: Sinon.stub(), + }, + ) { + const module = proxyquire.noCallThru()( + "../../../src/extension/commands/stopLogCatMonitor", + { + vscode: { + window: { + showQuickPick: showQuickPickStub, + }, + }, + "../android/logCatMonitorManager": { + LogCatMonitorManager: { + logCatMonitorsCache, + delMonitor: delMonitorStub, + }, + }, + "../log/OutputChannelLogger": { + OutputChannelLogger: { + getMainChannel: () => logger, + }, + }, + }, + ) as typeof import("../../../src/extension/commands/stopLogCatMonitor"); + + return { + StopLogCatMonitor: module.StopLogCatMonitor, + delMonitorStub, + showQuickPickStub, + }; + } + + async function runCommand( + commandClass: typeof import("../../../src/extension/commands/stopLogCatMonitor").StopLogCatMonitor, + ): Promise { + const command = commandClass.formInstance(); + await command.baseFn(); + } + + test("should throw when there are no active LogCat monitors", async function () { + const { StopLogCatMonitor, delMonitorStub, showQuickPickStub } = createCommandModule({}); + + await assert.rejects( + () => runCommand(StopLogCatMonitor), + (error: any) => { + assert.strictEqual( + error.errorCode, + InternalErrorCode.AndroidCouldNotFindActiveLogCatMonitor, + ); + return true; + }, + ); + assert.strictEqual(showQuickPickStub.called, false); + assert.strictEqual(delMonitorStub.called, false); + }); + + test("should delete the only active LogCat monitor without showing QuickPick", async function () { + const { StopLogCatMonitor, delMonitorStub, showQuickPickStub } = createCommandModule({ + emulator: createMonitor("emulator"), + }); + + await runCommand(StopLogCatMonitor); + + assert.strictEqual(showQuickPickStub.called, false); + assert.strictEqual(delMonitorStub.calledWithExactly("emulator"), true); + }); + + test("should delete the selected LogCat monitor", async function () { + const showQuickPickStub = Sinon.stub().returns(Promise.resolve("device")); + const { StopLogCatMonitor, delMonitorStub } = createCommandModule( + { + emulator: createMonitor("emulator"), + device: createMonitor("device"), + }, + showQuickPickStub, + ); + + await runCommand(StopLogCatMonitor); + + assert.strictEqual(showQuickPickStub.calledWithExactly(["emulator", "device"]), true); + assert.strictEqual(delMonitorStub.calledWithExactly("device"), true); + }); + + test("should return without deleting a LogCat monitor when selection is cancelled", async function () { + const showQuickPickStub = Sinon.stub().returns(Promise.resolve(undefined)); + const { StopLogCatMonitor, delMonitorStub } = createCommandModule( + { + emulator: createMonitor("emulator"), + device: createMonitor("device"), + }, + showQuickPickStub, + ); + + await runCommand(StopLogCatMonitor); + + assert.strictEqual(showQuickPickStub.calledWithExactly(["emulator", "device"]), true); + assert.strictEqual(delMonitorStub.called, false); + }); +});