diff --git a/packages/salesforcedx-utils-vscode/src/commands/commandletExecutors.ts b/packages/salesforcedx-utils-vscode/src/commands/commandletExecutors.ts index 029f4c6216..5dded50909 100644 --- a/packages/salesforcedx-utils-vscode/src/commands/commandletExecutors.ts +++ b/packages/salesforcedx-utils-vscode/src/commands/commandletExecutors.ts @@ -130,6 +130,10 @@ export abstract class LibraryCommandletExecutor protected showChannelOutput = true; protected readonly telemetry = new TelemetryBuilder(); + public static readonly libraryCommandCompletionEventEmitter = new vscode.EventEmitter(); + public static readonly onLibraryCommandCompletion: vscode.Event = + LibraryCommandletExecutor.libraryCommandCompletionEventEmitter.event; + /** * @param executionName Name visible to user while executing. * @param logName Name for logging purposes such as telemetry. @@ -220,6 +224,7 @@ export abstract class LibraryCommandletExecutor properties, measurements ); + LibraryCommandletExecutor.libraryCommandCompletionEventEmitter.fire(!!success); } catch (e) { if (e instanceof Error) { telemetryService.sendException(e.name, e.message); @@ -227,6 +232,7 @@ export abstract class LibraryCommandletExecutor channelService.appendLine(e.message); } channelService.showChannelOutput(); + LibraryCommandletExecutor.libraryCommandCompletionEventEmitter.fire(false); } } diff --git a/packages/salesforcedx-utils-vscode/test/jest/commands/commandletExecutors.test.ts b/packages/salesforcedx-utils-vscode/test/jest/commands/commandletExecutors.test.ts new file mode 100644 index 0000000000..945e8b4d95 --- /dev/null +++ b/packages/salesforcedx-utils-vscode/test/jest/commands/commandletExecutors.test.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { Progress, CancellationToken } from 'vscode'; +import * as vscode from 'vscode'; +import { ContinueResponse, LibraryCommandletExecutor } from '../../../src'; +import { ChannelService } from '../../../src/commands/channelService'; +import { SettingsService } from '../../../src/settings'; + +class SimpleTestLibraryCommandletExecutor extends LibraryCommandletExecutor { + public run( + response: ContinueResponse, + progress?: Progress<{ message?: string | undefined; increment?: number | undefined }>, + token?: CancellationToken): Promise { + return new Promise(resolve => { + resolve(true); + }); + } +} + +jest.mock('../../../src/commands/channelService'); + +describe('LibraryCommandletExecutor', () => { + + it('should fire the onLibraryCommandCompletion event once the library command is done', async () => { + const fireSpy = jest.spyOn( + LibraryCommandletExecutor.libraryCommandCompletionEventEmitter, + 'fire' + ); + const channel = vscode.window.createOutputChannel('simpleExecutorChannel'); + const executor = new SimpleTestLibraryCommandletExecutor('simpleExecutor', 'logName', channel); + + await executor.execute({} as ContinueResponse<{}>); + + expect(fireSpy).toHaveBeenCalledWith(false); + }); + + it('should not fire onLibraryCommandCompletion event if an error is thrown before try catch block', async () => { + const fireSpy = jest.spyOn( + LibraryCommandletExecutor.libraryCommandCompletionEventEmitter, + 'fire' + ); + try { + jest + .spyOn(SettingsService, 'getEnableClearOutputBeforeEachCommand') + .mockImplementation(() => { + throw new Error(); + }); + const channel = vscode.window.createOutputChannel('simpleExecutorChannel'); + const executor = new SimpleTestLibraryCommandletExecutor('simpleExecutor', 'logName', channel); + + await executor.execute({} as ContinueResponse<{}>); + } catch(e) { + expect(fireSpy).not.toHaveBeenCalled(); + } + }); +}); diff --git a/packages/salesforcedx-vscode-core/src/commands/util/commandEventDispatcher.ts b/packages/salesforcedx-vscode-core/src/commands/util/commandEventDispatcher.ts index 0249ae2d3e..0964bdb910 100644 --- a/packages/salesforcedx-vscode-core/src/commands/util/commandEventDispatcher.ts +++ b/packages/salesforcedx-vscode-core/src/commands/util/commandEventDispatcher.ts @@ -4,6 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { LibraryCommandletExecutor } from '@salesforce/salesforcedx-utils-vscode'; import * as vscode from 'vscode'; import { RefreshSObjectsExecutor } from '..'; @@ -23,7 +24,14 @@ export class CommandEventDispatcher implements vscode.Disposable { return RefreshSObjectsExecutor.onRefreshSObjectsCommandCompletion(listener); } + public onLibraryCommandCompletion( + listener: (event: unknown) => unknown + ): vscode.Disposable { + return LibraryCommandletExecutor.onLibraryCommandCompletion(listener); + } + public dispose() { RefreshSObjectsExecutor.refreshSObjectsCommandCompletionEventEmitter.dispose(); + LibraryCommandletExecutor.libraryCommandCompletionEventEmitter.dispose(); } } diff --git a/packages/salesforcedx-vscode-core/test/jest/commands/util/commandEventDispatcher.test.ts b/packages/salesforcedx-vscode-core/test/jest/commands/util/commandEventDispatcher.test.ts index 0a99db1548..08910b1622 100644 --- a/packages/salesforcedx-vscode-core/test/jest/commands/util/commandEventDispatcher.test.ts +++ b/packages/salesforcedx-vscode-core/test/jest/commands/util/commandEventDispatcher.test.ts @@ -4,6 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { LibraryCommandletExecutor } from '@salesforce/salesforcedx-utils-vscode'; import * as vscode from 'vscode'; import { RefreshSObjectsExecutor } from '../../../../src/commands'; import { CommandEventDispatcher } from '../../../../src/commands/util/commandEventDispatcher'; @@ -46,11 +47,31 @@ describe('CommandEventDispatcher', () => { }); }); + describe('onLibraryCommandCompletion', () => { + const mockDisposable = new vscode.Disposable(() => {}); + + beforeEach(() => { + (LibraryCommandletExecutor as any).onLibraryCommandCompletion = jest + .fn() + .mockReturnValue(mockDisposable); + }); + + it('should call onLibraryCommandCompletion event and return the disposable', () => { + const dispatcher = CommandEventDispatcher.getInstance(); + const listener = () => {}; + const disposable = dispatcher.onLibraryCommandCompletion(listener); + + expect(disposable).toBe(mockDisposable); + expect((LibraryCommandletExecutor as any).onLibraryCommandCompletion).toHaveBeenCalledWith(listener); + }); + }); + describe('dispose', () => { beforeEach(() => { ( RefreshSObjectsExecutor as any ).refreshSObjectsCommandCompletionEventEmitter = { dispose: jest.fn() }; + (LibraryCommandletExecutor as any).libraryCommandCompletionEventEmitter = { dispose: jest.fn() }; }); it('should dispose the refreshSObjectsCommandCompletionEventEmitter', () => { @@ -62,5 +83,13 @@ describe('CommandEventDispatcher', () => { .refreshSObjectsCommandCompletionEventEmitter.dispose ).toHaveBeenCalled(); }); + + it('should dispose the libraryCommandCompletionEventEmitter', () => { + const dispatcher = CommandEventDispatcher.getInstance(); + dispatcher.dispose(); + + expect( + (LibraryCommandletExecutor as any).libraryCommandCompletionEventEmitter.dispose).toHaveBeenCalled(); + }); }); });