|
2 | 2 | // Licensed under the MIT License. |
3 | 3 |
|
4 | 4 | import * as assert from 'assert'; |
| 5 | +import fse from 'fs-extra'; |
5 | 6 | import * as path from 'path'; |
6 | 7 | import * as sinon from 'sinon'; |
7 | 8 | import * as typeMoq from 'typemoq'; |
@@ -151,6 +152,107 @@ suite('TerminalEnvVarInjector', () => { |
151 | 152 | assert.strictEqual(mockScopedCollection.replace.called, false); |
152 | 153 | }); |
153 | 154 |
|
| 155 | + suite('env file changes', () => { |
| 156 | + let envChangeCallback: ((args: { uri?: Uri; changeType: number }) => void) | undefined; |
| 157 | + let variables: Record<string, string>; |
| 158 | + let existingFiles: Set<string>; |
| 159 | + const defaultEnvFile = path.join(testWorkspaceFolder.uri.fsPath, '.env'); |
| 160 | + const configuredEnvFile = path.join(testWorkspaceFolder.uri.fsPath, 'configured.env'); |
| 161 | + |
| 162 | + setup(() => { |
| 163 | + variables = {}; |
| 164 | + existingFiles = new Set(); |
| 165 | + sinon.stub(fse, 'pathExists').callsFake(async (filePath) => existingFiles.has(path.resolve(filePath.toString()))); |
| 166 | + sinon.stub(workspaceApis, 'getWorkspaceFolder').returns(testWorkspaceFolder); |
| 167 | + envVarManager.reset(); |
| 168 | + envVarManager.setup((m) => m.onDidChangeEnvironmentVariables).returns( |
| 169 | + () => (listener) => { |
| 170 | + envChangeCallback = listener; |
| 171 | + return new Disposable(() => {}); |
| 172 | + }, |
| 173 | + ); |
| 174 | + envVarManager |
| 175 | + .setup((m) => m.getEnvironmentVariables(typeMoq.It.isAny())) |
| 176 | + .returns(() => Promise.resolve({ ...variables })); |
| 177 | + }); |
| 178 | + |
| 179 | + async function fireChange(changeType: number, filePath = defaultEnvFile): Promise<void> { |
| 180 | + assert.ok(envChangeCallback); |
| 181 | + envChangeCallback({ uri: Uri.file(filePath), changeType }); |
| 182 | + await new Promise((resolve) => setTimeout(resolve, 20)); |
| 183 | + } |
| 184 | + |
| 185 | + test('creating an env file with injection disabled preserves shell activation variables', async () => { |
| 186 | + getConfigurationStub.returns(createMockConfig({ useEnvFile: false }) as WorkspaceConfiguration); |
| 187 | + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); |
| 188 | + await fireChange(2); |
| 189 | + |
| 190 | + sinon.assert.notCalled(mockScopedCollection.clear); |
| 191 | + sinon.assert.notCalled(mockScopedCollection.delete); |
| 192 | + sinon.assert.notCalled(mockScopedCollection.replace); |
| 193 | + }); |
| 194 | + |
| 195 | + test('creating, editing, and deleting an env file updates only its injected variables', async () => { |
| 196 | + getConfigurationStub.returns(createMockConfig({ useEnvFile: true }) as WorkspaceConfiguration); |
| 197 | + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); |
| 198 | + await new Promise((resolve) => setTimeout(resolve, 20)); |
| 199 | + |
| 200 | + existingFiles.add(defaultEnvFile); |
| 201 | + variables = { TERMINAL_PROBE_VALUE: 'created' }; |
| 202 | + await fireChange(2); |
| 203 | + sinon.assert.calledWith(mockScopedCollection.replace, 'TERMINAL_PROBE_VALUE', 'created'); |
| 204 | + |
| 205 | + variables = { OTHER_VALUE: 'edited' }; |
| 206 | + await fireChange(1); |
| 207 | + sinon.assert.calledWith(mockScopedCollection.delete, 'TERMINAL_PROBE_VALUE'); |
| 208 | + sinon.assert.calledWith(mockScopedCollection.replace, 'OTHER_VALUE', 'edited'); |
| 209 | + |
| 210 | + existingFiles.delete(defaultEnvFile); |
| 211 | + variables = {}; |
| 212 | + await fireChange(3); |
| 213 | + sinon.assert.calledWith(mockScopedCollection.delete, 'OTHER_VALUE'); |
| 214 | + sinon.assert.notCalled(mockScopedCollection.clear); |
| 215 | + }); |
| 216 | + |
| 217 | + test('deleting one env file retains variables from the other configured file', async () => { |
| 218 | + getConfigurationStub.returns( |
| 219 | + createMockConfig({ useEnvFile: true, envFilePath: configuredEnvFile }) as WorkspaceConfiguration, |
| 220 | + ); |
| 221 | + existingFiles.add(defaultEnvFile); |
| 222 | + existingFiles.add(configuredEnvFile); |
| 223 | + variables = { CONFIGURED_VALUE: 'configured', PROJECT_VALUE: 'project' }; |
| 224 | + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); |
| 225 | + await new Promise((resolve) => setTimeout(resolve, 20)); |
| 226 | + |
| 227 | + existingFiles.delete(configuredEnvFile); |
| 228 | + variables = { PROJECT_VALUE: 'project' }; |
| 229 | + await fireChange(3, configuredEnvFile); |
| 230 | + |
| 231 | + sinon.assert.calledWith(mockScopedCollection.delete, 'CONFIGURED_VALUE'); |
| 232 | + sinon.assert.neverCalledWith(mockScopedCollection.delete, 'PROJECT_VALUE'); |
| 233 | + sinon.assert.notCalled(mockScopedCollection.clear); |
| 234 | + }); |
| 235 | + |
| 236 | + test('deleting the project env file retains variables from the configured file', async () => { |
| 237 | + getConfigurationStub.returns( |
| 238 | + createMockConfig({ useEnvFile: true, envFilePath: configuredEnvFile }) as WorkspaceConfiguration, |
| 239 | + ); |
| 240 | + existingFiles.add(defaultEnvFile); |
| 241 | + existingFiles.add(configuredEnvFile); |
| 242 | + variables = { CONFIGURED_VALUE: 'configured', PROJECT_VALUE: 'project' }; |
| 243 | + injector = new TerminalEnvVarInjector(envVarCollection.object, envVarManager.object); |
| 244 | + await new Promise((resolve) => setTimeout(resolve, 20)); |
| 245 | + |
| 246 | + existingFiles.delete(defaultEnvFile); |
| 247 | + variables = { CONFIGURED_VALUE: 'configured' }; |
| 248 | + await fireChange(3); |
| 249 | + |
| 250 | + sinon.assert.calledWith(mockScopedCollection.delete, 'PROJECT_VALUE'); |
| 251 | + sinon.assert.neverCalledWith(mockScopedCollection.delete, 'CONFIGURED_VALUE'); |
| 252 | + sinon.assert.notCalled(mockScopedCollection.clear); |
| 253 | + }); |
| 254 | + }); |
| 255 | + |
154 | 256 | test('should NOT inject when useEnvFile is false even with python.envFile configured', async () => { |
155 | 257 | getConfigurationStub.returns( |
156 | 258 | createMockConfig({ |
|
0 commit comments