diff --git a/news/2 Fixes/7607.md b/news/2 Fixes/7607.md new file mode 100644 index 000000000000..af6109dbd415 --- /dev/null +++ b/news/2 Fixes/7607.md @@ -0,0 +1 @@ +Added prompt to flip "inheritEnv" setting to false to fix conda activation issue diff --git a/package.nls.json b/package.nls.json index 0dc62bb90625..1c70d070fa5c 100644 --- a/package.nls.json +++ b/package.nls.json @@ -121,6 +121,7 @@ "Experiments.inGroup": "User belongs to experiment group '{0}'", "Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters", "Interpreters.LoadingInterpreters": "Loading Python Interpreters", + "Interpreters.condaInheritEnvMessage": "We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we suggest the \"terminal.integrated.inheritEnv\" setting to be changed to false. Would you like to update this setting?", "Logging.CurrentWorkingDirectory": "cwd:", "Common.doNotShowAgain": "Do not show again", "Common.reload": "Reload", @@ -367,6 +368,6 @@ "DataScience.untitledNotebookMessage": "Your changes will be lost if you don't save them.", "DataScience.untitledNotebookYes": "Save", "DataScience.untitledNotebookNo": "Cancel", - "DataScience.noInterpreter" : "No python selected", - "DataScience.notebookNotFound" : "python -m jupyter notebook --version is not running" + "DataScience.noInterpreter": "No python selected", + "DataScience.notebookNotFound": "python -m jupyter notebook --version is not running" } diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index df6efc20df6f..883d86a28241 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -60,6 +60,7 @@ export namespace Experiments { export namespace Interpreters { export const loading = localize('Interpreters.LoadingInterpreters', 'Loading Python Interpreters'); export const refreshing = localize('Interpreters.RefreshingInterpreters', 'Refreshing Python Interpreters'); + export const condaInheritEnvMessage = localize('Interpreters.condaInheritEnvMessage', 'We noticed you\'re using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we suggest the \"terminal.integrated.inheritEnv\" setting to be changed to false. Would you like to update this setting?'); export const environmentPromptMessage = localize('Interpreters.environmentPromptMessage', 'We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?'); export const selectInterpreterTip = localize('Interpreters.selectInterpreterTip', 'Tip: you can change the Python interpreter used by the Python extension by clicking on the Python version in the status bar'); } diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index 6e12603f717f..d1796e922717 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -74,6 +74,7 @@ import { WindowsStoreInterpreter } from './locators/services/windowsStoreInterpr import { WorkspaceVirtualEnvironmentsSearchPathProvider, WorkspaceVirtualEnvService } from './locators/services/workspaceVirtualEnvService'; import { WorkspaceVirtualEnvWatcherService } from './locators/services/workspaceVirtualEnvWatcherService'; import { IPipEnvServiceHelper, IPythonInPathCommandProvider } from './locators/types'; +import { CondaInheritEnvPrompt } from './virtualEnvs/condaInheritEnvPrompt'; import { VirtualEnvironmentManager } from './virtualEnvs/index'; import { IVirtualEnvironmentManager } from './virtualEnvs/types'; import { VirtualEnvironmentPrompt } from './virtualEnvs/virtualEnvPrompt'; @@ -135,6 +136,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IEnvironmentActivationService, EnvironmentActivationService); + serviceManager.addSingleton(IExtensionActivationService, CondaInheritEnvPrompt); serviceManager.addSingleton(WindowsStoreInterpreter, WindowsStoreInterpreter); serviceManager.addSingleton(InterpreterHashProvider, InterpreterHashProvider); serviceManager.addSingleton(InterpeterHashProviderFactory, InterpeterHashProviderFactory); diff --git a/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts b/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts new file mode 100644 index 000000000000..0dfec6eb5780 --- /dev/null +++ b/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable, optional } from 'inversify'; +import { ConfigurationTarget, Uri } from 'vscode'; +import { IExtensionActivationService } from '../../activation/types'; +import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; +import { traceDecorators, traceError } from '../../common/logger'; +import { IPersistentStateFactory } from '../../common/types'; +import { InteractiveShiftEnterBanner, Interpreters } from '../../common/utils/localize'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { IInterpreterService, InterpreterType } from '../contracts'; + +export const condaInheritEnvPromptKey = 'CONDA_INHERIT_ENV_PROMPT_KEY'; + +@injectable() +export class CondaInheritEnvPrompt implements IExtensionActivationService { + constructor( + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, + @optional() public hasPromptBeenShownInCurrentSession: boolean = false + ) { } + + public async activate(resource: Uri): Promise { + this.initializeInBackground(resource).ignoreErrors(); + } + + @traceDecorators.error('Failed to intialize conda inherit env prompt') + public async initializeInBackground(resource: Uri): Promise { + const show = await this.shouldShowPrompt(resource); + if (!show) { + return; + } + await this.promptAndUpdate(); + } + + @traceDecorators.error('Failed to display conda inherit env prompt') + public async promptAndUpdate() { + const notificationPromptEnabled = this.persistentStateFactory.createGlobalPersistentState(condaInheritEnvPromptKey, true); + if (!notificationPromptEnabled.value) { + return; + } + const prompts = [InteractiveShiftEnterBanner.bannerLabelYes(), InteractiveShiftEnterBanner.bannerLabelNo()]; + const telemetrySelections: ['Yes', 'No'] = ['Yes', 'No']; + const selection = await this.appShell.showInformationMessage(Interpreters.condaInheritEnvMessage(), ...prompts); + sendTelemetryEvent(EventName.CONDA_INHERIT_ENV_PROMPT, undefined, { selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined }); + if (!selection) { + return; + } + if (selection === prompts[0]) { + await this.workspaceService.getConfiguration('terminal').update('integrated.inheritEnv', false, ConfigurationTarget.Global); + } else if (selection === prompts[1]) { + await notificationPromptEnabled.updateValue(false); + } + } + + @traceDecorators.error('Failed to check whether to display prompt for conda inherit env setting') + public async shouldShowPrompt(resource: Uri): Promise { + if (this.hasPromptBeenShownInCurrentSession) { + return false; + } + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + if (!interpreter || interpreter.type !== InterpreterType.Conda) { + return false; + } + const setting = this.workspaceService.getConfiguration('terminal', resource).inspect('integrated.inheritEnv'); + if (!setting) { + traceError('WorkspaceConfiguration.inspect returns `undefined` for setting `terminal.integrated.inheritEnv`'); + return false; + } + if (setting.globalValue !== undefined || setting.workspaceValue !== undefined || setting.workspaceFolderValue !== undefined) { + return false; + } + this.hasPromptBeenShownInCurrentSession = true; + return true; + } +} diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index 0cd6515c6c46..a8dbbce7bb99 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -30,6 +30,7 @@ export enum EventName { PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL = 'PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL', TERMINAL_SHELL_IDENTIFICATION = 'TERMINAL_SHELL_IDENTIFICATION', PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT = 'PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT', + CONDA_INHERIT_ENV_PROMPT = 'CONDA_INHERIT_ENV_PROMPT', INSIDERS_RELOAD_PROMPT = 'INSIDERS_RELOAD_PROMPT', INSIDERS_PROMPT = 'INSIDERS_PROMPT', OPT_INTO_INSIDERS_AGAIN_PROMPT = 'OPT_INTO_INSIDERS_AGAIN_PROMPT', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 568fcd1fbe2b..80b0956bb734 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -931,6 +931,17 @@ export interface IEventNamePropertyMapping { */ interpreters?: number; }; + /** + * Telemetry event sent with details when user clicks the prompt with the following message + * `Prompt message` :- 'We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we suggest the "terminal.integrated.inheritEnv" setting to be changed to false. Would you like to update this setting?' + */ + [EventName.CONDA_INHERIT_ENV_PROMPT]: { + /** + * `Yes` When 'Yes' option is selected + * `No` When 'No' option is selected + */ + selection: 'Yes' | 'No' | undefined; + }; /** * Telemetry event sent with details when user clicks a button in the virtual environment prompt. * `Prompt message` :- 'We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?' diff --git a/src/test/interpreters/serviceRegistry.unit.test.ts b/src/test/interpreters/serviceRegistry.unit.test.ts index e8d7b2af517b..b3ed9a91b5fd 100644 --- a/src/test/interpreters/serviceRegistry.unit.test.ts +++ b/src/test/interpreters/serviceRegistry.unit.test.ts @@ -82,6 +82,7 @@ import { WorkspaceVirtualEnvWatcherService } from '../../client/interpreter/loca import { IPipEnvServiceHelper, IPythonInPathCommandProvider } from '../../client/interpreter/locators/types'; import { registerTypes } from '../../client/interpreter/serviceRegistry'; import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; +import { CondaInheritEnvPrompt } from '../../client/interpreter/virtualEnvs/condaInheritEnvPrompt'; import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; import { VirtualEnvironmentPrompt } from '../../client/interpreter/virtualEnvs/virtualEnvPrompt'; import { ServiceManager } from '../../client/ioc/serviceManager'; @@ -142,6 +143,7 @@ suite('Interpreters - Service Registry', () => { [IInterpreterAutoSelectionService, InterpreterAutoSelectionService], [IEnvironmentActivationService, EnvironmentActivationService], + [IExtensionActivationService, CondaInheritEnvPrompt], [WindowsStoreInterpreter, WindowsStoreInterpreter], [InterpreterHashProvider, InterpreterHashProvider], diff --git a/src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts b/src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts new file mode 100644 index 000000000000..8939ee595c57 --- /dev/null +++ b/src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts @@ -0,0 +1,377 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { assert, expect } from 'chai'; +import * as sinon from 'sinon'; +import { instance, mock, verify, when } from 'ts-mockito'; +import * as TypeMoq from 'typemoq'; +import { ConfigurationTarget, Uri, WorkspaceConfiguration } from 'vscode'; +import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; +import { PersistentStateFactory } from '../../../client/common/persistentState'; +import { IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; +import { createDeferred, createDeferredFromPromise, sleep } from '../../../client/common/utils/async'; +import { InteractiveShiftEnterBanner, Interpreters } from '../../../client/common/utils/localize'; +import { IInterpreterService, InterpreterType } from '../../../client/interpreter/contracts'; +import { CondaInheritEnvPrompt, condaInheritEnvPromptKey } from '../../../client/interpreter/virtualEnvs/condaInheritEnvPrompt'; + +// tslint:disable:no-any + +// tslint:disable:max-func-body-length +suite('Conda Inherit Env Prompt', async () => { + const resource = Uri.file('a'); + let workspaceService: TypeMoq.IMock; + let appShell: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + let persistentStateFactory: IPersistentStateFactory; + let notificationPromptEnabled: TypeMoq.IMock>; + let condaInheritEnvPrompt: CondaInheritEnvPrompt; + function verifyAll() { + workspaceService.verifyAll(); + appShell.verifyAll(); + interpreterService.verifyAll(); + } + + suite('Method shouldShowPrompt()', () => { + setup(() => { + workspaceService = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); + persistentStateFactory = mock(PersistentStateFactory); + condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + }); + test('Returns false if prompt has already been shown in the current session', async () => { + condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory), true); + const workspaceConfig = TypeMoq.Mock.ofType(); + interpreterService + .setup(is => is.getActiveInterpreter(resource)) + .returns(() => Promise.resolve(undefined) as any) + .verifiable(TypeMoq.Times.never()); + workspaceService + .setup(ws => ws.getConfiguration('terminal', resource)) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.never()); + const result = await condaInheritEnvPrompt.shouldShowPrompt(resource); + expect(result).to.equal(false, 'Prompt should not be shown'); + expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(true, 'Should be true'); + verifyAll(); + }); + test('Returns false if active interpreter is not of type Conda', async () => { + const interpreter = { + type: InterpreterType.Pipenv + }; + const workspaceConfig = TypeMoq.Mock.ofType(); + interpreterService + .setup(is => is.getActiveInterpreter(resource)) + .returns(() => Promise.resolve(interpreter) as any) + .verifiable(TypeMoq.Times.once()); + workspaceService + .setup(ws => ws.getConfiguration('terminal', resource)) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.never()); + const result = await condaInheritEnvPrompt.shouldShowPrompt(resource); + expect(result).to.equal(false, 'Prompt should not be shown'); + expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(false, 'Should be false'); + verifyAll(); + }); + test('Returns false if no active interpreter is present', async () => { + const workspaceConfig = TypeMoq.Mock.ofType(); + interpreterService + .setup(is => is.getActiveInterpreter(resource)) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.once()); + workspaceService + .setup(ws => ws.getConfiguration('terminal', resource)) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.never()); + const result = await condaInheritEnvPrompt.shouldShowPrompt(resource); + expect(result).to.equal(false, 'Prompt should not be shown'); + expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(false, 'Should be false'); + verifyAll(); + }); + test('Returns false if settings returned is `undefined`', async () => { + const interpreter = { + type: InterpreterType.Conda + }; + const workspaceConfig = TypeMoq.Mock.ofType(); + interpreterService + .setup(is => is.getActiveInterpreter(resource)) + .returns(() => Promise.resolve(interpreter) as any) + .verifiable(TypeMoq.Times.once()); + workspaceService + .setup(ws => ws.getConfiguration('terminal', resource)) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.once()); + workspaceConfig + .setup(ws => ws.inspect('integrated.inheritEnv')) + .returns(() => undefined); + const result = await condaInheritEnvPrompt.shouldShowPrompt(resource); + expect(result).to.equal(false, 'Prompt should not be shown'); + expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(false, 'Should be false'); + verifyAll(); + }); + [ + { + name: 'Returns false if globalValue `terminal.integrated.inheritEnv` setting is set', + settings: { + globalValue: true, + workspaceValue: undefined, + workspaceFolderValue: undefined + } + }, + { + name: 'Returns false if workspaceValue of `terminal.integrated.inheritEnv` setting is set', + settings: { + globalValue: undefined, + workspaceValue: true, + workspaceFolderValue: undefined + } + }, + { + name: 'Returns false if workspaceFolderValue of `terminal.integrated.inheritEnv` setting is set', + settings: { + globalValue: undefined, + workspaceValue: undefined, + workspaceFolderValue: false + } + } + ].forEach(testParams => { + test(testParams.name, async () => { + const interpreter = { + type: InterpreterType.Conda + }; + const workspaceConfig = TypeMoq.Mock.ofType(); + interpreterService + .setup(is => is.getActiveInterpreter(resource)) + .returns(() => Promise.resolve(interpreter) as any) + .verifiable(TypeMoq.Times.once()); + workspaceService + .setup(ws => ws.getConfiguration('terminal', resource)) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.once()); + workspaceConfig + .setup(ws => ws.inspect('integrated.inheritEnv')) + .returns(() => testParams.settings as any); + const result = await condaInheritEnvPrompt.shouldShowPrompt(resource); + expect(result).to.equal(false, 'Prompt should not be shown'); + expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(false, 'Should be false'); + verifyAll(); + }); + }); + test('Returns true otherwise', async () => { + const interpreter = { + type: InterpreterType.Conda + }; + const settings = { + globalValue: undefined, + workspaceValue: undefined, + workspaceFolderValue: undefined + }; + const workspaceConfig = TypeMoq.Mock.ofType(); + interpreterService + .setup(is => is.getActiveInterpreter(resource)) + .returns(() => Promise.resolve(interpreter) as any) + .verifiable(TypeMoq.Times.once()); + workspaceService + .setup(ws => ws.getConfiguration('terminal', resource)) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.once()); + workspaceConfig + .setup(ws => ws.inspect('integrated.inheritEnv')) + .returns(() => settings as any); + const result = await condaInheritEnvPrompt.shouldShowPrompt(resource); + expect(result).to.equal(true, 'Prompt should be shown'); + expect(condaInheritEnvPrompt.hasPromptBeenShownInCurrentSession).to.equal(true, 'Should be true'); + verifyAll(); + }); + }); + suite('Method activate()', () => { + let initializeInBackground: sinon.SinonStub; + setup(() => { + workspaceService = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); + persistentStateFactory = mock(PersistentStateFactory); + }); + + teardown(() => { + sinon.restore(); + }); + + test('Invokes initializeInBackground() in the background', async () => { + const initializeInBackgroundDeferred = createDeferred(); + initializeInBackground = sinon.stub(CondaInheritEnvPrompt.prototype, 'initializeInBackground'); + initializeInBackground.callsFake(() => initializeInBackgroundDeferred.promise); + condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + + const promise = condaInheritEnvPrompt.activate(resource); + const deferred = createDeferredFromPromise(promise); + await sleep(1); + + // Ensure activate() function has completed while initializeInBackground() is still not resolved + assert.equal(deferred.completed, true); + + initializeInBackgroundDeferred.resolve(); + await sleep(1); + assert.ok(initializeInBackground.calledOnce); + }); + + test('Ignores errors raised by initializeInBackground()', async () => { + initializeInBackground = sinon.stub(CondaInheritEnvPrompt.prototype, 'initializeInBackground'); + initializeInBackground.rejects(new Error('Kaboom')); + condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + await condaInheritEnvPrompt.activate(resource); + assert.ok(initializeInBackground.calledOnce); + }); + }); + + suite('Method initializeInBackground()', () => { + let shouldShowPrompt: sinon.SinonStub; + let promptAndUpdate: sinon.SinonStub; + setup(() => { + workspaceService = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); + persistentStateFactory = mock(PersistentStateFactory); + }); + + teardown(() => { + sinon.restore(); + }); + + test('Show prompt if shouldShowPrompt() returns true', async () => { + shouldShowPrompt = sinon.stub(CondaInheritEnvPrompt.prototype, 'shouldShowPrompt'); + shouldShowPrompt.callsFake(() => Promise.resolve(true)); + promptAndUpdate = sinon.stub(CondaInheritEnvPrompt.prototype, 'promptAndUpdate'); + promptAndUpdate.callsFake(() => Promise.resolve(undefined)); + condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + await condaInheritEnvPrompt.initializeInBackground(resource); + assert.ok(shouldShowPrompt.calledOnce); + assert.ok(promptAndUpdate.calledOnce); + }); + + test('Do not show prompt if shouldShowPrompt() returns false', async () => { + shouldShowPrompt = sinon.stub(CondaInheritEnvPrompt.prototype, 'shouldShowPrompt'); + shouldShowPrompt.callsFake(() => Promise.resolve(false)); + promptAndUpdate = sinon.stub(CondaInheritEnvPrompt.prototype, 'promptAndUpdate'); + promptAndUpdate.callsFake(() => Promise.resolve(undefined)); + condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + await condaInheritEnvPrompt.initializeInBackground(resource); + assert.ok(shouldShowPrompt.calledOnce); + assert.ok(promptAndUpdate.notCalled); + }); + }); + + suite('Method promptAndUpdate()', () => { + const prompts = [InteractiveShiftEnterBanner.bannerLabelYes(), InteractiveShiftEnterBanner.bannerLabelNo()]; + setup(() => { + workspaceService = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + interpreterService = TypeMoq.Mock.ofType(); + persistentStateFactory = mock(PersistentStateFactory); + notificationPromptEnabled = TypeMoq.Mock.ofType>(); + when(persistentStateFactory.createGlobalPersistentState(condaInheritEnvPromptKey, true)).thenReturn(notificationPromptEnabled.object); + condaInheritEnvPrompt = new CondaInheritEnvPrompt(interpreterService.object, workspaceService.object, appShell.object, instance(persistentStateFactory)); + }); + + test('Does not display prompt if it is disabled', async () => { + notificationPromptEnabled + .setup(n => n.value) + .returns(() => false) + .verifiable(TypeMoq.Times.once()); + appShell + .setup(a => a.showInformationMessage(Interpreters.condaInheritEnvMessage(), ...prompts)) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.never()); + await condaInheritEnvPrompt.promptAndUpdate(); + verify(persistentStateFactory.createGlobalPersistentState(condaInheritEnvPromptKey, true)).once(); + verifyAll(); + notificationPromptEnabled.verifyAll(); + }); + test('Do nothing if no option is selected', async () => { + const workspaceConfig = TypeMoq.Mock.ofType(); + notificationPromptEnabled + .setup(n => n.value) + .returns(() => true) + .verifiable(TypeMoq.Times.once()); + appShell + .setup(a => a.showInformationMessage(Interpreters.condaInheritEnvMessage(), ...prompts)) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.once()); + workspaceService + .setup(ws => ws.getConfiguration('terminal')) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.never()); + workspaceConfig + .setup(wc => wc.update('integrated.inheritEnv', false, ConfigurationTarget.Global)) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.never()); + notificationPromptEnabled + .setup(n => n.updateValue(false)) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.never()); + await condaInheritEnvPrompt.promptAndUpdate(); + verify(persistentStateFactory.createGlobalPersistentState(condaInheritEnvPromptKey, true)).once(); + verifyAll(); + workspaceConfig.verifyAll(); + notificationPromptEnabled.verifyAll(); + }); + test('Update terminal settings if `Yes` is selected', async () => { + const workspaceConfig = TypeMoq.Mock.ofType(); + notificationPromptEnabled + .setup(n => n.value) + .returns(() => true) + .verifiable(TypeMoq.Times.once()); + appShell + .setup(a => a.showInformationMessage(Interpreters.condaInheritEnvMessage(), ...prompts)) + .returns(() => Promise.resolve(InteractiveShiftEnterBanner.bannerLabelYes())) + .verifiable(TypeMoq.Times.once()); + workspaceService + .setup(ws => ws.getConfiguration('terminal')) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.once()); + workspaceConfig + .setup(wc => wc.update('integrated.inheritEnv', false, ConfigurationTarget.Global)) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + notificationPromptEnabled + .setup(n => n.updateValue(false)) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.never()); + await condaInheritEnvPrompt.promptAndUpdate(); + verify(persistentStateFactory.createGlobalPersistentState(condaInheritEnvPromptKey, true)).once(); + verifyAll(); + workspaceConfig.verifyAll(); + notificationPromptEnabled.verifyAll(); + }); + test('Disable notification prompt if `No` is selected', async () => { + const workspaceConfig = TypeMoq.Mock.ofType(); + notificationPromptEnabled + .setup(n => n.value) + .returns(() => true) + .verifiable(TypeMoq.Times.once()); + appShell + .setup(a => a.showInformationMessage(Interpreters.condaInheritEnvMessage(), ...prompts)) + .returns(() => Promise.resolve(InteractiveShiftEnterBanner.bannerLabelNo())) + .verifiable(TypeMoq.Times.once()); + workspaceService + .setup(ws => ws.getConfiguration('terminal')) + .returns(() => workspaceConfig.object) + .verifiable(TypeMoq.Times.never()); + workspaceConfig + .setup(wc => wc.update('integrated.inheritEnv', false, ConfigurationTarget.Global)) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.never()); + notificationPromptEnabled + .setup(n => n.updateValue(false)) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.once()); + await condaInheritEnvPrompt.promptAndUpdate(); + verify(persistentStateFactory.createGlobalPersistentState(condaInheritEnvPromptKey, true)).once(); + verifyAll(); + workspaceConfig.verifyAll(); + notificationPromptEnabled.verifyAll(); + }); + }); +});