Add FilePathDiscovery
This commit is contained in:
190
extensions/ql-vscode/src/common/vscode/file-path-discovery.ts
Normal file
190
extensions/ql-vscode/src/common/vscode/file-path-discovery.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { Discovery } from "../discovery";
|
||||
import {
|
||||
EventEmitter,
|
||||
RelativePattern,
|
||||
Uri,
|
||||
WorkspaceFoldersChangeEvent,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import { MultiFileSystemWatcher } from "./multi-file-system-watcher";
|
||||
import { AppEventEmitter } from "../events";
|
||||
import { extLogger } from "..";
|
||||
import { FilePathSet } from "../file-path-set";
|
||||
import { exists, lstat } from "fs-extra";
|
||||
import { containsPath } from "../../pure/files";
|
||||
import { getOnDiskWorkspaceFoldersObjects } from "./workspace-folders";
|
||||
|
||||
interface PathData {
|
||||
path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers all files matching a given filter contained in the workspace.
|
||||
*
|
||||
* Scans the whole workspace on startup, and then watches for changes to files
|
||||
* to do the minimum work to keep up with changes.
|
||||
*
|
||||
* Can configure which changes it watches for, which files are considered
|
||||
* relevant, and what extra data to compute for each file.
|
||||
*/
|
||||
export abstract class FilePathDiscovery<T extends PathData> extends Discovery {
|
||||
/** The set of known paths we are tracking */
|
||||
protected paths: T[] = [];
|
||||
protected readonly onDidChangePathsEmitter: AppEventEmitter<void>;
|
||||
|
||||
private readonly changedFilePaths = new FilePathSet();
|
||||
private readonly watcher: MultiFileSystemWatcher = this.push(
|
||||
new MultiFileSystemWatcher(),
|
||||
);
|
||||
|
||||
/**
|
||||
* @param name Name of the discovery operation, for logging purposes.
|
||||
* @param fileWatchPattern Passed to `vscode.RelativePattern` to determine the files to watch for changes to.
|
||||
*/
|
||||
constructor(name: string, private readonly fileWatchPattern: string) {
|
||||
super(name, extLogger);
|
||||
|
||||
this.onDidChangePathsEmitter = this.push(new EventEmitter<void>());
|
||||
this.push(
|
||||
workspace.onDidChangeWorkspaceFolders(
|
||||
this.workspaceFoldersChanged.bind(this),
|
||||
),
|
||||
);
|
||||
this.push(this.watcher.onDidChange(this.fileChanged.bind(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute any extra data to be stored regarding the given path.
|
||||
*/
|
||||
protected abstract getDataForPath(path: string): Promise<T>;
|
||||
|
||||
/**
|
||||
* Is the given path relevant to this discovery operation?
|
||||
*/
|
||||
protected abstract pathIsRelevant(path: string): boolean;
|
||||
|
||||
/**
|
||||
* Should the given new data overwrite the existing data we have stored?
|
||||
*/
|
||||
protected abstract shouldOverwriteExistingData(
|
||||
newData: T,
|
||||
existingData: T,
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
* Do the initial scan of the entire workspace and set up watchers for future changes.
|
||||
*/
|
||||
public async initialRefresh() {
|
||||
getOnDiskWorkspaceFoldersObjects().forEach((workspaceFolder) => {
|
||||
this.changedFilePaths.addPath(workspaceFolder.uri.fsPath);
|
||||
});
|
||||
|
||||
this.updateWatchers();
|
||||
return this.refresh();
|
||||
}
|
||||
|
||||
private workspaceFoldersChanged(event: WorkspaceFoldersChangeEvent) {
|
||||
event.added.forEach((workspaceFolder) => {
|
||||
this.changedFilePaths.addPath(workspaceFolder.uri.fsPath);
|
||||
});
|
||||
event.removed.forEach((workspaceFolder) => {
|
||||
this.changedFilePaths.addPath(workspaceFolder.uri.fsPath);
|
||||
});
|
||||
|
||||
this.updateWatchers();
|
||||
void this.refresh();
|
||||
}
|
||||
|
||||
private updateWatchers() {
|
||||
this.watcher.clear();
|
||||
for (const workspaceFolder of getOnDiskWorkspaceFoldersObjects()) {
|
||||
// Watch for changes to individual files
|
||||
this.watcher.addWatch(
|
||||
new RelativePattern(workspaceFolder, this.fileWatchPattern),
|
||||
);
|
||||
// need to explicitly watch for changes to directories themselves.
|
||||
this.watcher.addWatch(new RelativePattern(workspaceFolder, "**/"));
|
||||
}
|
||||
}
|
||||
|
||||
private fileChanged(uri: Uri) {
|
||||
this.changedFilePaths.addPath(uri.fsPath);
|
||||
void this.refresh();
|
||||
}
|
||||
|
||||
protected async discover() {
|
||||
let pathsUpdated = false;
|
||||
let path: string | undefined;
|
||||
while ((path = this.changedFilePaths.popPath()) !== undefined) {
|
||||
if (await this.handledChangedPath(path)) {
|
||||
pathsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathsUpdated) {
|
||||
this.onDidChangePathsEmitter.fire();
|
||||
}
|
||||
}
|
||||
|
||||
private async handledChangedPath(path: string): Promise<boolean> {
|
||||
if (!(await exists(path)) || !this.pathIsInWorkspace(path)) {
|
||||
return this.handledRemovedPath(path);
|
||||
}
|
||||
if ((await lstat(path)).isDirectory()) {
|
||||
return await this.handleChangedDirectory(path);
|
||||
}
|
||||
return this.handleChangedFile(path);
|
||||
}
|
||||
|
||||
private pathIsInWorkspace(path: string): boolean {
|
||||
return getOnDiskWorkspaceFoldersObjects().some((workspaceFolder) =>
|
||||
containsPath(workspaceFolder.uri.fsPath, path),
|
||||
);
|
||||
}
|
||||
|
||||
private handledRemovedPath(path: string): boolean {
|
||||
const oldLength = this.paths.length;
|
||||
this.paths = this.paths.filter((q) => !containsPath(path, q.path));
|
||||
return this.paths.length !== oldLength;
|
||||
}
|
||||
|
||||
private async handleChangedDirectory(path: string): Promise<boolean> {
|
||||
const newPaths = await workspace.findFiles(
|
||||
new RelativePattern(path, this.fileWatchPattern),
|
||||
);
|
||||
|
||||
let pathsUpdated = false;
|
||||
for (const path of newPaths) {
|
||||
if (await this.addOrUpdatePath(path.fsPath)) {
|
||||
pathsUpdated = true;
|
||||
}
|
||||
}
|
||||
return pathsUpdated;
|
||||
}
|
||||
|
||||
private async handleChangedFile(path: string): Promise<boolean> {
|
||||
if (this.pathIsRelevant(path)) {
|
||||
return await this.addOrUpdatePath(path);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async addOrUpdatePath(path: string): Promise<boolean> {
|
||||
const data = await this.getDataForPath(path);
|
||||
const existingDataIndex = this.paths.findIndex((x) => x.path === path);
|
||||
if (existingDataIndex !== -1) {
|
||||
if (
|
||||
this.shouldOverwriteExistingData(data, this.paths[existingDataIndex])
|
||||
) {
|
||||
this.paths.splice(existingDataIndex, 1, data);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
this.paths.push(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
import {
|
||||
EventEmitter,
|
||||
FileSystemWatcher,
|
||||
Uri,
|
||||
workspace,
|
||||
WorkspaceFolder,
|
||||
WorkspaceFoldersChangeEvent,
|
||||
} from "vscode";
|
||||
import { FilePathDiscovery } from "../../../../../src/common/vscode/file-path-discovery";
|
||||
import { basename, dirname, join } from "path";
|
||||
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
||||
import * as tmp from "tmp";
|
||||
import { expectArraysEqual } from "../../../utils/expect-arrays-equal";
|
||||
|
||||
interface TestData {
|
||||
path: string;
|
||||
contents: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A test FilePathDiscovery that operates on files with the ".test" extension.
|
||||
*/
|
||||
class TestFilePathDiscovery extends FilePathDiscovery<TestData> {
|
||||
constructor() {
|
||||
super("TestFilePathDiscovery", "**/*.test");
|
||||
}
|
||||
|
||||
public get onDidChangePaths() {
|
||||
return this.onDidChangePathsEmitter.event;
|
||||
}
|
||||
|
||||
public getPaths(): TestData[] {
|
||||
return this.paths;
|
||||
}
|
||||
|
||||
protected async getDataForPath(path: string): Promise<TestData> {
|
||||
return {
|
||||
path,
|
||||
contents: readFileSync(path, "utf8"),
|
||||
};
|
||||
}
|
||||
|
||||
protected pathIsRelevant(path: string): boolean {
|
||||
return path.endsWith(".test");
|
||||
}
|
||||
|
||||
protected shouldOverwriteExistingData(
|
||||
newData: TestData,
|
||||
existingData: TestData,
|
||||
): boolean {
|
||||
return newData.contents !== existingData.contents;
|
||||
}
|
||||
}
|
||||
|
||||
describe("FilePathDiscovery", () => {
|
||||
let tmpDir: string;
|
||||
let tmpDirRemoveCallback: (() => void) | undefined;
|
||||
|
||||
let workspaceFolder: WorkspaceFolder;
|
||||
let workspacePath: string;
|
||||
let workspaceFoldersSpy: jest.SpyInstance;
|
||||
|
||||
const onDidCreateFile = new EventEmitter<Uri>();
|
||||
const onDidChangeFile = new EventEmitter<Uri>();
|
||||
const onDidDeleteFile = new EventEmitter<Uri>();
|
||||
let createFileSystemWatcherSpy: jest.SpyInstance;
|
||||
|
||||
const onDidChangeWorkspaceFolders =
|
||||
new EventEmitter<WorkspaceFoldersChangeEvent>();
|
||||
|
||||
let discovery: TestFilePathDiscovery;
|
||||
|
||||
beforeEach(() => {
|
||||
const t = tmp.dirSync();
|
||||
tmpDir = t.name;
|
||||
tmpDirRemoveCallback = t.removeCallback;
|
||||
|
||||
workspaceFolder = {
|
||||
uri: Uri.file(join(tmpDir, "workspace")),
|
||||
name: "test",
|
||||
index: 0,
|
||||
};
|
||||
workspacePath = workspaceFolder.uri.fsPath;
|
||||
workspaceFoldersSpy = jest
|
||||
.spyOn(workspace, "workspaceFolders", "get")
|
||||
.mockReturnValue([workspaceFolder]);
|
||||
|
||||
const watcher: FileSystemWatcher = {
|
||||
ignoreCreateEvents: false,
|
||||
ignoreChangeEvents: false,
|
||||
ignoreDeleteEvents: false,
|
||||
onDidCreate: onDidCreateFile.event,
|
||||
onDidChange: onDidChangeFile.event,
|
||||
onDidDelete: onDidDeleteFile.event,
|
||||
dispose: () => undefined,
|
||||
};
|
||||
createFileSystemWatcherSpy = jest
|
||||
.spyOn(workspace, "createFileSystemWatcher")
|
||||
.mockReturnValue(watcher);
|
||||
|
||||
jest
|
||||
.spyOn(workspace, "onDidChangeWorkspaceFolders")
|
||||
.mockImplementation(onDidChangeWorkspaceFolders.event);
|
||||
|
||||
discovery = new TestFilePathDiscovery();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tmpDirRemoveCallback?.();
|
||||
discovery.dispose();
|
||||
});
|
||||
|
||||
describe("initialRefresh", () => {
|
||||
it("should handle no files being present", async () => {
|
||||
await discovery.initialRefresh();
|
||||
expect(discovery.getPaths()).toEqual([]);
|
||||
});
|
||||
|
||||
it("should recursively discover all test files", async () => {
|
||||
makeTestFile(join(workspacePath, "123.test"));
|
||||
makeTestFile(join(workspacePath, "456.test"));
|
||||
makeTestFile(join(workspacePath, "bar", "789.test"));
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "123.test"), contents: "123" },
|
||||
{ path: join(workspacePath, "456.test"), contents: "456" },
|
||||
{ path: join(workspacePath, "bar", "789.test"), contents: "789" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should ignore non-test files", async () => {
|
||||
makeTestFile(join(workspacePath, "1.test"));
|
||||
makeTestFile(join(workspacePath, "2.foo"));
|
||||
makeTestFile(join(workspacePath, "bar.ql"));
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "1" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("file added", () => {
|
||||
it("should discover a single new file", async () => {
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expect(discovery.getPaths()).toEqual([]);
|
||||
|
||||
const newFile = join(workspacePath, "1.test");
|
||||
makeTestFile(newFile);
|
||||
onDidCreateFile.fire(Uri.file(newFile));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "1" },
|
||||
]);
|
||||
expect(didChangePathsListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should do nothing if file doesnt actually exist", async () => {
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expect(discovery.getPaths()).toEqual([]);
|
||||
|
||||
onDidCreateFile.fire(Uri.file(join(workspacePath, "1.test")));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), []);
|
||||
expect(didChangePathsListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should recursively discover a directory of new files", async () => {
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expect(discovery.getPaths()).toEqual([]);
|
||||
|
||||
const newDir = join(workspacePath, "foo");
|
||||
makeTestFile(join(newDir, "1.test"));
|
||||
makeTestFile(join(newDir, "bar", "2.test"));
|
||||
makeTestFile(join(newDir, "bar", "3.test"));
|
||||
onDidCreateFile.fire(Uri.file(newDir));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(newDir, "1.test"), contents: "1" },
|
||||
{ path: join(newDir, "bar", "2.test"), contents: "2" },
|
||||
{ path: join(newDir, "bar", "3.test"), contents: "3" },
|
||||
]);
|
||||
expect(didChangePathsListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should do nothing if file is already known", async () => {
|
||||
const testFile = join(workspacePath, "1.test");
|
||||
makeTestFile(testFile);
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "1" },
|
||||
]);
|
||||
|
||||
onDidCreateFile.fire(Uri.file(testFile));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "1" },
|
||||
]);
|
||||
expect(didChangePathsListener).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("file changed", () => {
|
||||
it("should do nothing if nothing has actually changed", async () => {
|
||||
const testFile = join(workspacePath, "123.test");
|
||||
makeTestFile(testFile);
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "123.test"), contents: "123" },
|
||||
]);
|
||||
|
||||
onDidChangeFile.fire(Uri.file(testFile));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "123.test"), contents: "123" },
|
||||
]);
|
||||
expect(didChangePathsListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update data if it has changed", async () => {
|
||||
const testFile = join(workspacePath, "1.test");
|
||||
makeTestFile(testFile, "foo");
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "foo" },
|
||||
]);
|
||||
|
||||
writeFileSync(testFile, "bar");
|
||||
onDidChangeFile.fire(Uri.file(testFile));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "bar" },
|
||||
]);
|
||||
expect(didChangePathsListener).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("file deleted", () => {
|
||||
it("should remove a file that has been deleted", async () => {
|
||||
const testFile = join(workspacePath, "1.test");
|
||||
makeTestFile(testFile);
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "1" },
|
||||
]);
|
||||
|
||||
rmSync(testFile);
|
||||
onDidDeleteFile.fire(Uri.file(testFile));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), []);
|
||||
expect(didChangePathsListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should do nothing if file still exists", async () => {
|
||||
const testFile = join(workspacePath, "1.test");
|
||||
makeTestFile(testFile);
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "1" },
|
||||
]);
|
||||
|
||||
onDidDeleteFile.fire(Uri.file(testFile));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "1.test"), contents: "1" },
|
||||
]);
|
||||
expect(didChangePathsListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should remove a directory of files that has been deleted", async () => {
|
||||
makeTestFile(join(workspacePath, "123.test"));
|
||||
makeTestFile(join(workspacePath, "bar", "456.test"));
|
||||
makeTestFile(join(workspacePath, "bar", "789.test"));
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
const didChangePathsListener = jest.fn();
|
||||
discovery.onDidChangePaths(didChangePathsListener);
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "123.test"), contents: "123" },
|
||||
{ path: join(workspacePath, "bar", "456.test"), contents: "456" },
|
||||
{ path: join(workspacePath, "bar", "789.test"), contents: "789" },
|
||||
]);
|
||||
|
||||
rmSync(join(workspacePath, "bar"), { recursive: true });
|
||||
|
||||
onDidDeleteFile.fire(Uri.file(join(workspacePath, "bar")));
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "123.test"), contents: "123" },
|
||||
]);
|
||||
expect(didChangePathsListener).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("workspaceFoldersChanged", () => {
|
||||
it("initialRefresh establishes watchers", async () => {
|
||||
await discovery.initialRefresh();
|
||||
|
||||
// Called twice for each workspace folder
|
||||
expect(createFileSystemWatcherSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should install watchers when workspace folders change", async () => {
|
||||
await discovery.initialRefresh();
|
||||
|
||||
createFileSystemWatcherSpy.mockClear();
|
||||
|
||||
const previousWorkspaceFolders = workspace.workspaceFolders || [];
|
||||
const newWorkspaceFolders: WorkspaceFolder[] = [
|
||||
{
|
||||
uri: Uri.file(join(tmpDir, "workspace2")),
|
||||
name: "workspace2",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.file(join(tmpDir, "workspace3")),
|
||||
name: "workspace3",
|
||||
index: 1,
|
||||
},
|
||||
];
|
||||
workspaceFoldersSpy.mockReturnValue(newWorkspaceFolders);
|
||||
|
||||
onDidChangeWorkspaceFolders.fire({
|
||||
added: newWorkspaceFolders,
|
||||
removed: previousWorkspaceFolders,
|
||||
});
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
// Called twice for each workspace folder
|
||||
expect(createFileSystemWatcherSpy).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it("should discover files in new workspaces", async () => {
|
||||
makeTestFile(join(workspacePath, "123.test"));
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "123.test"), contents: "123" },
|
||||
]);
|
||||
|
||||
const previousWorkspaceFolders = workspace.workspaceFolders || [];
|
||||
const newWorkspaceFolders: WorkspaceFolder[] = [
|
||||
workspaceFolder,
|
||||
{
|
||||
uri: Uri.file(join(tmpDir, "workspace2")),
|
||||
name: "workspace2",
|
||||
index: 1,
|
||||
},
|
||||
];
|
||||
workspaceFoldersSpy.mockReturnValue(newWorkspaceFolders);
|
||||
|
||||
makeTestFile(join(tmpDir, "workspace2", "456.test"));
|
||||
|
||||
onDidChangeWorkspaceFolders.fire({
|
||||
added: newWorkspaceFolders,
|
||||
removed: previousWorkspaceFolders,
|
||||
});
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(workspacePath, "123.test"), contents: "123" },
|
||||
{ path: join(tmpDir, "workspace2", "456.test"), contents: "456" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should forgot files in old workspaces, even if the files on disk still exist", async () => {
|
||||
const workspaceFolders: WorkspaceFolder[] = [
|
||||
{
|
||||
uri: Uri.file(join(tmpDir, "workspace1")),
|
||||
name: "workspace1",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
uri: Uri.file(join(tmpDir, "workspace2")),
|
||||
name: "workspace2",
|
||||
index: 1,
|
||||
},
|
||||
];
|
||||
workspaceFoldersSpy.mockReturnValue(workspaceFolders);
|
||||
|
||||
makeTestFile(join(tmpDir, "workspace1", "123.test"));
|
||||
makeTestFile(join(tmpDir, "workspace2", "456.test"));
|
||||
|
||||
await discovery.initialRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(tmpDir, "workspace1", "123.test"), contents: "123" },
|
||||
{ path: join(tmpDir, "workspace2", "456.test"), contents: "456" },
|
||||
]);
|
||||
|
||||
workspaceFoldersSpy.mockReturnValue([workspaceFolders[0]]);
|
||||
onDidChangeWorkspaceFolders.fire({
|
||||
added: [],
|
||||
removed: [workspaceFolders[1]],
|
||||
});
|
||||
await discovery.waitForCurrentRefresh();
|
||||
|
||||
expectArraysEqual(discovery.getPaths(), [
|
||||
{ path: join(tmpDir, "workspace1", "123.test"), contents: "123" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeTestFile(path: string, contents?: string) {
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
writeFileSync(path, contents ?? basename(path, ".test"));
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export function expectArraysEqual<T>(actual: T[], expected: T[]) {
|
||||
// Check that all of the expected values are present
|
||||
expect(actual).toEqual(expect.arrayContaining(expected));
|
||||
// Check that no extra un-expected values are present
|
||||
expect(expected).toEqual(expect.arrayContaining(actual));
|
||||
}
|
||||
Reference in New Issue
Block a user