diff --git a/src/managers/builtin/pipUtils.ts b/src/managers/builtin/pipUtils.ts index d221f5a8..4cb4549c 100644 --- a/src/managers/builtin/pipUtils.ts +++ b/src/managers/builtin/pipUtils.ts @@ -178,6 +178,7 @@ export async function getProjectInstallable( { location: ProgressLocation.Notification, title: VenvManagerStrings.searchingDependencies, + cancellable: true, }, async (_progress, token) => { const results: Uri[] = ( diff --git a/src/test/managers/builtin/pipUtils.unit.test.ts b/src/test/managers/builtin/pipUtils.unit.test.ts index 076596e8..fb484ad2 100644 --- a/src/test/managers/builtin/pipUtils.unit.test.ts +++ b/src/test/managers/builtin/pipUtils.unit.test.ts @@ -199,4 +199,56 @@ suite('Pip Utils - getProjectInstallable', () => { assert.ok(firstResult.uri, 'Should have a URI'); assert.ok(firstResult.uri.fsPath.startsWith(workspacePath), 'Should be in workspace directory'); }); + + test('should show cancellable progress notification', async () => { + // Arrange: Mock findFiles to return empty results + findFilesStub.resolves([]); + + // Act: Call getProjectInstallable + const workspacePath = Uri.file('/test/path/root').fsPath; + const projects = [{ name: 'workspace', uri: Uri.file(workspacePath) }]; + await getProjectInstallable(mockApi as PythonEnvironmentApi, projects); + + // Assert: Verify withProgress was called with cancellable option + assert.ok(withProgressStub.calledOnce, 'Should call withProgress once'); + const progressOptions = withProgressStub.firstCall.args[0] as ProgressOptions; + assert.strictEqual(progressOptions.cancellable, true, 'Progress should be cancellable'); + }); + + test('should handle cancellation during file search', async () => { + // Arrange: Create a cancellation token that is already cancelled + const cancelledToken: CancellationToken = { + isCancellationRequested: true, + onCancellationRequested: () => ({ dispose: () => {} }), + }; + + // Mock withProgress to immediately call the callback with the cancelled token + withProgressStub.callsFake( + async ( + _options: ProgressOptions, + callback: ( + progress: Progress<{ message?: string; increment?: number }>, + token: CancellationToken, + ) => Thenable, + ) => { + return await callback({} as Progress<{ message?: string; increment?: number }>, cancelledToken); + }, + ); + + // Mock findFiles to check that token is passed + findFilesStub.callsFake((_pattern: string, _exclude: string, _maxResults: number, token: CancellationToken) => { + // Verify the cancellation token is passed to findFiles + assert.strictEqual(token, cancelledToken, 'Cancellation token should be passed to findFiles'); + return Promise.resolve([]); + }); + + // Act: Call getProjectInstallable + const workspacePath = Uri.file('/test/path/root').fsPath; + const projects = [{ name: 'workspace', uri: Uri.file(workspacePath) }]; + await getProjectInstallable(mockApi as PythonEnvironmentApi, projects); + + // Assert: Verify findFiles was called with the cancellation token + assert.ok(findFilesStub.called, 'findFiles should be called'); + assert.strictEqual(findFilesStub.callCount, 4, 'Should call findFiles 4 times for different patterns'); + }); });