• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.tscWatch {
2    const scenario = "emit";
3    describe("unittests:: tsc-watch:: emit with outFile or out setting", () => {
4        function verifyOutAndOutFileSetting(subScenario: string, out?: string, outFile?: string) {
5            verifyTscWatch({
6                scenario,
7                subScenario: `emit with outFile or out setting/${subScenario}`,
8                commandLineArgs: ["--w", "-p", "/a/tsconfig.json"],
9                sys: () => createWatchedSystem({
10                    "/a/a.ts": "let x = 1",
11                    "/a/b.ts": "let y = 1",
12                    "/a/tsconfig.json": JSON.stringify({ compilerOptions: { out, outFile } }),
13                    [libFile.path]: libFile.content,
14                }),
15                changes: [
16                    {
17                        caption: "Make change in the file",
18                        change: sys => sys.writeFile("/a/a.ts", "let x = 11"),
19                        timeouts: runQueuedTimeoutCallbacks
20                    },
21                    {
22                        caption: "Make change in the file again",
23                        change: sys => sys.writeFile("/a/a.ts", "let xy = 11"),
24                        timeouts: runQueuedTimeoutCallbacks
25                    }
26                ]
27            });
28        }
29        verifyOutAndOutFileSetting("config does not have out or outFile");
30        verifyOutAndOutFileSetting("config has out", "/a/out.js");
31        verifyOutAndOutFileSetting("config has outFile", /*out*/ undefined, "/a/out.js");
32
33        function verifyFilesEmittedOnce(subScenario: string, useOutFile: boolean) {
34            verifyTscWatch({
35                scenario,
36                subScenario: `emit with outFile or out setting/${subScenario}`,
37                commandLineArgs: ["--w", "-p", "/a/b/project/tsconfig.json"],
38                sys: () => {
39                    const file1: File = {
40                        path: "/a/b/output/AnotherDependency/file1.d.ts",
41                        content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0,  Min = 1, Average = 2, } }"
42                    };
43                    const file2: File = {
44                        path: "/a/b/dependencies/file2.d.ts",
45                        content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }"
46                    };
47                    const file3: File = {
48                        path: "/a/b/project/src/main.ts",
49                        content: "namespace Main { export function fooBar() {} }"
50                    };
51                    const file4: File = {
52                        path: "/a/b/project/src/main2.ts",
53                        content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) {  } }"
54                    };
55                    const configFile: File = {
56                        path: "/a/b/project/tsconfig.json",
57                        content: JSON.stringify({
58                            compilerOptions: useOutFile ?
59                                { outFile: "../output/common.js", target: "es5" } :
60                                { outDir: "../output", target: "es5" },
61                            files: [file1.path, file2.path, file3.path, file4.path]
62                        })
63                    };
64                    return createWatchedSystem([file1, file2, file3, file4, libFile, configFile]);
65                },
66                changes: emptyArray
67            });
68        }
69        verifyFilesEmittedOnce("with --outFile and multiple declaration files in the program", /*useOutFile*/ true);
70        verifyFilesEmittedOnce("without --outFile and multiple declaration files in the program", /*useOutFile*/ false);
71    });
72
73    describe("unittests:: tsc-watch:: emit for configured projects", () => {
74        const file1Consumer1Path = "/a/b/file1Consumer1.ts";
75        const file1Consumer2Path = "/a/b/file1Consumer2.ts";
76        const moduleFile1Path = "/a/b/moduleFile1.ts";
77        const moduleFile2Path = "/a/b/moduleFile2.ts";
78        const globalFilePath = "/a/b/globalFile3.ts";
79        const configFilePath = "/a/b/tsconfig.json";
80        interface VerifyTscWatchEmit {
81            subScenario: string;
82            /** custom config file options */
83            configObj?: any;
84            /** Additional files and folders to add */
85            getAdditionalFileOrFolder?: () => File[];
86            /** initial list of files to emit if not the default list */
87            firstReloadFileList?: string[];
88            changes: TscWatchCompileChange[]
89        }
90        function verifyTscWatchEmit({
91            subScenario,
92            configObj,
93            getAdditionalFileOrFolder,
94            firstReloadFileList,
95            changes
96        }: VerifyTscWatchEmit) {
97            verifyTscWatch({
98                scenario,
99                subScenario: `emit for configured projects/${subScenario}`,
100                commandLineArgs: ["--w", "-p", configFilePath],
101                sys: () => {
102                    const moduleFile1: File = {
103                        path: moduleFile1Path,
104                        content: "export function Foo() { };",
105                    };
106
107                    const file1Consumer1: File = {
108                        path: file1Consumer1Path,
109                        content: `import {Foo} from "./moduleFile1"; export var y = 10;`,
110                    };
111
112                    const file1Consumer2: File = {
113                        path: file1Consumer2Path,
114                        content: `import {Foo} from "./moduleFile1"; let z = 10;`,
115                    };
116
117                    const moduleFile2: File = {
118                        path: moduleFile2Path,
119                        content: `export var Foo4 = 10;`,
120                    };
121
122                    const globalFile3: File = {
123                        path: globalFilePath,
124                        content: `interface GlobalFoo { age: number }`
125                    };
126                    const configFile: File = {
127                        path: configFilePath,
128                        content: JSON.stringify(configObj || {})
129                    };
130                    const additionalFiles = getAdditionalFileOrFolder?.() || emptyArray;
131                    const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles];
132                    return createWatchedSystem(firstReloadFileList ?
133                        map(firstReloadFileList, fileName => find(files, file => file.path === fileName)!) :
134                        files
135                    );
136                },
137                changes
138            });
139        }
140
141        function modifyModuleFile1Shape(sys: WatchedSystem) {
142            sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { };`);
143        }
144        const changeModuleFile1Shape: TscWatchCompileChange = {
145            caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { };`",
146            change: modifyModuleFile1Shape,
147            timeouts: checkSingleTimeoutQueueLengthAndRun,
148        };
149
150        verifyTscWatchEmit({
151            subScenario: "should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed",
152            changes: [
153                changeModuleFile1Shape,
154                {
155                    caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`",
156                    change: sys => sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { console.log('hi'); };`),
157                    timeouts: checkSingleTimeoutQueueLengthAndRun,
158                }
159            ]
160        });
161
162        verifyTscWatchEmit({
163            subScenario: "should be up-to-date with the reference map changes",
164            changes: [
165                {
166                    caption: "Change file1Consumer1 content to `export let y = Foo();`",
167                    change: sys => sys.writeFile(file1Consumer1Path, `export let y = Foo();`),
168                    timeouts: checkSingleTimeoutQueueLengthAndRun,
169                },
170                changeModuleFile1Shape,
171                {
172                    caption: "Add the import statements back to file1Consumer1",
173                    change: sys => sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`),
174                    timeouts: checkSingleTimeoutQueueLengthAndRun,
175                },
176                {
177                    caption: "Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`",
178                    change: sys => sys.writeFile(moduleFile1Path, `export let y = Foo();`),
179                    timeouts: checkSingleTimeoutQueueLengthAndRun,
180                },
181                {
182                    caption: "Multiple file edits in one go",
183                    // Change file1Consumer1 content to `export let y = Foo();`
184                    // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
185                    change: sys => {
186                        sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`);
187                        modifyModuleFile1Shape(sys);
188                    },
189                    timeouts: checkSingleTimeoutQueueLengthAndRun,
190                }
191            ]
192        });
193
194        verifyTscWatchEmit({
195            subScenario: "should be up-to-date with deleted files",
196            changes: [
197                {
198                    caption: "change moduleFile1 shape and delete file1Consumer2",
199                    change: sys => {
200                        modifyModuleFile1Shape(sys);
201                        sys.deleteFile(file1Consumer2Path);
202                    },
203                    timeouts: checkSingleTimeoutQueueLengthAndRun,
204                }
205            ]
206        });
207
208        verifyTscWatchEmit({
209            subScenario: "should be up-to-date with newly created files",
210            changes: [
211                {
212                    caption: "change moduleFile1 shape and create file1Consumer3",
213                    change: sys => {
214                        sys.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`);
215                        modifyModuleFile1Shape(sys);
216                    },
217                    timeouts: checkSingleTimeoutQueueLengthAndRun,
218                }
219            ]
220        });
221
222        verifyTscWatchEmit({
223            subScenario: "should detect changes in non-root files",
224            configObj: { files: [file1Consumer1Path] },
225            changes: [
226                changeModuleFile1Shape,
227                {
228                    caption: "change file1 internal, and verify only file1 is affected",
229                    change: sys => sys.appendFile(moduleFile1Path, "var T1: number;"),
230                    timeouts: checkSingleTimeoutQueueLengthAndRun,
231                }
232            ]
233        });
234
235        verifyTscWatchEmit({
236            subScenario: "should return all files if a global file changed shape",
237            changes: [
238                {
239                    caption: "change shape of global file",
240                    change: sys => sys.appendFile(globalFilePath, "var T2: string;"),
241                    timeouts: checkSingleTimeoutQueueLengthAndRun,
242                }
243            ]
244        });
245
246        verifyTscWatchEmit({
247            subScenario: "should always return the file itself if '--isolatedModules' is specified",
248            configObj: { compilerOptions: { isolatedModules: true } },
249            changes: [
250                changeModuleFile1Shape
251            ]
252        });
253
254        verifyTscWatchEmit({
255            subScenario: "should always return the file itself if '--out' or '--outFile' is specified",
256            configObj: { compilerOptions: { module: "system", outFile: "/a/b/out.js" } },
257            changes: [
258                changeModuleFile1Shape
259            ]
260        });
261
262        verifyTscWatchEmit({
263            subScenario: "should return cascaded affected file list",
264            getAdditionalFileOrFolder: () => [{
265                path: "/a/b/file1Consumer1Consumer1.ts",
266                content: `import {y} from "./file1Consumer1";`
267            }],
268            changes: [
269                {
270                    caption: "change file1Consumer1",
271                    change: sys => sys.appendFile(file1Consumer1Path, "export var T: number;"),
272                    timeouts: checkSingleTimeoutQueueLengthAndRun,
273                },
274                changeModuleFile1Shape,
275                {
276                    caption: "change file1Consumer1 and moduleFile1",
277                    change: sys => {
278                        sys.appendFile(file1Consumer1Path, "export var T2: number;");
279                        sys.writeFile(moduleFile1Path, `export var T2: number;export function Foo() { };`);
280                    },
281                    timeouts: checkSingleTimeoutQueueLengthAndRun,
282                }
283            ]
284        });
285
286        verifyTscWatchEmit({
287            subScenario: "should work fine for files with circular references",
288            getAdditionalFileOrFolder: () => [
289                {
290                    path: "/a/b/file1.ts",
291                    content: `/// <reference path="./file2.ts" />
292export var t1 = 10;`
293                },
294                {
295                    path: "/a/b/file2.ts",
296                    content: `/// <reference path="./file1.ts" />
297export var t2 = 10;`
298                }
299            ],
300            firstReloadFileList: [libFile.path, "/a/b/file1.ts", "/a/b/file2.ts", configFilePath],
301            changes: [
302                {
303                    caption: "change file1",
304                    change: sys => sys.appendFile("/a/b/file1.ts", "export var t3 = 10;"),
305                    timeouts: checkSingleTimeoutQueueLengthAndRun,
306                }
307            ]
308        });
309
310        verifyTscWatchEmit({
311            subScenario: "should detect removed code file",
312            getAdditionalFileOrFolder: () => [{
313                path: "/a/b/referenceFile1.ts",
314                content: `/// <reference path="./moduleFile1.ts" />
315export var x = Foo();`
316            }],
317            firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", moduleFile1Path, configFilePath],
318            changes: [
319                {
320                    caption: "delete moduleFile1",
321                    change: sys => sys.deleteFile(moduleFile1Path),
322                    timeouts: checkSingleTimeoutQueueLengthAndRun,
323                }
324            ]
325        });
326
327        verifyTscWatchEmit({
328            subScenario: "should detect non existing code file",
329            getAdditionalFileOrFolder: () => [{
330                path: "/a/b/referenceFile1.ts",
331                content: `/// <reference path="./moduleFile2.ts" />
332export var x = Foo();`
333            }],
334            firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", configFilePath],
335            changes: [
336                {
337                    caption: "edit refereceFile1",
338                    change: sys => sys.appendFile("/a/b/referenceFile1.ts", "export var yy = Foo();"),
339                    timeouts: checkSingleTimeoutQueueLengthAndRun,
340                },
341                {
342                    caption: "create moduleFile2",
343                    change: sys => sys.writeFile(moduleFile2Path, "export var Foo4 = 10;"),
344                    timeouts: checkSingleTimeoutQueueLengthAndRun,
345                }
346            ]
347        });
348    });
349
350    describe("unittests:: tsc-watch:: emit file content", () => {
351        function verifyNewLine(subScenario: string, newLine: string) {
352            verifyTscWatch({
353                scenario,
354                subScenario: `emit file content/${subScenario}`,
355                commandLineArgs: ["--w", "/a/app.ts"],
356                sys: () => createWatchedSystem(
357                    [
358                        {
359                            path: "/a/app.ts",
360                            content: ["var x = 1;", "var y = 2;"].join(newLine)
361                        },
362                        libFile
363                    ],
364                    { newLine }
365                ),
366                changes: [
367                    {
368                        caption: "Append a line",
369                        change: sys => sys.appendFile("/a/app.ts", newLine + "var z = 3;"),
370                        timeouts: checkSingleTimeoutQueueLengthAndRun,
371                    }
372                ],
373            });
374        }
375        verifyNewLine("handles new lines lineFeed", "\n");
376        verifyNewLine("handles new lines carriageReturn lineFeed", "\r\n");
377
378        verifyTscWatch({
379            scenario,
380            subScenario: "emit file content/should emit specified file",
381            commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
382            sys: () => {
383                const file1 = {
384                    path: "/a/b/f1.ts",
385                    content: `export function Foo() { return 10; }`
386                };
387
388                const file2 = {
389                    path: "/a/b/f2.ts",
390                    content: `import {Foo} from "./f1"; export let y = Foo();`
391                };
392
393                const file3 = {
394                    path: "/a/b/f3.ts",
395                    content: `import {y} from "./f2"; let x = y;`
396                };
397
398                const configFile = {
399                    path: "/a/b/tsconfig.json",
400                    content: "{}"
401                };
402                return createWatchedSystem([file1, file2, file3, configFile, libFile]);
403            },
404            changes: [
405                {
406                    caption: "Append content to f1",
407                    change: sys => sys.appendFile("/a/b/f1.ts", "export function foo2() { return 2; }"),
408                    timeouts: checkSingleTimeoutQueueLengthAndRun,
409                },
410                {
411                    caption: "Again Append content to f1",
412                    change: sys => sys.appendFile("/a/b/f1.ts", "export function fooN() { return 2; }"),
413                    timeouts: checkSingleTimeoutQueueLengthAndRun,
414                }
415            ],
416        });
417
418        verifyTscWatch({
419            scenario,
420            subScenario: "emit file content/elides const enums correctly in incremental compilation",
421            commandLineArgs: ["-w", "/user/someone/projects/myproject/file3.ts"],
422            sys: () => {
423                const currentDirectory = "/user/someone/projects/myproject";
424                const file1: File = {
425                    path: `${currentDirectory}/file1.ts`,
426                    content: "export const enum E1 { V = 1 }"
427                };
428                const file2: File = {
429                    path: `${currentDirectory}/file2.ts`,
430                    content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }`
431                };
432                const file3: File = {
433                    path: `${currentDirectory}/file3.ts`,
434                    content: `import { E2 } from "./file2"; const v: E2 = E2.V;`
435                };
436                return createWatchedSystem([file1, file2, file3, libFile]);
437            },
438            changes: [
439                {
440                    caption: "Append content to file3",
441                    change: sys => sys.appendFile("/user/someone/projects/myproject/file3.ts", "function foo2() { return 2; }"),
442                    timeouts: checkSingleTimeoutQueueLengthAndRun,
443                }
444            ],
445        });
446
447        verifyTscWatch({
448            scenario,
449            subScenario: "emit file content/file is deleted and created as part of change",
450            commandLineArgs: ["-w"],
451            sys: () => {
452                const projectLocation = "/home/username/project";
453                const file: File = {
454                    path: `${projectLocation}/app/file.ts`,
455                    content: "var a = 10;"
456                };
457                const configFile: File = {
458                    path: `${projectLocation}/tsconfig.json`,
459                    content: JSON.stringify({
460                        include: [
461                            "app/**/*.ts"
462                        ]
463                    })
464                };
465                const files = [file, configFile, libFile];
466                return createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true });
467            },
468            changes: [
469                {
470                    caption: "file is deleted and then created to modify content",
471                    change: sys => sys.appendFile("/home/username/project/app/file.ts", "\nvar b = 10;", { invokeFileDeleteCreateAsPartInsteadOfChange: true }),
472                    timeouts: checkSingleTimeoutQueueLengthAndRun,
473                }
474            ]
475        });
476    });
477
478    describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => {
479        verifyTscWatch({
480            scenario,
481            subScenario: "when module emit is specified as node/when instead of filechanged recursive directory watcher is invoked",
482            commandLineArgs: ["--w", "--p", "/a/rootFolder/project/tsconfig.json"],
483            sys: () => {
484                const configFile: File = {
485                    path: "/a/rootFolder/project/tsconfig.json",
486                    content: JSON.stringify({
487                        compilerOptions: {
488                            module: "none",
489                            allowJs: true,
490                            outDir: "Static/scripts/"
491                        },
492                        include: [
493                            "Scripts/**/*"
494                        ],
495                    })
496                };
497                const file1: File = {
498                    path: "/a/rootFolder/project/Scripts/TypeScript.ts",
499                    content: "var z = 10;"
500                };
501                const file2: File = {
502                    path: "/a/rootFolder/project/Scripts/Javascript.js",
503                    content: "var zz = 10;"
504                };
505                return createWatchedSystem([configFile, file1, file2, libFile]);
506            },
507            changes: [
508                {
509                    caption: "Modify typescript file",
510                    change: sys => sys.modifyFile(
511                        "/a/rootFolder/project/Scripts/TypeScript.ts",
512                        "var zz30 = 100;",
513                        { invokeDirectoryWatcherInsteadOfFileChanged: true },
514                    ),
515                    timeouts: runQueuedTimeoutCallbacks,
516                }
517            ],
518        });
519    });
520}
521