• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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