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