Skip to content

Commit f7fbd29

Browse files
authored
Add unit tests for terminal.ts (#1796)
1 parent c66856e commit f7fbd29

File tree

2 files changed

+288
-3
lines changed

2 files changed

+288
-3
lines changed

src/terminal.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import * as vscode from "vscode";
1616
import configuration from "./configuration";
1717

1818
/** The separator to use between paths in the PATH environment variable */
19-
const pathSeparator: string = process.platform === "win32" ? ";" : ":";
19+
const pathSeparator = () => (process.platform === "win32" ? ";" : ":");
2020

2121
/**
2222
* Configures Swift environment variables for VS Code. Will automatically update
@@ -56,7 +56,7 @@ export class SwiftEnvironmentVariablesManager implements vscode.Disposable {
5656
}
5757

5858
if (configuration.path) {
59-
environment.prepend("PATH", configuration.path + pathSeparator, {
59+
environment.prepend("PATH", configuration.path + pathSeparator(), {
6060
applyAtShellIntegration: true,
6161
});
6262
}
@@ -80,7 +80,7 @@ export class SwiftTerminalProfileProvider implements vscode.TerminalProfileProvi
8080
if (!configuration.enableTerminalEnvironment) {
8181
const disposable = vscode.window.onDidOpenTerminal(terminal => {
8282
if (configuration.path) {
83-
terminal.sendText(`export PATH=${configuration.path + pathSeparator}$PATH`);
83+
terminal.sendText(`export PATH=${configuration.path + pathSeparator()}$PATH`);
8484
}
8585
disposable.dispose();
8686
});

test/unit-tests/terminal.test.ts

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2025 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as vscode from "vscode";
16+
import { expect } from "chai";
17+
import {
18+
mockObject,
19+
mockGlobalValue,
20+
MockedObject,
21+
instance,
22+
mockGlobalObject,
23+
} from "../MockUtils";
24+
import { SwiftEnvironmentVariablesManager, SwiftTerminalProfileProvider } from "../../src/terminal";
25+
import configuration from "../../src/configuration";
26+
27+
suite("Terminal", () => {
28+
const mockedPlatform = mockGlobalValue(process, "platform");
29+
const enableTerminalEnvironmentConfig = mockGlobalValue(
30+
configuration,
31+
"enableTerminalEnvironment"
32+
);
33+
const pathConfig = mockGlobalValue(configuration, "path");
34+
const swiftEnvironmentVariablesConfig = mockGlobalValue(
35+
configuration,
36+
"swiftEnvironmentVariables"
37+
);
38+
39+
setup(() => {
40+
// Set default platform to non-Windows for most tests
41+
mockedPlatform.setValue("darwin");
42+
43+
// Default configuration values
44+
enableTerminalEnvironmentConfig.setValue(true);
45+
pathConfig.setValue("/path/to/swift");
46+
swiftEnvironmentVariablesConfig.setValue({ SWIFT_ENV: "test" });
47+
});
48+
49+
suite("SwiftEnvironmentVariablesManager", () => {
50+
let mockedExtensionContext: MockedObject<vscode.ExtensionContext>;
51+
let mockedEnvironmentVariableCollection: MockedObject<vscode.GlobalEnvironmentVariableCollection>;
52+
let mockedDisposable: MockedObject<vscode.Disposable>;
53+
54+
const mockedWorkspace = mockGlobalObject(vscode, "workspace");
55+
56+
setup(() => {
57+
// Set default platform to non-Windows for most tests
58+
mockedPlatform.setValue("darwin");
59+
60+
mockedEnvironmentVariableCollection =
61+
mockObject<vscode.GlobalEnvironmentVariableCollection>({
62+
clear: () => {},
63+
prepend: () => {},
64+
replace: () => {},
65+
getScoped: (_scope: vscode.EnvironmentVariableScope) =>
66+
instance(mockedEnvironmentVariableCollection),
67+
});
68+
69+
mockedExtensionContext = mockObject<vscode.ExtensionContext>({
70+
environmentVariableCollection: instance(mockedEnvironmentVariableCollection),
71+
});
72+
73+
mockedDisposable = mockObject<vscode.Disposable>({
74+
dispose: () => {},
75+
});
76+
77+
mockedWorkspace.onDidChangeConfiguration.returns(instance(mockedDisposable));
78+
});
79+
80+
test("constructor initializes and calls update", () => {
81+
new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
82+
expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce;
83+
expect(mockedEnvironmentVariableCollection.prepend).to.have.been.calledWith(
84+
"PATH",
85+
"/path/to/swift:"
86+
);
87+
expect(mockedEnvironmentVariableCollection.replace).to.have.been.calledWith(
88+
"SWIFT_ENV",
89+
"test"
90+
);
91+
});
92+
93+
test("constructor registers configuration change listener", () => {
94+
new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
95+
96+
expect(mockedWorkspace.onDidChangeConfiguration).to.have.been.calledOnce;
97+
});
98+
99+
test("update does nothing when enableTerminalEnvironment is false", () => {
100+
enableTerminalEnvironmentConfig.setValue(false);
101+
102+
new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
103+
104+
expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce;
105+
expect(mockedEnvironmentVariableCollection.prepend).to.not.have.been.called;
106+
expect(mockedEnvironmentVariableCollection.replace).to.not.have.been.called;
107+
});
108+
109+
test("update handles empty path", () => {
110+
pathConfig.setValue("");
111+
112+
new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
113+
114+
expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce;
115+
expect(mockedEnvironmentVariableCollection.prepend).to.not.have.been.called;
116+
expect(mockedEnvironmentVariableCollection.replace).to.have.been.calledWith(
117+
"SWIFT_ENV",
118+
"test"
119+
);
120+
});
121+
122+
test("update handles empty environment variables", () => {
123+
swiftEnvironmentVariablesConfig.setValue({});
124+
125+
new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
126+
127+
expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce;
128+
expect(mockedEnvironmentVariableCollection.prepend).to.have.been.calledWith(
129+
"PATH",
130+
"/path/to/swift:"
131+
);
132+
expect(mockedEnvironmentVariableCollection.replace).to.not.have.been.called;
133+
});
134+
135+
test("update uses Windows path separator on Windows", () => {
136+
mockedPlatform.setValue("win32");
137+
138+
new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
139+
140+
expect(mockedEnvironmentVariableCollection.prepend).to.have.been.calledWith(
141+
"PATH",
142+
"/path/to/swift;"
143+
);
144+
});
145+
146+
test("dispose clears environment variables and disposes subscriptions", () => {
147+
const manager = new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
148+
149+
mockedEnvironmentVariableCollection.clear.resetHistory();
150+
151+
manager.dispose();
152+
153+
expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce;
154+
expect(mockedDisposable.dispose).to.have.been.calledOnce;
155+
});
156+
157+
test("onDidChangeConfiguration calls update", () => {
158+
new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
159+
160+
const callback = mockedWorkspace.onDidChangeConfiguration.getCall(0).args[0];
161+
162+
mockedEnvironmentVariableCollection.clear.resetHistory();
163+
164+
callback({ affectsConfiguration: (section: string) => section === "swift.path" });
165+
166+
expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce;
167+
168+
mockedEnvironmentVariableCollection.clear.resetHistory();
169+
170+
callback({ affectsConfiguration: (section: string) => section === "other.setting" });
171+
172+
expect(mockedEnvironmentVariableCollection.clear).to.not.have.been.called;
173+
});
174+
175+
test("onDidChangeConfiguration calls update", () => {
176+
// Create the manager
177+
new SwiftEnvironmentVariablesManager(instance(mockedExtensionContext));
178+
179+
// Get the callback
180+
const callback = mockedWorkspace.onDidChangeConfiguration.getCall(0).args[0];
181+
182+
// Reset call history
183+
mockedEnvironmentVariableCollection.clear.resetHistory();
184+
185+
// Call the callback with an event that affects swift.path
186+
callback({ affectsConfiguration: (section: string) => section === "swift.path" });
187+
188+
// Verify that clear was called again
189+
expect(mockedEnvironmentVariableCollection.clear).to.have.been.calledOnce;
190+
191+
// Reset call history
192+
mockedEnvironmentVariableCollection.clear.resetHistory();
193+
194+
// Call the callback with an event that does not affect swift.path
195+
callback({ affectsConfiguration: (section: string) => section === "other.setting" });
196+
197+
// Verify that clear was not called
198+
expect(mockedEnvironmentVariableCollection.clear).to.not.have.been.called;
199+
});
200+
});
201+
202+
suite("SwiftTerminalProfileProvider", () => {
203+
// Mock configuration values
204+
const mockedWindow = mockGlobalObject(vscode, "window");
205+
let mockedTerminal: MockedObject<vscode.Terminal>;
206+
let mockedDisposable: MockedObject<vscode.Disposable>;
207+
208+
setup(() => {
209+
// Create mocks
210+
mockedTerminal = mockObject<vscode.Terminal>({
211+
sendText: () => {},
212+
});
213+
214+
mockedDisposable = mockObject<vscode.Disposable>({
215+
dispose: () => {},
216+
});
217+
218+
mockedWindow.onDidOpenTerminal.returns(instance(mockedDisposable));
219+
mockedWindow.registerTerminalProfileProvider.returns(instance(mockedDisposable));
220+
});
221+
222+
test("provideTerminalProfile returns correct profile with environment variables", () => {
223+
const provider = new SwiftTerminalProfileProvider();
224+
const profile = provider.provideTerminalProfile();
225+
226+
expect(profile).to.be.instanceOf(vscode.TerminalProfile);
227+
expect((profile as vscode.TerminalProfile).options.name).to.equal("Swift Terminal");
228+
expect((profile as vscode.TerminalProfile).options.iconPath).to.be.instanceOf(
229+
vscode.ThemeIcon
230+
);
231+
232+
// Access env property safely with type assertion
233+
const options = (profile as vscode.TerminalProfile).options;
234+
const env = options as unknown as { env: Record<string, string> };
235+
expect(env.env).to.deep.equal({ SWIFT_ENV: "test" });
236+
});
237+
238+
test("provideTerminalProfile sets up terminal when enableTerminalEnvironment is false", () => {
239+
enableTerminalEnvironmentConfig.setValue(false);
240+
241+
const provider = new SwiftTerminalProfileProvider();
242+
const profile = provider.provideTerminalProfile();
243+
expect(profile).to.exist;
244+
expect(mockedWindow.onDidOpenTerminal).to.have.been.calledOnce;
245+
246+
const callback = mockedWindow.onDidOpenTerminal.getCall(0).args[0];
247+
callback(instance(mockedTerminal));
248+
249+
expect(mockedDisposable.dispose).to.have.been.calledOnce;
250+
expect(mockedTerminal.sendText).to.have.been.calledWith(
251+
"export PATH=/path/to/swift:$PATH"
252+
);
253+
});
254+
255+
test("provideTerminalProfile uses Windows path separator on Windows", () => {
256+
mockedPlatform.setValue("win32");
257+
enableTerminalEnvironmentConfig.setValue(false);
258+
259+
const provider = new SwiftTerminalProfileProvider();
260+
const profile = provider.provideTerminalProfile();
261+
expect(profile).to.exist;
262+
263+
const callback = mockedWindow.onDidOpenTerminal.getCall(0).args[0];
264+
callback(instance(mockedTerminal));
265+
266+
expect(mockedTerminal.sendText).to.have.been.calledWith(
267+
"export PATH=/path/to/swift;$PATH"
268+
);
269+
});
270+
271+
test("register calls registerTerminalProfileProvider", () => {
272+
const disposable = SwiftTerminalProfileProvider.register();
273+
274+
expect(mockedWindow.registerTerminalProfileProvider).to.have.been.calledOnce;
275+
expect(mockedWindow.registerTerminalProfileProvider.getCall(0).args[0]).to.equal(
276+
"swift.terminalProfile"
277+
);
278+
expect(
279+
mockedWindow.registerTerminalProfileProvider.getCall(0).args[1]
280+
).to.be.instanceOf(SwiftTerminalProfileProvider);
281+
282+
expect(disposable).to.equal(instance(mockedDisposable));
283+
});
284+
});
285+
});

0 commit comments

Comments
 (0)