Skip to content

Commit 3052913

Browse files
author
zhengbli
committed
add tests for tsserver project system
1 parent 131f759 commit 3052913

2 files changed

Lines changed: 179 additions & 20 deletions

File tree

src/compiler/sys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace ts {
44
export type FileWatcherCallback = (fileName: string, removed?: boolean) => void;
5-
export type DirectoryWatcherCallback = (directoryName: string) => void;
5+
export type DirectoryWatcherCallback = (fileName: string) => void;
66
export interface WatchedFile {
77
fileName: string;
88
callback: FileWatcherCallback;

tests/cases/unittests/tsserverProjectSystem.ts

Lines changed: 178 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,30 @@ namespace ts {
107107
}
108108
}
109109

110+
function checkConfiguredProjectNumber(projectService: server.ProjectService, expected: number) {
111+
assert.equal(projectService.configuredProjects.length, expected, `expected ${expected} configured project(s)`);
112+
}
113+
114+
function checkInferredProjectNumber(projectService: server.ProjectService, expected: number) {
115+
assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`);
116+
}
117+
118+
function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) {
119+
checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles);
120+
}
121+
122+
function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[]) {
123+
checkMapKeys("watchedDirectories", host.watchedDirectories, expectedDirectories);
124+
}
125+
126+
function checkConfiguredProjectActualFiles(project: server.Project, expectedFiles: string[]) {
127+
checkFileNames("configuredProjects project, actualFileNames", project.getFileNames(), expectedFiles);
128+
}
129+
130+
function checkConfiguredProjectRootFiles(project: server.Project, expectedFiles: string[]) {
131+
checkFileNames("configuredProjects project, rootFileNames", project.getRootFiles(), expectedFiles);
132+
}
133+
110134
class TestServerHost implements server.ServerHost {
111135
args: string[] = [];
112136
newLine: "\n";
@@ -188,6 +212,26 @@ namespace ts {
188212
};
189213
}
190214

215+
triggerDirectoryWatcherCallback(directoryName: string, fileName: string): void {
216+
const path = this.toPath(directoryName);
217+
const callbacks = lookUp(this.watchedDirectories, path);
218+
if (callbacks) {
219+
for (const callback of callbacks) {
220+
callback.cb(fileName);
221+
}
222+
}
223+
}
224+
225+
triggerFileWatcherCallback(fileName: string): void {
226+
const path = this.toPath(fileName);
227+
const callbacks = lookUp(this.watchedFiles, path);
228+
if (callbacks) {
229+
for (const callback of callbacks) {
230+
callback(path, /*removed*/ true);
231+
}
232+
}
233+
}
234+
191235
watchFile(fileName: string, callback: FileWatcherCallback) {
192236
const path = this.toPath(fileName);
193237
const callbacks = lookUp(this.watchedFiles, path) || (this.watchedFiles[path] = []);
@@ -204,7 +248,7 @@ namespace ts {
204248
}
205249

206250
// TOOD: record and invoke callbacks to simulate timer events
207-
readonly setTimeout = (callback: (...args: any[]) => void, ms: number, ...args: any[]): any => void 0;
251+
readonly setTimeout = setTimeout;
208252
readonly clearTimeout = (timeoutId: any): void => void 0;
209253
readonly readFile = (s: string) => (<File>this.fs.get(this.toPath(s))).content;
210254
readonly resolvePath = (s: string) => s;
@@ -216,7 +260,20 @@ namespace ts {
216260
readonly exit = () => notImplemented();
217261
}
218262

219-
describe("tsserver project system:", () => {
263+
describe("tsserver-project-system", () => {
264+
const commonFile1: FileOrFolder = {
265+
path: "/a/b/commonFile1.ts",
266+
content: "let x = 1"
267+
};
268+
const commonFile2: FileOrFolder = {
269+
path: "/a/b/commonFile2.ts",
270+
content: "let y = 1"
271+
};
272+
const libFile: FileOrFolder = {
273+
path: "/a/lib/lib.d.ts",
274+
content: libFileContent
275+
};
276+
220277
it("create inferred project", () => {
221278
const appFile: FileOrFolder = {
222279
path: "/a/b/c/app.ts",
@@ -225,10 +282,7 @@ namespace ts {
225282
console.log(f)
226283
`
227284
};
228-
const libFile: FileOrFolder = {
229-
path: "/a/lib/lib.d.ts",
230-
content: libFileContent
231-
};
285+
232286
const moduleFile: FileOrFolder = {
233287
path: "/a/b/c/module.d.ts",
234288
content: `export let x: number`
@@ -238,13 +292,13 @@ namespace ts {
238292
const { configFileName } = projectService.openClientFile(appFile.path);
239293

240294
assert(!configFileName, `should not find config, got: '${configFileName}`);
241-
assert.equal(projectService.inferredProjects.length, 1, "expected one inferred project");
242-
assert.equal(projectService.configuredProjects.length, 0, "expected no configured project");
295+
checkConfiguredProjectNumber(projectService, 0);
296+
checkInferredProjectNumber(projectService, 1);
243297

244298
const project = projectService.inferredProjects[0];
245299

246300
checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]);
247-
checkMapKeys("watchedDirectories", host.watchedDirectories, ["/a/b/c", "/a/b", "/a"]);
301+
checkWatchedDirectories(host, ["/a/b/c", "/a/b", "/a"]);
248302
});
249303

250304
it("create configured project without file list", () => {
@@ -258,10 +312,6 @@ namespace ts {
258312
]
259313
}`
260314
};
261-
const libFile: FileOrFolder = {
262-
path: "/a/lib/lib.d.ts",
263-
content: libFileContent
264-
};
265315
const file1: FileOrFolder = {
266316
path: "/a/b/c/f1.ts",
267317
content: "let x = 1"
@@ -274,21 +324,130 @@ namespace ts {
274324
path: "/a/b/e/f3.ts",
275325
content: "let z = 1"
276326
};
327+
277328
const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [ configFile, libFile, file1, file2, file3 ]);
278329
const projectService = new server.ProjectService(host, nullLogger);
279330
const { configFileName, configFileErrors } = projectService.openClientFile(file1.path);
280331

281332
assert(configFileName, "should find config file");
282333
assert.isTrue(!configFileErrors, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`);
283-
assert.equal(projectService.inferredProjects.length, 0, "expected no inferred project");
284-
assert.equal(projectService.configuredProjects.length, 1, "expected one configured project");
334+
checkInferredProjectNumber(projectService, 0);
335+
checkConfiguredProjectNumber(projectService, 1);
285336

286337
const project = projectService.configuredProjects[0];
287-
checkFileNames("configuredProjects project, actualFileNames", project.getFileNames(), [file1.path, libFile.path, file2.path]);
288-
checkFileNames("configuredProjects project, rootFileNames", project.getRootFiles(), [file1.path, file2.path]);
338+
checkConfiguredProjectActualFiles(project, [file1.path, libFile.path, file2.path]);
339+
checkConfiguredProjectRootFiles(project, [file1.path, file2.path]);
340+
// watching all files except one that was open
341+
checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]);
342+
checkWatchedDirectories(host, [getDirectoryPath(configFile.path)]);
343+
});
289344

290-
checkMapKeys("watchedFiles", host.watchedFiles, [configFile.path, file2.path, libFile.path]); // watching all files except one that was open
291-
checkMapKeys("watchedDirectories", host.watchedDirectories, [getDirectoryPath(configFile.path)]);
345+
it("add and then remove a config file in a folder with loose files", () => {
346+
const configFile: FileOrFolder = {
347+
path: "/a/b/tsconfig.json",
348+
content: `{
349+
"files": ["commonFile1.ts"]
350+
}`
351+
};
352+
const filesWithoutConfig = [ libFile, commonFile1, commonFile2 ];
353+
const filesWithConfig = [ libFile, commonFile1, commonFile2, configFile ];
354+
const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", filesWithoutConfig);
355+
const projectService = new server.ProjectService(host, nullLogger);
356+
projectService.openClientFile(commonFile1.path);
357+
projectService.openClientFile(commonFile2.path);
358+
359+
checkInferredProjectNumber(projectService, 2);
360+
checkWatchedDirectories(host, ["/a/b", "/a"]);
361+
362+
// Add a tsconfig file
363+
host.reloadFS(filesWithConfig);
364+
host.triggerDirectoryWatcherCallback("/a/b", configFile.path);
365+
366+
checkInferredProjectNumber(projectService, 1);
367+
checkConfiguredProjectNumber(projectService, 1);
368+
// watching all files except one that was open
369+
checkWatchedFiles(host, [libFile.path, configFile.path]);
370+
371+
// remove the tsconfig file
372+
host.reloadFS(filesWithoutConfig);
373+
host.triggerFileWatcherCallback(configFile.path);
374+
checkInferredProjectNumber(projectService, 2);
375+
checkConfiguredProjectNumber(projectService, 0);
376+
checkWatchedDirectories(host, ["/a/b", "/a"]);
377+
});
378+
379+
it("add new files to a configured project without file list", (done: () => void) => {
380+
const configFile: FileOrFolder = {
381+
path: "/a/b/tsconfig.json",
382+
content: `{}`
383+
};
384+
const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [commonFile1, libFile, configFile]);
385+
const projectService = new server.ProjectService(host, nullLogger);
386+
projectService.openClientFile(commonFile1.path);
387+
checkWatchedDirectories(host, ["/a/b"]);
388+
checkConfiguredProjectNumber(projectService, 1);
389+
390+
const project = projectService.configuredProjects[0];
391+
checkConfiguredProjectRootFiles(project, [commonFile1.path]);
392+
393+
// add a new ts file
394+
host.reloadFS([commonFile1, commonFile2, libFile, configFile]);
395+
host.triggerDirectoryWatcherCallback("/a/b", commonFile2.path);
396+
// project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer.
397+
setTimeout(() => {
398+
checkConfiguredProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
399+
done();
400+
}, 1000);
401+
});
402+
403+
it("should ignore non-existing files specified in the config file", () => {
404+
const configFile: FileOrFolder = {
405+
path: "/a/b/tsconfig.json",
406+
content: `{
407+
"compilerOptions": {},
408+
"files": [
409+
"commonFile1.ts",
410+
"commonFile3.ts"
411+
]
412+
}`
413+
};
414+
const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [commonFile1, commonFile2, configFile]);
415+
const projectService = new server.ProjectService(host, nullLogger);
416+
projectService.openClientFile(commonFile1.path);
417+
projectService.openClientFile(commonFile2.path);
418+
419+
checkConfiguredProjectNumber(projectService, 1);
420+
const project = projectService.configuredProjects[0];
421+
checkConfiguredProjectRootFiles(project, [commonFile1.path]);
422+
checkInferredProjectNumber(projectService, 1);
423+
});
424+
425+
it("handle recreated files correctly", (done: () => void) => {
426+
const configFile: FileOrFolder = {
427+
path: "/a/b/tsconfig.json",
428+
content: `{}`
429+
};
430+
const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [commonFile1, commonFile2, configFile]);
431+
const projectService = new server.ProjectService(host, nullLogger);
432+
projectService.openClientFile(commonFile1.path);
433+
434+
checkConfiguredProjectNumber(projectService, 1);
435+
const project = projectService.configuredProjects[0];
436+
checkConfiguredProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
437+
438+
// delete commonFile1
439+
projectService.closeClientFile(commonFile1.path);
440+
host.reloadFS([configFile]);
441+
host.triggerDirectoryWatcherCallback("/a/b", commonFile1.path);
442+
host.setTimeout(() => {
443+
// re-add commonFile1
444+
host.reloadFS([commonFile1, configFile]);
445+
projectService.openClientFile(commonFile1.path);
446+
host.setTimeout(() => {
447+
checkConfiguredProjectRootFiles(project, [commonFile1.path, commonFile2.path]);
448+
done();
449+
}, 500);
450+
}, 500);
292451
});
293452
});
294453
}

0 commit comments

Comments
 (0)