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