1namespace ts.projectSystem { 2 describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { 3 const aTs: File = { 4 path: `${tscWatch.projects}/a/a.ts`, 5 content: "export class A { }" 6 }; 7 const configA: File = { 8 path: `${tscWatch.projects}/a/tsconfig.json`, 9 content: "{}" 10 }; 11 const bTsPath = `${tscWatch.projects}/b/b.ts`; 12 const configBPath = `${tscWatch.projects}/b/tsconfig.json`; 13 const files = [libFile, aTs, configA]; 14 15 function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => { 16 session: TestSession; 17 getNumberOfEvents: () => number; 18 clearEvents: () => void; 19 verifyProjectLoadEvents: (expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) => void; 20 }) { 21 function createSessionToVerifyEvent(files: readonly File[]) { 22 const host = createServerHost(files); 23 const originalReadFile = host.readFile; 24 const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host); 25 host.readFile = file => { 26 if (file === configA.path || file === configBPath) { 27 assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file"); 28 } 29 return originalReadFile.call(host, file); 30 }; 31 const service = session.getProjectService(); 32 return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents }; 33 34 function verifyEvent(project: server.Project, reason: string) { 35 verifyProjectLoadEvents([ 36 { eventName: server.ProjectLoadingStartEvent, data: { project, reason } }, 37 { eventName: server.ProjectLoadingFinishEvent, data: { project } } 38 ]); 39 clearEvents(); 40 } 41 42 function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) { 43 openFilesForSession([file], session); 44 checkNumberOfProjects(service, { configuredProjects }); 45 const project = service.configuredProjects.get(configPath)!; 46 assert.isDefined(project); 47 verifyEvent(project, `Creating possible configured project for ${file.path} to open`); 48 } 49 } 50 51 it("when project is created by open file", () => { 52 const bTs: File = { 53 path: bTsPath, 54 content: "export class B {}" 55 }; 56 const configB: File = { 57 path: configBPath, 58 content: "{}" 59 }; 60 const { verifyEventWithOpenTs } = createSessionToVerifyEvent(files.concat(bTs, configB)); 61 verifyEventWithOpenTs(aTs, configA.path, 1); 62 verifyEventWithOpenTs(bTs, configB.path, 2); 63 }); 64 65 it("when change is detected in the config file", () => { 66 const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files); 67 verifyEventWithOpenTs(aTs, configA.path, 1); 68 69 host.writeFile(configA.path, configA.content); 70 host.checkTimeoutQueueLengthAndRun(2); 71 const project = service.configuredProjects.get(configA.path)!; 72 verifyEvent(project, `Change in config file detected`); 73 }); 74 75 it("when change is detected in an extended config file", () => { 76 const bTs: File = { 77 path: bTsPath, 78 content: "export class B {}" 79 }; 80 const configB: File = { 81 path: configBPath, 82 content: JSON.stringify({ 83 extends: "../a/tsconfig.json", 84 }) 85 }; 86 const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files.concat(bTs, configB)); 87 verifyEventWithOpenTs(bTs, configB.path, 1); 88 89 host.writeFile(configA.path, configA.content); 90 host.checkTimeoutQueueLengthAndRun(2); 91 const project = service.configuredProjects.get(configB.path)!; 92 verifyEvent(project, `Change in extended config file ${configA.path} detected`); 93 }); 94 95 describe("when opening original location project", () => { 96 it("with project references", () => { 97 verify(); 98 }); 99 100 it("when disableSourceOfProjectReferenceRedirect is true", () => { 101 verify(/*disableSourceOfProjectReferenceRedirect*/ true); 102 }); 103 104 function verify(disableSourceOfProjectReferenceRedirect?: true) { 105 const aDTs: File = { 106 path: `${tscWatch.projects}/a/a.d.ts`, 107 content: `export declare class A { 108} 109//# sourceMappingURL=a.d.ts.map 110` 111 }; 112 const aDTsMap: File = { 113 path: `${tscWatch.projects}/a/a.d.ts.map`, 114 content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` 115 }; 116 const bTs: File = { 117 path: bTsPath, 118 content: `import {A} from "../a/a"; new A();` 119 }; 120 const configB: File = { 121 path: configBPath, 122 content: JSON.stringify({ 123 ...(disableSourceOfProjectReferenceRedirect && { 124 compilerOptions: { 125 disableSourceOfProjectReferenceRedirect 126 } 127 }), 128 references: [{ path: "../a" }] 129 }) 130 }; 131 132 const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB)); 133 verifyEventWithOpenTs(bTs, configB.path, 1); 134 135 session.executeCommandSeq<protocol.ReferencesRequest>({ 136 command: protocol.CommandTypes.References, 137 arguments: { 138 file: bTs.path, 139 ...protocolLocationFromSubstring(bTs.content, "A()") 140 } 141 }); 142 143 checkNumberOfProjects(service, { configuredProjects: 2 }); 144 const project = service.configuredProjects.get(configA.path)!; 145 assert.isDefined(project); 146 verifyEvent( 147 project, 148 disableSourceOfProjectReferenceRedirect ? 149 `Creating project for original file: ${aTs.path} for location: ${aDTs.path}` : 150 `Creating project for original file: ${aTs.path}` 151 ); 152 } 153 }); 154 155 describe("with external projects and config files ", () => { 156 const projectFileName = `${tscWatch.projects}/a/project.csproj`; 157 158 function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { 159 const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); 160 service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); 161 service.openExternalProject({ 162 projectFileName, 163 rootFiles: toExternalFiles([aTs.path, configA.path]), 164 options: {} 165 } as protocol.ExternalProject); 166 checkNumberOfProjects(service, { configuredProjects: 1 }); 167 return { session, service, verifyEvent, getNumberOfEvents }; 168 169 function verifyEvent() { 170 const projectA = service.configuredProjects.get(configA.path)!; 171 assert.isDefined(projectA); 172 verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); 173 } 174 } 175 176 it("when lazyConfiguredProjectsFromExternalProject is false", () => { 177 const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); 178 verifyEvent(); 179 }); 180 181 it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { 182 const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); 183 assert.equal(getNumberOfEvents(), 0); 184 185 openFilesForSession([aTs], session); 186 verifyEvent(); 187 }); 188 189 it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { 190 const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); 191 assert.equal(getNumberOfEvents(), 0); 192 193 service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); 194 verifyEvent(); 195 }); 196 }); 197 } 198 199 describe("when using event handler", () => { 200 verifyProjectLoadingStartAndFinish(host => { 201 const { session, events } = createSessionWithEventTracking<server.ProjectLoadingStartEvent | server.ProjectLoadingFinishEvent>(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); 202 return { 203 session, 204 getNumberOfEvents: () => events.length, 205 clearEvents: () => events.length = 0, 206 verifyProjectLoadEvents: expected => assert.deepEqual(events, expected) 207 }; 208 }); 209 }); 210 211 describe("when using default event handler", () => { 212 verifyProjectLoadingStartAndFinish(host => { 213 const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler<protocol.ProjectLoadingStartEvent | protocol.ProjectLoadingFinishEvent>(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); 214 return { 215 session, 216 getNumberOfEvents: () => getEvents().length, 217 clearEvents, 218 verifyProjectLoadEvents 219 }; 220 221 function verifyProjectLoadEvents(expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) { 222 const actual = getEvents().map(e => ({ eventName: e.event, data: e.body })); 223 const mappedExpected = expected.map(e => { 224 const { project, ...rest } = e.data; 225 return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } }; 226 }); 227 assert.deepEqual(actual, mappedExpected); 228 } 229 }); 230 }); 231 }); 232} 233