• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.tscWatch {
2    describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => {
3        const config: File = {
4            path: `${projectRoot}/tsconfig.json`,
5            content: `{}`
6        };
7        interface VerifyEmitAndErrorUpdatesWorker extends VerifyEmitAndErrorUpdates {
8            configFile: () => File;
9        }
10        function verifyEmitAndErrorUpdatesWorker({
11            subScenario,
12            files,
13            currentDirectory,
14            lib,
15            configFile,
16            changes,
17            baselineIncremental
18        }: VerifyEmitAndErrorUpdatesWorker) {
19            verifyTscWatch({
20                scenario: "emitAndErrorUpdates",
21                subScenario,
22                commandLineArgs: ["--w"],
23                sys: () => createWatchedSystem(
24                    [...files(), configFile(), lib?.() || libFile],
25                    { currentDirectory: currentDirectory || projectRoot }
26                ),
27                changes,
28                baselineIncremental
29            });
30        }
31
32        function changeCompilerOptions(input: VerifyEmitAndErrorUpdates, additionalOptions: CompilerOptions): File {
33            const configFile = input.configFile?.() || config;
34            const content = JSON.parse(configFile.content);
35            content.compilerOptions = { ...content.compilerOptions, ...additionalOptions };
36            return { path: configFile.path, content: JSON.stringify(content) };
37        }
38
39        interface VerifyEmitAndErrorUpdates {
40            subScenario: string
41            files: () => File[];
42            currentDirectory?: string;
43            lib?: () => File;
44            changes: TscWatchCompileChange[];
45            configFile?: () => File;
46            baselineIncremental?: boolean
47        }
48        function verifyEmitAndErrorUpdates(input: VerifyEmitAndErrorUpdates) {
49            verifyEmitAndErrorUpdatesWorker({
50                ...input,
51                subScenario: `default/${input.subScenario}`,
52                configFile: () => input.configFile?.() || config
53            });
54
55            verifyEmitAndErrorUpdatesWorker({
56                ...input,
57                subScenario: `defaultAndD/${input.subScenario}`,
58                configFile: () => changeCompilerOptions(input, { declaration: true })
59            });
60
61            verifyEmitAndErrorUpdatesWorker({
62                ...input,
63                subScenario: `isolatedModules/${input.subScenario}`,
64                configFile: () => changeCompilerOptions(input, { isolatedModules: true })
65            });
66
67            verifyEmitAndErrorUpdatesWorker({
68                ...input,
69                subScenario: `isolatedModulesAndD/${input.subScenario}`,
70                configFile: () => changeCompilerOptions(input, { isolatedModules: true, declaration: true })
71            });
72
73            verifyEmitAndErrorUpdatesWorker({
74                ...input,
75                subScenario: `assumeChangesOnlyAffectDirectDependencies/${input.subScenario}`,
76                configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true })
77            });
78
79            verifyEmitAndErrorUpdatesWorker({
80                ...input,
81                subScenario: `assumeChangesOnlyAffectDirectDependenciesAndD/${input.subScenario}`,
82                configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true, declaration: true })
83            });
84        }
85
86        describe("deep import changes", () => {
87            const aFile: File = {
88                path: `${projectRoot}/a.ts`,
89                content: `import {B} from './b';
90declare var console: any;
91let b = new B();
92console.log(b.c.d);`
93            };
94
95            function verifyDeepImportChange(subScenario: string, bFile: File, cFile: File) {
96                verifyEmitAndErrorUpdates({
97                    subScenario: `deepImportChanges/${subScenario}`,
98                    files: () => [aFile, bFile, cFile],
99                    changes: [
100                        {
101                            caption: "Rename property d to d2 of class C",
102                            change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")),
103                            timeouts: runQueuedTimeoutCallbacks,
104                        }
105                    ],
106                });
107            }
108
109            describe("updates errors when deep import file changes", () => {
110                const bFile: File = {
111                    path: `${projectRoot}/b.ts`,
112                    content: `import {C} from './c';
113export class B
114{
115    c = new C();
116}`
117                };
118                const cFile: File = {
119                    path: `${projectRoot}/c.ts`,
120                    content: `export class C
121{
122    d = 1;
123}`
124                };
125                verifyDeepImportChange(
126                    "updates errors when deep import file changes",
127                    bFile,
128                    cFile
129                );
130            });
131
132            describe("updates errors when deep import through declaration file changes", () => {
133                const bFile: File = {
134                    path: `${projectRoot}/b.d.ts`,
135                    content: `import {C} from './c';
136export class B
137{
138    c: C;
139}`
140                };
141                const cFile: File = {
142                    path: `${projectRoot}/c.d.ts`,
143                    content: `export class C
144{
145    d: number;
146}`
147                };
148                verifyDeepImportChange(
149                    "updates errors when deep import through declaration file changes",
150                    bFile,
151                    cFile
152                );
153            });
154        });
155
156        describe("updates errors in file not exporting a deep multilevel import that changes", () => {
157            const aFile: File = {
158                path: `${projectRoot}/a.ts`,
159                content: `export interface Point {
160    name: string;
161    c: Coords;
162}
163export interface Coords {
164    x2: number;
165    y: number;
166}`
167            };
168            const bFile: File = {
169                path: `${projectRoot}/b.ts`,
170                content: `import { Point } from "./a";
171export interface PointWrapper extends Point {
172}`
173            };
174            const cFile: File = {
175                path: `${projectRoot}/c.ts`,
176                content: `import { PointWrapper } from "./b";
177export function getPoint(): PointWrapper {
178    return {
179        name: "test",
180        c: {
181            x: 1,
182            y: 2
183        }
184    }
185};`
186            };
187            const dFile: File = {
188                path: `${projectRoot}/d.ts`,
189                content: `import { getPoint } from "./c";
190getPoint().c.x;`
191            };
192            const eFile: File = {
193                path: `${projectRoot}/e.ts`,
194                content: `import "./d";`
195            };
196            verifyEmitAndErrorUpdates({
197                subScenario: "file not exporting a deep multilevel import that changes",
198                files: () => [aFile, bFile, cFile, dFile, eFile],
199                changes: [
200                    {
201                        caption: "Rename property x2 to x of interface Coords",
202                        change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")),
203                        timeouts: runQueuedTimeoutCallbacks,
204                    }
205                ]
206            });
207        });
208
209        describe("updates errors when file transitively exported file changes", () => {
210            const config: File = {
211                path: `${projectRoot}/tsconfig.json`,
212                content: JSON.stringify({
213                    files: ["app.ts"],
214                    compilerOptions: { baseUrl: "." }
215                })
216            };
217            const app: File = {
218                path: `${projectRoot}/app.ts`,
219                content: `import { Data } from "lib2/public";
220export class App {
221    public constructor() {
222        new Data().test();
223    }
224}`
225            };
226            const lib2Public: File = {
227                path: `${projectRoot}/lib2/public.ts`,
228                content: `export * from "./data";`
229            };
230            const lib2Data: File = {
231                path: `${projectRoot}/lib2/data.ts`,
232                content: `import { ITest } from "lib1/public";
233export class Data {
234    public test() {
235        const result: ITest = {
236            title: "title"
237        }
238        return result;
239    }
240}`
241            };
242            const lib1Public: File = {
243                path: `${projectRoot}/lib1/public.ts`,
244                content: `export * from "./tools/public";`
245            };
246            const lib1ToolsPublic: File = {
247                path: `${projectRoot}/lib1/tools/public.ts`,
248                content: `export * from "./tools.interface";`
249            };
250            const lib1ToolsInterface: File = {
251                path: `${projectRoot}/lib1/tools/tools.interface.ts`,
252                content: `export interface ITest {
253    title: string;
254}`
255            };
256
257            function verifyTransitiveExports(subScenario: string, files: readonly File[]) {
258                verifyEmitAndErrorUpdates({
259                    subScenario: `updates errors when file transitively exported file changes/${subScenario}`,
260                    files: () => [lib1ToolsInterface, lib1ToolsPublic, app, lib2Public, lib1Public, ...files],
261                    configFile: () => config,
262                    changes: [
263                        {
264                            caption: "Rename property title to title2 of interface ITest",
265                            change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")),
266                            timeouts: runQueuedTimeoutCallbacks,
267                        }
268                    ]
269                });
270            }
271            describe("when there are no circular import and exports", () => {
272                verifyTransitiveExports(
273                    "when there are no circular import and exports",
274                    [lib2Data]
275                );
276            });
277
278            describe("when there are circular import and exports", () => {
279                const lib2Data: File = {
280                    path: `${projectRoot}/lib2/data.ts`,
281                    content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2";
282export class Data {
283    public dat?: Data2; public test() {
284        const result: ITest = {
285            title: "title"
286        }
287        return result;
288    }
289}`
290                };
291                const lib2Data2: File = {
292                    path: `${projectRoot}/lib2/data2.ts`,
293                    content: `import { Data } from "./data";
294export class Data2 {
295    public dat?: Data;
296}`
297                };
298                verifyTransitiveExports(
299                    "when there are circular import and exports",
300                    [lib2Data, lib2Data2]
301                );
302            });
303        });
304
305        describe("with noEmitOnError", () => {
306            function change(caption: string, content: string): TscWatchCompileChange {
307                return {
308                    caption,
309                    change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content),
310                    // build project
311                    timeouts: checkSingleTimeoutQueueLengthAndRun
312                };
313            }
314            const noChange: TscWatchCompileChange = {
315                caption: "No change",
316                change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!),
317                // build project
318                timeouts: checkSingleTimeoutQueueLengthAndRun,
319            };
320            verifyEmitAndErrorUpdates({
321                subScenario: "with noEmitOnError",
322                currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError`,
323                files: () => ["shared/types/db.ts", "src/main.ts", "src/other.ts"]
324                    .map(f => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)),
325                lib: () => ({ path: libFile.path, content: libContent }),
326                configFile: () => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", "tsconfig.json"),
327                changes: [
328                    noChange,
329                    change("Fix Syntax error", `import { A } from "../shared/types/db";
330const a = {
331    lastName: 'sdsd'
332};`),
333                    change("Semantic Error", `import { A } from "../shared/types/db";
334const a: string = 10;`),
335                    noChange,
336                    change("Fix Semantic Error", `import { A } from "../shared/types/db";
337const a: string = "hello";`),
338                    noChange,
339                ],
340                baselineIncremental: true
341            });
342        });
343    });
344}
345