Skip to content

Commit 66a6bc6

Browse files
Copilotedvilme
andauthored
Fix localized Conda named environment creation (#1770)
Conda named environment creation could silently route to the prefix flow when VS Code used a translated display language. The flow stored the localized “Named” label, then compared it against the English literal `"Named"`. - **Conda creation flow** - Compare against `CondaStrings.condaNamed` instead of a hard-coded English string. - Keep the multi-workspace “force named environment” path consistent with the localized discriminator. ```ts return state.envType === CondaStrings.condaNamed ? enterEnvironmentName : selectLocation; ``` - **Regression coverage** - Added a focused unit test that simulates a localized “Named” label and verifies the flow prompts for an environment name and calls named Conda creation. <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #1767 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> Co-authored-by: Eduardo Villalpando Mello <eduardovil@microsoft.com>
1 parent 6cb40a9 commit 66a6bc6

2 files changed

Lines changed: 82 additions & 5 deletions

File tree

‎src/managers/conda/condaStepBasedFlow.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import {
1414

1515
// Recommended Python version for Conda environments
1616
const RECOMMENDED_CONDA_PYTHON = '3.11.11';
17+
const DEFAULT_CONDA_NAMED_LABEL = 'Named';
18+
19+
function getCondaNamedLabel(): string {
20+
return CondaStrings.condaNamed || DEFAULT_CONDA_NAMED_LABEL;
21+
}
1722

1823
/**
1924
* State interface for the Conda environment creation flow.
@@ -60,13 +65,13 @@ async function selectEnvironmentType(state: CondaCreationState): Promise<StepFun
6065
try {
6166
// Skip this step if we have multiple URIs (force named environment)
6267
if (state.uris && state.uris.length > 1) {
63-
state.envType = 'Named';
68+
state.envType = getCondaNamedLabel();
6469
return selectPythonVersion;
6570
}
6671

6772
const selection = (await showQuickPickWithButtons(
6873
[
69-
{ label: CondaStrings.condaNamed, description: CondaStrings.condaNamedDescription },
74+
{ label: getCondaNamedLabel(), description: CondaStrings.condaNamedDescription },
7075
{ label: CondaStrings.condaPrefix, description: CondaStrings.condaPrefixDescription },
7176
],
7277
{
@@ -129,7 +134,7 @@ async function selectPythonVersion(state: CondaCreationState): Promise<StepFunct
129134
state.pythonVersion = (selection as QuickPickItem).description;
130135

131136
// Next step depends on environment type
132-
return state.envType === 'Named' ? enterEnvironmentName : selectLocation;
137+
return state.envType === getCondaNamedLabel() ? enterEnvironmentName : selectLocation;
133138
} catch (ex) {
134139
if (ex === QuickInputButtons.Back) {
135140
// Go back to environment type selection
@@ -292,7 +297,7 @@ export async function createStepBasedCondaFlow(
292297
}
293298

294299
// If we have all required data, create the environment
295-
if (state.envType === CondaStrings.condaNamed && state.envName) {
300+
if (state.envType === getCondaNamedLabel() && state.envName) {
296301
return await createNamedCondaEnvironment(api, log, manager, state.envName, state.pythonVersion);
297302
} else if (state.envType === CondaStrings.condaPrefix && state.prefix) {
298303
// For prefix environments, we need to pass the fsPath where the environment will be created

‎src/test/managers/conda/condaEnvManager.createRemove.unit.test.ts‎

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@ import * as fse from 'fs-extra';
44
import * as os from 'os';
55
import * as path from 'path';
66
import * as sinon from 'sinon';
7-
import { Uri } from 'vscode';
7+
import { QuickPickItem, Uri } from 'vscode';
88
import {
99
DidChangeEnvironmentEventArgs,
1010
DidChangeEnvironmentsEventArgs,
11+
EnvironmentManager,
1112
EnvironmentChangeKind,
1213
PythonEnvironment,
1314
PythonEnvironmentApi,
1415
PythonProject,
1516
} from '../../../api';
17+
import { CondaStrings } from '../../../common/localize';
1618
import * as windowApis from '../../../common/window.apis';
1719
import { NativePythonFinder } from '../../../managers/common/nativePythonFinder';
1820
import { CondaEnvManager } from '../../../managers/conda/condaEnvManager';
21+
import { createStepBasedCondaFlow } from '../../../managers/conda/condaStepBasedFlow';
1922
import * as condaUtils from '../../../managers/conda/condaUtils';
23+
import { createMockLogOutputChannel } from '../../mocks/helper';
2024
import { makeMockCondaEnvironment as makeEnv } from '../../mocks/pythonEnvironment';
2125

2226
const TEST_ROOT = Uri.file(path.join(os.tmpdir(), 'vscode-python-envs-tests', 'conda-manager')).fsPath;
@@ -46,6 +50,74 @@ function createManager(apiOverrides?: Partial<PythonEnvironmentApi>): CondaEnvMa
4650
return manager;
4751
}
4852

53+
suite('CondaEnvManager.create - step-based flow', () => {
54+
let originalCondaNamed: string;
55+
56+
setup(() => {
57+
originalCondaNamed = CondaStrings.condaNamed;
58+
Object.defineProperty(CondaStrings, 'condaNamed', { value: '명명됨', configurable: true });
59+
});
60+
61+
teardown(() => {
62+
sinon.restore();
63+
Object.defineProperty(CondaStrings, 'condaNamed', { value: originalCondaNamed, configurable: true });
64+
});
65+
66+
test('routes a localized Named selection to named environment creation', async () => {
67+
const createdEnvironment = {} as PythonEnvironment;
68+
const showQuickPickStub = sinon.stub(windowApis, 'showQuickPickWithButtons');
69+
showQuickPickStub.onFirstCall().callsFake(async (items: readonly QuickPickItem[]) => items[0]);
70+
showQuickPickStub.onSecondCall().resolves({ label: 'Python', description: '3.12' } as QuickPickItem);
71+
const showInputBoxStub = sinon.stub(windowApis, 'showInputBoxWithButtons').resolves('localized-env');
72+
const createNamedStub = sinon.stub(condaUtils, 'createNamedCondaEnvironment').resolves(createdEnvironment);
73+
const createPrefixStub = sinon.stub(condaUtils, 'createPrefixCondaEnvironment');
74+
const api = {
75+
getEnvironments: sinon.stub().resolves([]),
76+
getPythonProject: sinon.stub().returns(undefined),
77+
} as unknown as PythonEnvironmentApi;
78+
79+
const result = await createStepBasedCondaFlow(
80+
api,
81+
createMockLogOutputChannel(),
82+
{} as EnvironmentManager,
83+
Uri.file('workspace'),
84+
);
85+
86+
assert.strictEqual(result, createdEnvironment);
87+
assert.strictEqual((showQuickPickStub.firstCall.args[0] as QuickPickItem[])[0].label, '명명됨');
88+
assert.ok(showInputBoxStub.calledOnce);
89+
assert.ok(createNamedStub.calledOnceWithExactly(api, sinon.match.any, sinon.match.any, 'localized-env', '3.12'));
90+
assert.ok(createPrefixStub.notCalled);
91+
});
92+
93+
test('falls back to English Named when the localized label is unavailable', async () => {
94+
Object.defineProperty(CondaStrings, 'condaNamed', { value: undefined, configurable: true });
95+
const createdEnvironment = {} as PythonEnvironment;
96+
const showQuickPickStub = sinon.stub(windowApis, 'showQuickPickWithButtons');
97+
showQuickPickStub.resolves({ label: 'Python', description: '3.12' } as QuickPickItem);
98+
const showInputBoxStub = sinon.stub(windowApis, 'showInputBoxWithButtons').resolves('fallback-env');
99+
const createNamedStub = sinon.stub(condaUtils, 'createNamedCondaEnvironment').resolves(createdEnvironment);
100+
const createPrefixStub = sinon.stub(condaUtils, 'createPrefixCondaEnvironment');
101+
const api = {
102+
getEnvironments: sinon.stub().resolves([]),
103+
getPythonProject: sinon.stub().returns(undefined),
104+
} as unknown as PythonEnvironmentApi;
105+
106+
const result = await createStepBasedCondaFlow(
107+
api,
108+
createMockLogOutputChannel(),
109+
{} as EnvironmentManager,
110+
[Uri.file(testPath('workspace', 'one')), Uri.file(testPath('workspace', 'two'))],
111+
);
112+
113+
assert.strictEqual(result, createdEnvironment);
114+
assert.ok(showQuickPickStub.calledOnce);
115+
assert.ok(showInputBoxStub.calledOnce);
116+
assert.ok(createNamedStub.calledOnceWithExactly(api, sinon.match.any, sinon.match.any, 'fallback-env', '3.12'));
117+
assert.ok(createPrefixStub.notCalled);
118+
});
119+
});
120+
49121
suite('CondaEnvManager.create - orchestration', () => {
50122
let createCondaStub: sinon.SinonStub;
51123
let generateNameStub: sinon.SinonStub;

0 commit comments

Comments
 (0)