@@ -6,7 +6,7 @@ import { expect } from 'chai';
66import * as path from 'path' ;
77import * as sinon from 'sinon' ;
88import * as TypeMoq from 'typemoq' ;
9- import { ConfigurationTarget , OpenDialogOptions , QuickPickItem , Uri } from 'vscode' ;
9+ import { ConfigurationTarget , OpenDialogOptions , QuickPick , QuickPickItem , Uri } from 'vscode' ;
1010import { cloneDeep } from 'lodash' ;
1111import { instance , mock , verify , when } from 'ts-mockito' ;
1212import { IApplicationShell , ICommandManager , IWorkspaceService } from '../../../../client/common/application/types' ;
@@ -36,6 +36,7 @@ import { MockWorkspaceConfiguration } from '../../../mocks/mockWorkspaceConfig';
3636import { Octicons } from '../../../../client/common/constants' ;
3737import { IInterpreterService , PythonEnvironmentsChangedEvent } from '../../../../client/interpreter/contracts' ;
3838import { createDeferred , sleep } from '../../../../client/common/utils/async' ;
39+ import { SystemVariables } from '../../../../client/common/variables/systemVariables' ;
3940
4041const untildify = require ( 'untildify' ) ;
4142
@@ -219,6 +220,90 @@ suite('Set Interpreter Command', () => {
219220 assert . deepStrictEqual ( actualParameters , expectedParameters , 'Params not equal' ) ;
220221 } ) ;
221222
223+ test ( 'If system variables are used in the default interpreter path, make sure they are resolved when the path is displayed' , async ( ) => {
224+ // Create a SetInterpreterCommand instance from scratch, and use a different defaultInterpreterPath from the rest of the tests.
225+ const workspaceDefaultInterpreterPath = '${workspaceFolder}/defaultInterpreterPath' ;
226+
227+ const systemVariables = new SystemVariables ( undefined , undefined , workspace . object ) ;
228+ const pathUtils = new PathUtils ( false ) ;
229+
230+ const expandedPath = systemVariables . resolveAny ( workspaceDefaultInterpreterPath ) ;
231+ const expandedDetail = pathUtils . getDisplayName ( expandedPath ) ;
232+
233+ pythonSettings = TypeMoq . Mock . ofType < IPythonSettings > ( ) ;
234+ workspace = TypeMoq . Mock . ofType < IWorkspaceService > ( ) ;
235+
236+ pythonSettings . setup ( ( p ) => p . pythonPath ) . returns ( ( ) => currentPythonPath ) ;
237+ pythonSettings . setup ( ( p ) => p . defaultInterpreterPath ) . returns ( ( ) => workspaceDefaultInterpreterPath ) ;
238+ configurationService . setup ( ( x ) => x . getSettings ( TypeMoq . It . isAny ( ) ) ) . returns ( ( ) => pythonSettings . object ) ;
239+ workspace . setup ( ( w ) => w . rootPath ) . returns ( ( ) => 'rootPath' ) ;
240+ workspace
241+ . setup ( ( w ) => w . getConfiguration ( TypeMoq . It . isValue ( 'python' ) , TypeMoq . It . isAny ( ) ) )
242+ . returns (
243+ ( ) =>
244+ new MockWorkspaceConfiguration ( {
245+ defaultInterpreterPath : workspaceDefaultInterpreterPath ,
246+ } ) ,
247+ ) ;
248+
249+ setInterpreterCommand = new SetInterpreterCommand (
250+ appShell . object ,
251+ pathUtils ,
252+ pythonPathUpdater . object ,
253+ configurationService . object ,
254+ commandManager . object ,
255+ multiStepInputFactory . object ,
256+ platformService . object ,
257+ interpreterSelector . object ,
258+ workspace . object ,
259+ instance ( interpreterService ) ,
260+ ) ;
261+
262+ // Test info
263+ const state : InterpreterStateArgs = { path : 'some path' , workspace : undefined } ;
264+ const multiStepInput = TypeMoq . Mock . ofType < IMultiStepInput < InterpreterStateArgs > > ( ) ;
265+ const recommended = cloneDeep ( item ) ;
266+ recommended . label = `${ Octicons . Star } ${ item . label } ` ;
267+ recommended . description = Common . recommended ( ) ;
268+
269+ const defaultPathSuggestion = {
270+ label : `${ Octicons . Gear } ${ InterpreterQuickPickList . defaultInterpreterPath . label ( ) } ` ,
271+ detail : expandedDetail ,
272+ path : expandedPath ,
273+ alwaysShow : true ,
274+ } ;
275+
276+ const suggestions = [ expectedEnterInterpreterPathSuggestion , defaultPathSuggestion , recommended ] ;
277+ const expectedParameters : IQuickPickParameters < QuickPickItem > = {
278+ placeholder : InterpreterQuickPickList . quickPickListPlaceholder ( ) . format ( currentPythonPath ) ,
279+ items : suggestions ,
280+ activeItem : recommended ,
281+ matchOnDetail : true ,
282+ matchOnDescription : true ,
283+ title : InterpreterQuickPickList . browsePath . openButtonLabel ( ) ,
284+ sortByLabel : true ,
285+ keepScrollPosition : true ,
286+ } ;
287+ let actualParameters : IQuickPickParameters < QuickPickItem > | undefined ;
288+ multiStepInput
289+ . setup ( ( i ) => i . showQuickPick ( TypeMoq . It . isAny ( ) ) )
290+ . callback ( ( options ) => {
291+ actualParameters = options ;
292+ } )
293+ . returns ( ( ) => Promise . resolve ( ( undefined as unknown ) as QuickPickItem ) ) ;
294+
295+ await setInterpreterCommand . _pickInterpreter ( multiStepInput . object , state ) ;
296+
297+ expect ( actualParameters ) . to . not . equal ( undefined , 'Parameters not set' ) ;
298+ const refreshButtonCallback = actualParameters ! . customButtonSetup ?. callback ;
299+ expect ( refreshButtonCallback ) . to . not . equal ( undefined , 'Callback not set' ) ;
300+
301+ delete actualParameters ! . customButtonSetup ;
302+ delete actualParameters ! . onChangeItem ;
303+
304+ assert . deepStrictEqual ( actualParameters , expectedParameters , 'Params not equal' ) ;
305+ } ) ;
306+
222307 test ( 'Ensure a refresh is triggered if refresh button is clicked' , async ( ) => {
223308 const state : InterpreterStateArgs = { path : 'some path' , workspace : undefined } ;
224309 const multiStepInput = TypeMoq . Mock . ofType < IMultiStepInput < InterpreterStateArgs > > ( ) ;
@@ -237,8 +322,7 @@ suite('Set Interpreter Command', () => {
237322 expect ( refreshButtonCallback ) . to . not . equal ( undefined , 'Callback not set' ) ;
238323
239324 when ( interpreterService . triggerRefresh ( ) ) . thenResolve ( ) ;
240- // eslint-disable-next-line @typescript-eslint/no-explicit-any
241- await refreshButtonCallback ! ( { } as any ) ; // Invoke callback, meaning that the refresh button is clicked.
325+ await refreshButtonCallback ! ( { } as QuickPick < QuickPickItem > ) ; // Invoke callback, meaning that the refresh button is clicked.
242326 verify ( interpreterService . triggerRefresh ( ) ) . once ( ) ;
243327 } ) ;
244328
@@ -276,8 +360,7 @@ suite('Set Interpreter Command', () => {
276360 old : item . interpreter ,
277361 new : refreshedItem . interpreter ,
278362 } ;
279- // eslint-disable-next-line @typescript-eslint/no-explicit-any
280- await onChangedCallback ! ( changeEvent , quickPick as any ) ; // Invoke callback, meaning that the items are supposed to change.
363+ await onChangedCallback ! ( changeEvent , ( quickPick as unknown ) as QuickPick < QuickPickItem > ) ; // Invoke callback, meaning that the items are supposed to change.
281364
282365 assert . deepStrictEqual (
283366 quickPick ,
0 commit comments