Skip to content

Commit f236e65

Browse files
Add integration tests of QueryDiscovery
1 parent 301149f commit f236e65

2 files changed

Lines changed: 222 additions & 1 deletion

File tree

extensions/ql-vscode/src/queries-panel/query-discovery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { QueryDiscoverer } from "./query-tree-data-provider";
1212
/**
1313
* The results of discovering queries.
1414
*/
15-
interface QueryDiscoveryResults {
15+
export interface QueryDiscoveryResults {
1616
/**
1717
* A tree of directories and query files.
1818
* May have multiple roots because of multiple workspaces.
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import {
2+
EventEmitter,
3+
FileSystemWatcher,
4+
Uri,
5+
WorkspaceFoldersChangeEvent,
6+
workspace,
7+
} from "vscode";
8+
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
9+
import {
10+
QueryDiscovery,
11+
QueryDiscoveryResults,
12+
} from "../../../../src/queries-panel/query-discovery";
13+
import { createMockApp } from "../../../__mocks__/appMock";
14+
import { mockedObject } from "../../utils/mocking.helpers";
15+
import { basename, join } from "path";
16+
import { sleep } from "../../../../src/pure/time";
17+
18+
describe("QueryDiscovery", () => {
19+
beforeEach(() => {
20+
expect(workspace.workspaceFolders?.length).toEqual(1);
21+
});
22+
23+
describe("queries", () => {
24+
it("should return empty list when no QL files are present", async () => {
25+
const resolveQueries = jest.fn().mockResolvedValue([]);
26+
const cli = mockedObject<CodeQLCliServer>({
27+
resolveQueries,
28+
});
29+
30+
const discovery = new QueryDiscovery(createMockApp({}), cli);
31+
const results: QueryDiscoveryResults = await (
32+
discovery as any
33+
).discover();
34+
35+
expect(results.queries).toEqual([]);
36+
expect(resolveQueries).toHaveBeenCalledTimes(1);
37+
});
38+
39+
it("should organise query files into directories", async () => {
40+
const workspaceRoot = workspace.workspaceFolders![0].uri.fsPath;
41+
const cli = mockedObject<CodeQLCliServer>({
42+
resolveQueries: jest
43+
.fn()
44+
.mockResolvedValue([
45+
join(workspaceRoot, "dir1/query1.ql"),
46+
join(workspaceRoot, "dir2/query2.ql"),
47+
join(workspaceRoot, "query3.ql"),
48+
]),
49+
});
50+
51+
const discovery = new QueryDiscovery(createMockApp({}), cli);
52+
const results: QueryDiscoveryResults = await (
53+
discovery as any
54+
).discover();
55+
56+
expect(results.queries[0].children.length).toEqual(3);
57+
expect(results.queries[0].children[0].name).toEqual("dir1");
58+
expect(results.queries[0].children[0].children.length).toEqual(1);
59+
expect(results.queries[0].children[0].children[0].name).toEqual(
60+
"query1.ql",
61+
);
62+
expect(results.queries[0].children[1].name).toEqual("dir2");
63+
expect(results.queries[0].children[1].children.length).toEqual(1);
64+
expect(results.queries[0].children[1].children[0].name).toEqual(
65+
"query2.ql",
66+
);
67+
expect(results.queries[0].children[2].name).toEqual("query3.ql");
68+
});
69+
70+
it("should collapse directories containing only a single element", async () => {
71+
const workspaceRoot = workspace.workspaceFolders![0].uri.fsPath;
72+
const cli = mockedObject<CodeQLCliServer>({
73+
resolveQueries: jest
74+
.fn()
75+
.mockResolvedValue([
76+
join(workspaceRoot, "dir1/query1.ql"),
77+
join(workspaceRoot, "dir1/dir2/dir3/dir3/query2.ql"),
78+
]),
79+
});
80+
81+
const discovery = new QueryDiscovery(createMockApp({}), cli);
82+
const results: QueryDiscoveryResults = await (
83+
discovery as any
84+
).discover();
85+
86+
expect(results.queries[0].children.length).toEqual(1);
87+
expect(results.queries[0].children[0].name).toEqual("dir1");
88+
expect(results.queries[0].children[0].children.length).toEqual(2);
89+
expect(results.queries[0].children[0].children[0].name).toEqual(
90+
"dir2 / dir3 / dir3",
91+
);
92+
expect(
93+
results.queries[0].children[0].children[0].children.length,
94+
).toEqual(1);
95+
expect(
96+
results.queries[0].children[0].children[0].children[0].name,
97+
).toEqual("query2.ql");
98+
expect(results.queries[0].children[0].children[1].name).toEqual(
99+
"query1.ql",
100+
);
101+
});
102+
103+
it("calls resolveQueries once for each workspace", async () => {
104+
const workspaceRoots = ["/workspace1", "/workspace2", "/workspace3"];
105+
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValueOnce(
106+
workspaceRoots.map((root, index) => ({
107+
uri: Uri.file(root),
108+
name: basename(root),
109+
index,
110+
})),
111+
);
112+
113+
const resolveQueries = jest.fn().mockImplementation((queryDir) => {
114+
const workspaceIndex = workspaceRoots.indexOf(queryDir);
115+
if (workspaceIndex === -1) {
116+
throw new Error("Unexpected workspace");
117+
}
118+
return Promise.resolve([
119+
join(queryDir, `query${workspaceIndex + 1}.ql`),
120+
]);
121+
});
122+
const cli = mockedObject<CodeQLCliServer>({
123+
resolveQueries,
124+
});
125+
126+
const discovery = new QueryDiscovery(createMockApp({}), cli);
127+
const results: QueryDiscoveryResults = await (
128+
discovery as any
129+
).discover();
130+
131+
expect(results.queries.length).toEqual(3);
132+
expect(results.queries[0].children[0].name).toEqual("query1.ql");
133+
expect(results.queries[1].children[0].name).toEqual("query2.ql");
134+
expect(results.queries[2].children[0].name).toEqual("query3.ql");
135+
136+
expect(resolveQueries).toHaveBeenCalledTimes(3);
137+
});
138+
});
139+
140+
describe("onDidChangeQueries", () => {
141+
it("should fire onDidChangeQueries when a watcher fires", async () => {
142+
const onWatcherDidChangeEvent = new EventEmitter<Uri>();
143+
const watcher: FileSystemWatcher = {
144+
ignoreCreateEvents: false,
145+
ignoreChangeEvents: false,
146+
ignoreDeleteEvents: false,
147+
onDidCreate: onWatcherDidChangeEvent.event,
148+
onDidChange: onWatcherDidChangeEvent.event,
149+
onDidDelete: onWatcherDidChangeEvent.event,
150+
dispose: () => undefined,
151+
};
152+
const createFileSystemWatcherSpy = jest.spyOn(
153+
workspace,
154+
"createFileSystemWatcher",
155+
);
156+
createFileSystemWatcherSpy.mockReturnValue(watcher);
157+
158+
const workspaceRoot = workspace.workspaceFolders![0].uri.fsPath;
159+
const cli = mockedObject<CodeQLCliServer>({
160+
resolveQueries: jest
161+
.fn()
162+
.mockResolvedValue([join(workspaceRoot, "query1.ql")]),
163+
});
164+
165+
const discovery = new QueryDiscovery(
166+
createMockApp({
167+
createEventEmitter: () => new EventEmitter(),
168+
}),
169+
cli,
170+
);
171+
172+
const onDidChangeQueriesSpy = jest.fn();
173+
discovery.onDidChangeQueries(onDidChangeQueriesSpy);
174+
175+
const results = await (discovery as any).discover();
176+
(discovery as any).update(results);
177+
178+
expect(createFileSystemWatcherSpy).toHaveBeenCalledTimes(2);
179+
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(1);
180+
181+
onWatcherDidChangeEvent.fire(workspace.workspaceFolders![0].uri);
182+
183+
// Wait for refresh to finish
184+
await sleep(100);
185+
186+
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(2);
187+
});
188+
});
189+
190+
describe("onDidChangeWorkspaceFolders", () => {
191+
it("should refresh when workspace folders change", async () => {
192+
const onDidChangeWorkspaceFoldersEvent =
193+
new EventEmitter<WorkspaceFoldersChangeEvent>();
194+
195+
const discovery = new QueryDiscovery(
196+
createMockApp({
197+
createEventEmitter: () => new EventEmitter(),
198+
onDidChangeWorkspaceFolders: onDidChangeWorkspaceFoldersEvent.event,
199+
}),
200+
mockedObject<CodeQLCliServer>({
201+
resolveQueries: jest.fn().mockResolvedValue([]),
202+
}),
203+
);
204+
205+
const onDidChangeQueriesSpy = jest.fn();
206+
discovery.onDidChangeQueries(onDidChangeQueriesSpy);
207+
208+
const results = await (discovery as any).discover();
209+
(discovery as any).update(results);
210+
211+
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(1);
212+
213+
onDidChangeWorkspaceFoldersEvent.fire({ added: [], removed: [] });
214+
215+
// Wait for refresh to finish
216+
await sleep(100);
217+
218+
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(2);
219+
});
220+
});
221+
});

0 commit comments

Comments
 (0)