1import * as ts from "../../../_namespaces/ts"; 2 3describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => { 4 function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) { 5 assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); 6 const seen = new ts.Map<string, true>(); 7 ts.forEach(actual, f => { 8 assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); 9 seen.set(f, true); 10 assert.isTrue(ts.contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); 11 }); 12 } 13 14 function createVerifyInitialOpen(session: ts.projectSystem.TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: ts.server.ProjectsUpdatedInBackgroundEvent[]) => void) { 15 return (file: ts.projectSystem.File) => { 16 session.executeCommandSeq({ 17 command: ts.server.CommandNames.Open, 18 arguments: { 19 file: file.path 20 } 21 } as ts.projectSystem.protocol.OpenRequest); 22 verifyProjectsUpdatedInBackgroundEventHandler([]); 23 }; 24 } 25 26 interface ProjectsUpdatedInBackgroundEventVerifier { 27 session: ts.projectSystem.TestSession; 28 verifyProjectsUpdatedInBackgroundEventHandler(events: ts.server.ProjectsUpdatedInBackgroundEvent[]): void; 29 verifyInitialOpen(file: ts.projectSystem.File): void; 30 } 31 32 function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: ts.projectSystem.TestServerHost, logger?: ts.projectSystem.Logger) => ProjectsUpdatedInBackgroundEventVerifier) { 33 it("when adding new file", () => { 34 const commonFile1: ts.projectSystem.File = { 35 path: "/a/b/file1.ts", 36 content: "export var x = 10;" 37 }; 38 const commonFile2: ts.projectSystem.File = { 39 path: "/a/b/file2.ts", 40 content: "export var y = 10;" 41 }; 42 const commonFile3: ts.projectSystem.File = { 43 path: "/a/b/file3.ts", 44 content: "export var z = 10;" 45 }; 46 const configFile: ts.projectSystem.File = { 47 path: "/a/b/tsconfig.json", 48 content: `{}` 49 }; 50 const openFiles = [commonFile1.path]; 51 const host = ts.projectSystem.createServerHost([commonFile1, ts.projectSystem.libFile, configFile]); 52 const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); 53 verifyInitialOpen(commonFile1); 54 55 host.writeFile(commonFile2.path, commonFile2.content); 56 host.runQueuedTimeoutCallbacks(); 57 verifyProjectsUpdatedInBackgroundEventHandler([{ 58 eventName: ts.server.ProjectsUpdatedInBackgroundEvent, 59 data: { 60 openFiles 61 } 62 }]); 63 64 host.writeFile(commonFile3.path, commonFile3.content); 65 host.runQueuedTimeoutCallbacks(); 66 verifyProjectsUpdatedInBackgroundEventHandler([{ 67 eventName: ts.server.ProjectsUpdatedInBackgroundEvent, 68 data: { 69 openFiles 70 } 71 }]); 72 }); 73 74 describe("with --out or --outFile setting", () => { 75 function verifyEventWithOutSettings(compilerOptions: ts.CompilerOptions = {}) { 76 const config: ts.projectSystem.File = { 77 path: "/a/tsconfig.json", 78 content: JSON.stringify({ 79 compilerOptions 80 }) 81 }; 82 83 const f1: ts.projectSystem.File = { 84 path: "/a/a.ts", 85 content: "export let x = 1" 86 }; 87 const f2: ts.projectSystem.File = { 88 path: "/a/b.ts", 89 content: "export let y = 1" 90 }; 91 92 const openFiles = [f1.path]; 93 const files = [f1, config, ts.projectSystem.libFile]; 94 const host = ts.projectSystem.createServerHost(files); 95 const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); 96 verifyInitialOpen(f1); 97 98 host.writeFile(f2.path, f2.content); 99 host.runQueuedTimeoutCallbacks(); 100 101 verifyProjectsUpdatedInBackgroundEventHandler([{ 102 eventName: ts.server.ProjectsUpdatedInBackgroundEvent, 103 data: { 104 openFiles 105 } 106 }]); 107 108 host.writeFile(f2.path, "export let x = 11"); 109 host.runQueuedTimeoutCallbacks(); 110 verifyProjectsUpdatedInBackgroundEventHandler([{ 111 eventName: ts.server.ProjectsUpdatedInBackgroundEvent, 112 data: { 113 openFiles 114 } 115 }]); 116 } 117 118 it("when both options are not set", () => { 119 verifyEventWithOutSettings(); 120 }); 121 122 it("when --out is set", () => { 123 const outJs = "/a/out.js"; 124 verifyEventWithOutSettings({ out: outJs }); 125 }); 126 127 it("when --outFile is set", () => { 128 const outJs = "/a/out.js"; 129 verifyEventWithOutSettings({ outFile: outJs }); 130 }); 131 }); 132 133 describe("with modules and configured project", () => { 134 const file1Consumer1Path = "/a/b/file1Consumer1.ts"; 135 const moduleFile1Path = "/a/b/moduleFile1.ts"; 136 const configFilePath = "/a/b/tsconfig.json"; 137 interface InitialStateParams { 138 /** custom config file options */ 139 configObj?: any; 140 /** Additional files and folders to add */ 141 getAdditionalFileOrFolder?(): ts.projectSystem.File[]; 142 /** initial list of files to reload in fs and first file in this list being the file to open */ 143 firstReloadFileList?: string[]; 144 } 145 function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { 146 const moduleFile1: ts.projectSystem.File = { 147 path: moduleFile1Path, 148 content: "export function Foo() { };", 149 }; 150 151 const file1Consumer1: ts.projectSystem.File = { 152 path: file1Consumer1Path, 153 content: `import {Foo} from "./moduleFile1"; export var y = 10;`, 154 }; 155 156 const file1Consumer2: ts.projectSystem.File = { 157 path: "/a/b/file1Consumer2.ts", 158 content: `import {Foo} from "./moduleFile1"; let z = 10;`, 159 }; 160 161 const moduleFile2: ts.projectSystem.File = { 162 path: "/a/b/moduleFile2.ts", 163 content: `export var Foo4 = 10;`, 164 }; 165 166 const globalFile3: ts.projectSystem.File = { 167 path: "/a/b/globalFile3.ts", 168 content: `interface GlobalFoo { age: number }` 169 }; 170 171 const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : []; 172 const configFile = { 173 path: configFilePath, 174 content: JSON.stringify(configObj || { compilerOptions: {} }) 175 }; 176 177 const files: ts.projectSystem.File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, ts.projectSystem.libFile, configFile]; 178 179 const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files; 180 const host = ts.projectSystem.createServerHost([filesToReload[0], configFile]); 181 182 // Initial project creation 183 const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); 184 const openFiles = [filesToReload[0].path]; 185 verifyInitialOpen(filesToReload[0]); 186 187 // Since this is first event, it will have all the files 188 filesToReload.forEach(f => host.ensureFileOrFolder(f)); 189 if (!firstReloadFileList) host.runQueuedTimeoutCallbacks(); // Invalidated module resolutions to schedule project update 190 verifyProjectsUpdatedInBackgroundEvent(); 191 192 return { 193 host, 194 moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, 195 updateContentOfOpenFile, 196 verifyNoProjectsUpdatedInBackgroundEvent, 197 verifyProjectsUpdatedInBackgroundEvent 198 }; 199 200 function getFiles(filelist: string[]) { 201 return ts.map(filelist, getFile); 202 } 203 204 function getFile(fileName: string) { 205 return ts.find(files, file => file.path === fileName)!; 206 } 207 208 function verifyNoProjectsUpdatedInBackgroundEvent() { 209 host.runQueuedTimeoutCallbacks(); 210 verifyProjectsUpdatedInBackgroundEventHandler([]); 211 } 212 213 function verifyProjectsUpdatedInBackgroundEvent() { 214 host.runQueuedTimeoutCallbacks(); 215 verifyProjectsUpdatedInBackgroundEventHandler([{ 216 eventName: ts.server.ProjectsUpdatedInBackgroundEvent, 217 data: { 218 openFiles 219 } 220 }]); 221 } 222 223 function updateContentOfOpenFile(file: ts.projectSystem.File, newContent: string) { 224 session.executeCommandSeq<ts.projectSystem.protocol.ChangeRequest>({ 225 command: ts.server.CommandNames.Change, 226 arguments: { 227 file: file.path, 228 insertString: newContent, 229 endLine: 1, 230 endOffset: file.content.length, 231 line: 1, 232 offset: 1 233 } 234 }); 235 file.content = newContent; 236 } 237 } 238 239 it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { 240 const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); 241 242 // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` 243 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 244 verifyProjectsUpdatedInBackgroundEvent(); 245 246 // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` 247 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { console.log('hi'); };`); 248 verifyProjectsUpdatedInBackgroundEvent(); 249 }); 250 251 it("should be up-to-date with the reference map changes", () => { 252 const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState(); 253 254 // Change file1Consumer1 content to `export let y = Foo();` 255 updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); 256 verifyNoProjectsUpdatedInBackgroundEvent(); 257 258 // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` 259 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 260 verifyProjectsUpdatedInBackgroundEvent(); 261 262 // Add the import statements back to file1Consumer1 263 updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); 264 verifyNoProjectsUpdatedInBackgroundEvent(); 265 266 // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` 267 host.writeFile(moduleFile1.path, `export var T: number;export var T2: string;export function Foo() { };`); 268 verifyProjectsUpdatedInBackgroundEvent(); 269 270 // Multiple file edits in one go: 271 272 // Change file1Consumer1 content to `export let y = Foo();` 273 // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` 274 updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); 275 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 276 verifyProjectsUpdatedInBackgroundEvent(); 277 }); 278 279 it("should be up-to-date with deleted files", () => { 280 const { host, moduleFile1, file1Consumer2, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); 281 282 // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` 283 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 284 285 // Delete file1Consumer2 286 host.deleteFile(file1Consumer2.path); 287 verifyProjectsUpdatedInBackgroundEvent(); 288 }); 289 290 it("should be up-to-date with newly created files", () => { 291 const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState(); 292 293 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 294 host.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); 295 verifyProjectsUpdatedInBackgroundEvent(); 296 }); 297 298 it("should detect changes in non-root files", () => { 299 const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ 300 configObj: { files: [file1Consumer1Path] }, 301 }); 302 303 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 304 verifyProjectsUpdatedInBackgroundEvent(); 305 306 // change file1 internal, and verify only file1 is affected 307 host.writeFile(moduleFile1.path, moduleFile1.content + "var T1: number;"); 308 verifyProjectsUpdatedInBackgroundEvent(); 309 }); 310 311 it("should return all files if a global file changed shape", () => { 312 const { host, globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); 313 314 host.writeFile(globalFile3.path, globalFile3.content + "var T2: string;"); 315 verifyProjectsUpdatedInBackgroundEvent(); 316 }); 317 318 it("should always return the file itself if '--isolatedModules' is specified", () => { 319 const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ 320 configObj: { compilerOptions: { isolatedModules: true } } 321 }); 322 323 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 324 verifyProjectsUpdatedInBackgroundEvent(); 325 }); 326 327 it("should always return the file itself if '--out' or '--outFile' is specified", () => { 328 const outFilePath = "/a/b/out.js"; 329 const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ 330 configObj: { compilerOptions: { module: "system", outFile: outFilePath } } 331 }); 332 333 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 334 verifyProjectsUpdatedInBackgroundEvent(); 335 }); 336 337 it("should return cascaded affected file list", () => { 338 const file1Consumer1Consumer1: ts.projectSystem.File = { 339 path: "/a/b/file1Consumer1Consumer1.ts", 340 content: `import {y} from "./file1Consumer1";` 341 }; 342 const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ 343 getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] 344 }); 345 346 updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); 347 verifyNoProjectsUpdatedInBackgroundEvent(); 348 349 // Doesnt change the shape of file1Consumer1 350 host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); 351 verifyProjectsUpdatedInBackgroundEvent(); 352 353 // Change both files before the timeout 354 updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); 355 host.writeFile(moduleFile1.path, `export var T2: number;export function Foo() { };`); 356 verifyProjectsUpdatedInBackgroundEvent(); 357 }); 358 359 it("should work fine for files with circular references", () => { 360 const file1: ts.projectSystem.File = { 361 path: "/a/b/file1.ts", 362 content: ` 363 /// <reference path="./file2.ts" /> 364 export var t1 = 10;` 365 }; 366 const file2: ts.projectSystem.File = { 367 path: "/a/b/file2.ts", 368 content: ` 369 /// <reference path="./file1.ts" /> 370 export var t2 = 10;` 371 }; 372 const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ 373 getAdditionalFileOrFolder: () => [file1, file2], 374 firstReloadFileList: [file1.path, ts.projectSystem.libFile.path, file2.path, configFilePath] 375 }); 376 377 host.writeFile(file2.path, file2.content + "export var t3 = 10;"); 378 verifyProjectsUpdatedInBackgroundEvent(); 379 }); 380 381 it("should detect removed code file", () => { 382 const referenceFile1: ts.projectSystem.File = { 383 path: "/a/b/referenceFile1.ts", 384 content: ` 385 /// <reference path="./moduleFile1.ts" /> 386 export var x = Foo();` 387 }; 388 const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ 389 getAdditionalFileOrFolder: () => [referenceFile1], 390 firstReloadFileList: [referenceFile1.path, ts.projectSystem.libFile.path, moduleFile1Path, configFilePath] 391 }); 392 393 host.deleteFile(moduleFile1Path); 394 verifyProjectsUpdatedInBackgroundEvent(); 395 }); 396 397 it("should detect non-existing code file", () => { 398 const referenceFile1: ts.projectSystem.File = { 399 path: "/a/b/referenceFile1.ts", 400 content: ` 401 /// <reference path="./moduleFile2.ts" /> 402 export var x = Foo();` 403 }; 404 const { host, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ 405 getAdditionalFileOrFolder: () => [referenceFile1], 406 firstReloadFileList: [referenceFile1.path, ts.projectSystem.libFile.path, configFilePath] 407 }); 408 409 updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); 410 verifyNoProjectsUpdatedInBackgroundEvent(); 411 412 // Create module File2 and see both files are saved 413 host.writeFile(moduleFile2.path, moduleFile2.content); 414 verifyProjectsUpdatedInBackgroundEvent(); 415 }); 416 }); 417 418 describe("resolution when resolution cache size", () => { 419 function verifyWithMaxCacheLimit(subScenario: string, useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { 420 it(subScenario, () => { 421 const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; 422 const file1: ts.projectSystem.File = { 423 path: rootFolder + "a/b/project/file1.ts", 424 content: 'import a from "file2"' 425 }; 426 const file2: ts.projectSystem.File = { 427 path: rootFolder + "a/b/node_modules/file2.d.ts", 428 content: "export class a { }" 429 }; 430 const file3: ts.projectSystem.File = { 431 path: rootFolder + "a/b/project/file3.ts", 432 content: "export class c { }" 433 }; 434 const configFile: ts.projectSystem.File = { 435 path: rootFolder + "a/b/project/tsconfig.json", 436 content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) 437 }; 438 439 const openFiles = [file1.path]; 440 const host = ts.projectSystem.createServerHost([file1, file3, ts.projectSystem.libFile, configFile]); 441 const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host, ts.projectSystem.createLoggerWithInMemoryLogs(host)); 442 verifyInitialOpen(file1); 443 444 file3.content += "export class d {}"; 445 host.writeFile(file3.path, file3.content); 446 host.checkTimeoutQueueLengthAndRun(2); 447 448 // Since this is first event 449 verifyProjectsUpdatedInBackgroundEventHandler([{ 450 eventName: ts.server.ProjectsUpdatedInBackgroundEvent, 451 data: { 452 openFiles 453 } 454 }]); 455 456 host.writeFile(file2.path, file2.content); 457 host.runQueuedTimeoutCallbacks(); // For invalidation 458 host.runQueuedTimeoutCallbacks(); // For actual update 459 460 verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ 461 eventName: ts.server.ProjectsUpdatedInBackgroundEvent, 462 data: { 463 openFiles 464 } 465 }] : []); 466 ts.projectSystem.baselineTsserverLogs("projectUpdatedInBackground", `${scenario} and ${subScenario}`, session); 467 }); 468 } 469 verifyWithMaxCacheLimit("project is not at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); 470 verifyWithMaxCacheLimit("project is at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); 471 }); 472 } 473 474 describe("when event handler is set in the session", () => { 475 verifyProjectsUpdatedInBackgroundEvent("when event handler is set in the session", createSessionWithProjectChangedEventHandler); 476 477 function createSessionWithProjectChangedEventHandler(host: ts.projectSystem.TestServerHost, logger: ts.projectSystem.Logger | undefined): ProjectsUpdatedInBackgroundEventVerifier { 478 const { session, events: projectChangedEvents } = ts.projectSystem.createSessionWithEventTracking<ts.server.ProjectsUpdatedInBackgroundEvent>( 479 host, 480 ts.server.ProjectsUpdatedInBackgroundEvent, 481 logger && { logger } 482 ); 483 return { 484 session, 485 verifyProjectsUpdatedInBackgroundEventHandler, 486 verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) 487 }; 488 489 function eventToString(event: ts.server.ProjectsUpdatedInBackgroundEvent) { 490 return JSON.stringify(event && { eventName: event.eventName, data: event.data }); 491 } 492 493 function eventsToString(events: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) { 494 return "[" + ts.map(events, eventToString).join(",") + "]"; 495 } 496 497 function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) { 498 assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); 499 ts.forEach(projectChangedEvents, (actualEvent, i) => { 500 const expectedEvent = expectedEvents[i]; 501 assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); 502 verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); 503 }); 504 505 // Verified the events, reset them 506 projectChangedEvents.length = 0; 507 } 508 } 509 }); 510 511 describe("when event handler is not set but session is created with canUseEvents = true", () => { 512 describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { 513 verifyProjectsUpdatedInBackgroundEvent("without noGetErrOnBackgroundUpdate", createSessionThatUsesEvents); 514 }); 515 516 describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { 517 verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", (host, logger) => createSessionThatUsesEvents(host, logger, /*noGetErrOnBackgroundUpdate*/ true)); 518 }); 519 520 521 function createSessionThatUsesEvents(host: ts.projectSystem.TestServerHost, logger: ts.projectSystem.Logger | undefined, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { 522 const { session, getEvents, clearEvents } = ts.projectSystem.createSessionWithDefaultEventHandler<ts.projectSystem.protocol.ProjectsUpdatedInBackgroundEvent>( 523 host, 524 ts.server.ProjectsUpdatedInBackgroundEvent, 525 { noGetErrOnBackgroundUpdate, logger: logger || ts.projectSystem.createHasErrorMessageLogger() } 526 ); 527 528 return { 529 session, 530 verifyProjectsUpdatedInBackgroundEventHandler, 531 verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) 532 }; 533 534 function verifyProjectsUpdatedInBackgroundEventHandler(expected: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) { 535 const expectedEvents: ts.projectSystem.protocol.ProjectsUpdatedInBackgroundEventBody[] = ts.map(expected, e => { 536 return { 537 openFiles: e.data.openFiles 538 }; 539 }); 540 const events = getEvents(); 541 assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${ts.map(events, e => e.body)} Expected: ${expectedEvents}`); 542 ts.forEach(events, (actualEvent, i) => { 543 const expectedEvent = expectedEvents[i]; 544 verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles); 545 }); 546 547 // Verified the events, reset them 548 clearEvents(); 549 550 if (events.length) { 551 host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate 552 } 553 } 554 } 555 }); 556}); 557