Skip to content

Commit c66856e

Browse files
Add unit tests for TestCodeLensProvider (#1795)
* Add unit tests for TestCodeLensProvider * Update test/unit-tests/testexplorer/TestCodeLensProvider.test.ts Co-authored-by: award999 <[email protected]> --------- Co-authored-by: award999 <[email protected]>
1 parent 9655298 commit c66856e

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
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 * as sinon from "sinon";
17+
import * as TestUtils from "../../../src/TestExplorer/TestUtils";
18+
import { expect } from "chai";
19+
import { TestCodeLensProvider } from "../../../src/TestExplorer/TestCodeLensProvider";
20+
import { TestExplorer } from "../../../src/TestExplorer/TestExplorer";
21+
import { instance, mockObject } from "../../MockUtils";
22+
import configuration from "../../../src/configuration";
23+
24+
suite("TestCodeLensProvider", () => {
25+
let sandbox: sinon.SinonSandbox;
26+
let testExplorer: TestExplorer;
27+
let testItems: vscode.TestItem[];
28+
let document: vscode.TextDocument;
29+
let configStub: sinon.SinonStub;
30+
let flattenStub: sinon.SinonStub;
31+
let registerCodeLensProviderStub: sinon.SinonStub;
32+
let codeLensProvider: TestCodeLensProvider;
33+
34+
const token = () => new vscode.CancellationTokenSource().token;
35+
36+
setup(() => {
37+
sandbox = sinon.createSandbox();
38+
39+
testItems = [
40+
createTestItem("test1", "Test 1", "/path/to/file1.swift", new vscode.Range(0, 0, 1, 0)),
41+
createTestItem("test2", "Test 2", "/path/to/file2.swift", new vscode.Range(2, 0, 3, 0)),
42+
createTestItem("test3", "Test 3", "/path/to/file1.swift", new vscode.Range(4, 0, 5, 0)),
43+
createTestItem("test4", "Test 4", "/path/to/file1.swift", undefined),
44+
];
45+
46+
const testItemCollection = mockObject<vscode.TestItemCollection>({
47+
forEach: sandbox.stub().callsFake((callback: (item: vscode.TestItem) => void) => {
48+
testItems.forEach(item => callback(item));
49+
}),
50+
get: sandbox.stub(),
51+
delete: sandbox.stub(),
52+
replace: sandbox.stub(),
53+
size: testItems.length,
54+
add: sandbox.stub(),
55+
});
56+
57+
const testController = mockObject<vscode.TestController>({
58+
items: testItemCollection,
59+
createTestItem: sandbox.stub(),
60+
});
61+
62+
const onTestItemsDidChangeStub = sandbox.stub();
63+
onTestItemsDidChangeStub.returns({ dispose: sandbox.stub() });
64+
65+
testExplorer = mockObject<TestExplorer>({
66+
controller: instance(testController),
67+
onTestItemsDidChange: onTestItemsDidChangeStub,
68+
}) as unknown as TestExplorer; // allows for a partial mock of TestExplorer
69+
70+
document = instance(
71+
mockObject<vscode.TextDocument>({
72+
uri: vscode.Uri.file("/path/to/file1.swift"),
73+
})
74+
);
75+
76+
registerCodeLensProviderStub = sandbox
77+
.stub(vscode.languages, "registerCodeLensProvider")
78+
.returns({ dispose: sandbox.stub() });
79+
80+
configStub = sandbox.stub(configuration, "showTestCodeLenses");
81+
flattenStub = sandbox.stub(TestUtils, "flattenTestItemCollection").returns(testItems);
82+
codeLensProvider = new TestCodeLensProvider(testExplorer);
83+
});
84+
85+
teardown(() => {
86+
sandbox.restore();
87+
codeLensProvider.dispose();
88+
});
89+
90+
function createTestItem(
91+
id: string,
92+
label: string,
93+
filePath: string,
94+
range: vscode.Range | undefined
95+
): vscode.TestItem {
96+
return instance(
97+
mockObject<vscode.TestItem>({
98+
id,
99+
label,
100+
uri: filePath ? vscode.Uri.file(filePath) : undefined,
101+
range,
102+
})
103+
);
104+
}
105+
106+
test("constructor should register event handlers and code lens provider", () => {
107+
expect((testExplorer.onTestItemsDidChange as sinon.SinonStub).calledOnce).to.be.true;
108+
expect(registerCodeLensProviderStub.calledOnce).to.be.true;
109+
expect(registerCodeLensProviderStub.firstCall.args[0]).to.deep.equal({
110+
language: "swift",
111+
scheme: "file",
112+
});
113+
expect(registerCodeLensProviderStub.firstCall.args[1]).to.equal(codeLensProvider);
114+
});
115+
116+
test("provideCodeLenses should return empty array when showTestCodeLenses is false", async () => {
117+
configStub.value(false);
118+
119+
const result = await codeLensProvider.provideCodeLenses(document, token());
120+
121+
expect(result).to.be.an("array").that.is.empty;
122+
expect(flattenStub.called).to.be.false;
123+
});
124+
125+
test("provideCodeLenses should return empty array when showTestCodeLenses is an empty array", async () => {
126+
configStub.value([]);
127+
128+
const result = await codeLensProvider.provideCodeLenses(document, token());
129+
130+
expect(result).to.be.an("array").that.is.empty;
131+
expect(flattenStub.called).to.be.false;
132+
});
133+
134+
test("provideCodeLenses should filter test items by document URI", async () => {
135+
configStub.value(true);
136+
137+
const result = await codeLensProvider.provideCodeLenses(document, token());
138+
139+
// Should only include test items with matching URI (test1 and test3)
140+
expect(result).to.be.an("array").with.lengthOf(6); // 2 test items * 3 lens types
141+
expect(result).to.not.be.null;
142+
expect(result).to.not.be.undefined;
143+
144+
// Verify that the code lenses are for the correct test items
145+
const testItemIds = result!.map(
146+
lens => (lens.command?.arguments?.[0] as vscode.TestItem).id
147+
);
148+
expect(testItemIds).to.include.members([
149+
"test1",
150+
"test1",
151+
"test1",
152+
"test3",
153+
"test3",
154+
"test3",
155+
]);
156+
expect(testItemIds).to.not.include.members(["test2", "test4"]);
157+
});
158+
159+
test("provideCodeLenses should create code lenses for all types when showTestCodeLenses is true", async () => {
160+
configStub.value(true);
161+
162+
const result = await codeLensProvider.provideCodeLenses(document, token());
163+
164+
// Should create 3 lens types (run, debug, coverage) for each matching test item (test1 and test3)
165+
expect(result).to.be.an("array").with.lengthOf(6);
166+
expect(result).to.not.be.null;
167+
expect(result).to.not.be.undefined;
168+
169+
const commands = result!.map((lens: vscode.CodeLens) => lens.command?.command);
170+
expect(commands).to.include.members([
171+
"swift.runTest",
172+
"swift.runTest",
173+
"swift.debugTest",
174+
"swift.debugTest",
175+
"swift.runTestWithCoverage",
176+
"swift.runTestWithCoverage",
177+
]);
178+
});
179+
180+
test("provideCodeLenses should create code lenses only for specified types", async () => {
181+
configStub.value(["run", "debug"]);
182+
183+
const result = await codeLensProvider.provideCodeLenses(document, token());
184+
185+
// Should create 2 lens types (run, debug) for each matching test item (test1 and test3)
186+
expect(result).to.be.an("array").with.lengthOf(4);
187+
expect(result).to.not.be.null;
188+
expect(result).to.not.be.undefined;
189+
190+
const commands = result!.map((lens: vscode.CodeLens) => lens.command?.command);
191+
expect(commands).to.include.members([
192+
"swift.runTest",
193+
"swift.runTest",
194+
"swift.debugTest",
195+
"swift.debugTest",
196+
]);
197+
expect(commands).to.not.include("swift.runTestWithCoverage");
198+
});
199+
200+
test("provideCodeLenses should return empty array for test items without a range", async () => {
201+
configStub.value(true);
202+
203+
// Create a document that matches the URI of the test item without a range
204+
const noRangeDocument = instance(
205+
mockObject<vscode.TextDocument>({
206+
uri: vscode.Uri.file("/path/to/file1.swift"),
207+
})
208+
);
209+
210+
// Make flattenStub return only the test item without a range
211+
flattenStub.returns([testItems[3]]); // test4 has no range
212+
213+
const result = await codeLensProvider.provideCodeLenses(noRangeDocument, token());
214+
215+
expect(result).to.be.an("array").that.is.empty;
216+
});
217+
218+
test("provideCodeLenses should create code lenses for all types when config is true", async () => {
219+
configStub.value(true);
220+
221+
// Create a document that matches the URI of the test item with a range
222+
const singleItemDocument = instance(
223+
mockObject<vscode.TextDocument>({
224+
uri: vscode.Uri.file("/path/to/file1.swift"),
225+
})
226+
);
227+
228+
// Make flattenStub return only one test item with a range
229+
flattenStub.returns([testItems[0]]); // test1 has a range
230+
231+
const result = await codeLensProvider.provideCodeLenses(singleItemDocument, token());
232+
233+
// Should create 3 lens types (run, debug, coverage)
234+
expect(result).to.be.an("array").with.lengthOf(3);
235+
expect(result).to.not.be.null;
236+
expect(result).to.not.be.undefined;
237+
238+
const commands = result!.map((lens: vscode.CodeLens) => lens.command?.command);
239+
expect(commands).to.include.members([
240+
"swift.runTest",
241+
"swift.debugTest",
242+
"swift.runTestWithCoverage",
243+
]);
244+
});
245+
246+
test("provideCodeLenses should create code lenses only for specified types", async () => {
247+
configStub.value(["run"]);
248+
249+
// Create a document that matches the URI of the test item with a range
250+
const singleItemDocument = instance(
251+
mockObject<vscode.TextDocument>({
252+
uri: vscode.Uri.file("/path/to/file1.swift"),
253+
})
254+
);
255+
256+
// Make flattenStub return only one test item with a range
257+
flattenStub.returns([testItems[0]]); // test1 has a range
258+
259+
const result = await codeLensProvider.provideCodeLenses(singleItemDocument, token());
260+
261+
// Should create 1 lens type (run)
262+
expect(result).to.be.an("array").with.lengthOf(1);
263+
264+
// Ensure result is not null or undefined before accessing its properties
265+
expect(result).to.not.be.null;
266+
expect(result).to.not.be.undefined;
267+
expect(result![0].command?.command).to.equal("swift.runTest");
268+
});
269+
});

0 commit comments

Comments
 (0)