• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.tscWatch {
2    import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
3    describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => {
4        const scenario = "watchEnvironment";
5        verifyTscWatch({
6            scenario,
7            subScenario: "watchFile/using dynamic priority polling",
8            commandLineArgs: ["--w", `/a/username/project/typescript.ts`],
9            sys: () => {
10                const projectFolder = "/a/username/project";
11                const file1: File = {
12                    path: `${projectFolder}/typescript.ts`,
13                    content: "var z = 10;"
14                };
15                const environmentVariables = new Map<string, string>();
16                environmentVariables.set("TSC_WATCHFILE", TestFSWithWatch.Tsc_WatchFile.DynamicPolling);
17                return createWatchedSystem([file1, libFile], { environmentVariables });
18            },
19            changes: [
20                {
21                    caption: "Time spent to Transition libFile and file1 to low priority queue",
22                    change: noop,
23                    timeouts: (sys, programs) => {
24                        const initialProgram = programs[0][0];
25                        const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium];
26                        for (let index = 0; index < mediumPollingIntervalThreshold; index++) {
27                            // Transition libFile and file1 to low priority queue
28                            sys.checkTimeoutQueueLengthAndRun(1);
29                            assert.deepEqual(programs[0][0], initialProgram);
30                        }
31                        return;
32                    },
33                },
34                {
35                    caption: "Make change to file",
36                    // Make a change to file
37                    change: sys => sys.writeFile("/a/username/project/typescript.ts", "var zz30 = 100;"),
38                    // During this timeout the file would be detected as unchanged
39                    timeouts: checkSingleTimeoutQueueLengthAndRun,
40                },
41                {
42                    caption: "Callbacks: medium priority + high priority queue and scheduled program update",
43                    change: noop,
44                    // Callbacks: medium priority + high priority queue and scheduled program update
45                    // This should detect change in the file
46                    timeouts: sys => sys.checkTimeoutQueueLengthAndRun(3),
47                },
48                {
49                    caption: "Polling queues polled and everything is in the high polling queue",
50                    change: noop,
51                    timeouts: (sys, programs) => {
52                        const initialProgram = programs[0][0];
53                        const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium];
54                        const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold;
55                        for (let fileUnchangeDetected = 1; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) {
56                            // For high + Medium/low polling interval
57                            sys.checkTimeoutQueueLengthAndRun(2);
58                            assert.deepEqual(programs[0][0], initialProgram);
59                        }
60
61                        // Everything goes in high polling interval queue
62                        sys.checkTimeoutQueueLengthAndRun(1);
63                        return;
64                    },
65                }
66            ]
67        });
68
69        describe("tsc-watch when watchDirectories implementation", () => {
70            function verifyRenamingFileInSubFolder(subScenario: string, tscWatchDirectory: Tsc_WatchDirectory) {
71                const projectFolder = "/a/username/project";
72                const projectSrcFolder = `${projectFolder}/src`;
73                const configFile: File = {
74                    path: `${projectFolder}/tsconfig.json`,
75                    content: JSON.stringify({
76                        watchOptions: {
77                            synchronousWatchDirectory: true
78                        }
79                    })
80                };
81                const file: File = {
82                    path: `${projectSrcFolder}/file1.ts`,
83                    content: ""
84                };
85                verifyTscWatch({
86                    scenario,
87                    subScenario: `watchDirectories/${subScenario}`,
88                    commandLineArgs: ["--w", "-p", configFile.path],
89                    sys: () => {
90                        const files = [file, configFile, libFile];
91                        const environmentVariables = new Map<string, string>();
92                        environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory);
93                        return createWatchedSystem(files, { environmentVariables });
94                    },
95                    changes: [
96                        {
97                            caption: "Rename file1 to file2",
98                            // Rename the file:
99                            change: sys => sys.renameFile(file.path, file.path.replace("file1.ts", "file2.ts")),
100                            timeouts: sys => {
101                                if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) {
102                                    // With dynamic polling the fs change would be detected only by running timeouts
103                                    sys.runQueuedTimeoutCallbacks();
104                                }
105                                // Delayed update program
106                                sys.runQueuedTimeoutCallbacks();
107                                return;
108                            },
109                        },
110                    ],
111                });
112            }
113
114            verifyRenamingFileInSubFolder("uses watchFile when renaming file in subfolder", Tsc_WatchDirectory.WatchFile);
115
116            verifyRenamingFileInSubFolder("uses non recursive watchDirectory when renaming file in subfolder", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
117
118            verifyRenamingFileInSubFolder("uses non recursive dynamic polling when renaming file in subfolder", Tsc_WatchDirectory.DynamicPolling);
119
120            verifyTscWatch({
121                scenario,
122                subScenario: "watchDirectories/when there are symlinks to folders in recursive folders",
123                commandLineArgs: ["--w"],
124                sys: () => {
125                    const cwd = "/home/user/projects/myproject";
126                    const file1: File = {
127                        path: `${cwd}/src/file.ts`,
128                        content: `import * as a from "a"`
129                    };
130                    const tsconfig: File = {
131                        path: `${cwd}/tsconfig.json`,
132                        content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}`
133                    };
134                    const realA: File = {
135                        path: `${cwd}/node_modules/reala/index.d.ts`,
136                        content: `export {}`
137                    };
138                    const realB: File = {
139                        path: `${cwd}/node_modules/realb/index.d.ts`,
140                        content: `export {}`
141                    };
142                    const symLinkA: SymLink = {
143                        path: `${cwd}/node_modules/a`,
144                        symLink: `${cwd}/node_modules/reala`
145                    };
146                    const symLinkB: SymLink = {
147                        path: `${cwd}/node_modules/b`,
148                        symLink: `${cwd}/node_modules/realb`
149                    };
150                    const symLinkBInA: SymLink = {
151                        path: `${cwd}/node_modules/reala/node_modules/b`,
152                        symLink: `${cwd}/node_modules/b`
153                    };
154                    const symLinkAInB: SymLink = {
155                        path: `${cwd}/node_modules/realb/node_modules/a`,
156                        symLink: `${cwd}/node_modules/a`
157                    };
158                    const files = [libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB];
159                    const environmentVariables = new Map<string, string>();
160                    environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
161                    return createWatchedSystem(files, { environmentVariables, currentDirectory: cwd });
162                },
163                changes: emptyArray
164            });
165
166            verifyTscWatch({
167                scenario,
168                subScenario: "watchDirectories/with non synchronous watch directory",
169                commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`],
170                sys: () => {
171                    const configFile: File = {
172                        path: `${projectRoot}/tsconfig.json`,
173                        content: "{}"
174                    };
175                    const file1: File = {
176                        path: `${projectRoot}/src/file1.ts`,
177                        content: `import { x } from "file2";`
178                    };
179                    const file2: File = {
180                        path: `${projectRoot}/node_modules/file2/index.d.ts`,
181                        content: `export const x = 10;`
182                    };
183                    const files = [libFile, file1, file2, configFile];
184                    return createWatchedSystem(files, { runWithoutRecursiveWatches: true });
185                },
186                changes: [
187                    {
188                        caption: "Directory watch updates because of file1.js creation",
189                        change: noop,
190                        timeouts: sys => {
191                            sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for file1.js output
192                            sys.checkTimeoutQueueLength(0);
193                        },
194                    },
195                    {
196                        caption: "Remove directory node_modules",
197                        // Remove directory node_modules
198                        change: sys => sys.deleteFolder(`${projectRoot}/node_modules`, /*recursive*/ true),
199                        timeouts: sys => {
200                            sys.checkTimeoutQueueLength(3); // 1. Failed lookup invalidation 2. For updating program and 3. for updating child watches
201                            sys.runQueuedTimeoutCallbacks(sys.getNextTimeoutId() - 2); // Update program
202                        },
203                    },
204                    {
205                        caption: "Pending directory watchers and program update",
206                        change: noop,
207                        timeouts: sys => {
208                            sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers
209                            sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update
210                            sys.checkTimeoutQueueLengthAndRun(1); // Actual program update
211                            sys.checkTimeoutQueueLength(0);
212                        },
213                    },
214                    {
215                        caption: "Start npm install",
216                        // npm install
217                        change: sys => sys.createDirectory(`${projectRoot}/node_modules`),
218                        timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure
219                    },
220                    {
221                        caption: "npm install folder creation of file2",
222                        change: sys => sys.createDirectory(`${projectRoot}/node_modules/file2`),
223                        timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure
224                    },
225                    {
226                        caption: "npm install index file in file2",
227                        change: sys => sys.writeFile(`${projectRoot}/node_modules/file2/index.d.ts`, `export const x = 10;`),
228                        timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure
229                    },
230                    {
231                        caption: "Updates the program",
232                        change: noop,
233                        timeouts: sys => {
234                            sys.runQueuedTimeoutCallbacks();
235                            sys.checkTimeoutQueueLength(2); // To Update program and failed lookup update
236                        },
237                    },
238                    {
239                        caption: "Invalidates module resolution cache",
240                        change: noop,
241                        timeouts: sys => {
242                            sys.runQueuedTimeoutCallbacks();
243                            sys.checkTimeoutQueueLength(1); // To Update program
244                        },
245                    },
246                    {
247                        caption: "Pending updates",
248                        change: noop,
249                        timeouts: sys => {
250                            sys.runQueuedTimeoutCallbacks();
251                            sys.checkTimeoutQueueLength(0);
252                        },
253                    },
254                ],
255            });
256
257            verifyTscWatch({
258                scenario,
259                subScenario: "watchDirectories/with non synchronous watch directory with outDir and declaration enabled",
260                commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`],
261                sys: () => {
262                    const configFile: File = {
263                        path: `${projectRoot}/tsconfig.json`,
264                        content: JSON.stringify({ compilerOptions: { outDir: "dist", declaration: true } })
265                    };
266                    const file1: File = {
267                        path: `${projectRoot}/src/file1.ts`,
268                        content: `import { x } from "file2";`
269                    };
270                    const file2: File = {
271                        path: `${projectRoot}/node_modules/file2/index.d.ts`,
272                        content: `export const x = 10;`
273                    };
274                    const files = [libFile, file1, file2, configFile];
275                    return createWatchedSystem(files, { runWithoutRecursiveWatches: true });
276                },
277                changes: [
278                    noopChange,
279                    {
280                        caption: "Add new file, should schedule and run timeout to update directory watcher",
281                        change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`),
282                        timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch
283                    },
284                    {
285                        caption: "Actual program update to include new file",
286                        change: noop,
287                        timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // Scheduling failed lookup update and program update
288                    },
289                    {
290                        caption: "After program emit with new file, should schedule and run timeout to update directory watcher",
291                        change: noop,
292                        timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch
293                    },
294                    noopChange,
295                ],
296            });
297
298            verifyTscWatch({
299                scenario,
300                subScenario: "watchDirectories/with non synchronous watch directory renaming a file",
301                commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`],
302                sys: () => {
303                    const configFile: File = {
304                        path: `${projectRoot}/tsconfig.json`,
305                        content: JSON.stringify({ compilerOptions: { outDir: "dist" } })
306                    };
307                    const file1: File = {
308                        path: `${projectRoot}/src/file1.ts`,
309                        content: `import { x } from "./file2";`
310                    };
311                    const file2: File = {
312                        path: `${projectRoot}/src/file2.ts`,
313                        content: `export const x = 10;`
314                    };
315                    const files = [libFile, file1, file2, configFile];
316                    return createWatchedSystem(files, { runWithoutRecursiveWatches: true });
317                },
318                changes: [
319                    noopChange,
320                    {
321                        caption: "rename the file",
322                        change: sys => sys.renameFile(`${projectRoot}/src/file2.ts`, `${projectRoot}/src/renamed.ts`),
323                        timeouts: sys => {
324                            sys.checkTimeoutQueueLength(2); // 1. For updating program and 2. for updating child watches
325                            sys.runQueuedTimeoutCallbacks(1); // Update program
326                        },
327                    },
328                    {
329                        caption: "Pending directory watchers and program update",
330                        change: noop,
331                        timeouts: sys => {
332                            sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers
333                            sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update
334                            sys.checkTimeoutQueueLengthAndRun(1); // Actual program update
335                            sys.checkTimeoutQueueLength(0);
336                        },
337                    },
338                ],
339            });
340        });
341
342        describe("handles watch compiler options", () => {
343            verifyTscWatch({
344                scenario,
345                subScenario: "watchOptions/with watchFile option",
346                commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
347                sys: () => {
348                    const configFile: File = {
349                        path: "/a/b/tsconfig.json",
350                        content: JSON.stringify({
351                            watchOptions: {
352                                watchFile: "UseFsEvents"
353                            }
354                        })
355                    };
356                    const files = [libFile, commonFile1, commonFile2, configFile];
357                    return createWatchedSystem(files);
358                },
359                changes: emptyArray
360            });
361
362            verifyTscWatch({
363                scenario,
364                subScenario: "watchOptions/with watchDirectory option",
365                commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
366                sys: () => {
367                    const configFile: File = {
368                        path: "/a/b/tsconfig.json",
369                        content: JSON.stringify({
370                            watchOptions: {
371                                watchDirectory: "UseFsEvents"
372                            }
373                        })
374                    };
375                    const files = [libFile, commonFile1, commonFile2, configFile];
376                    return createWatchedSystem(files, { runWithoutRecursiveWatches: true });
377                },
378                changes: emptyArray
379            });
380
381            verifyTscWatch({
382                scenario,
383                subScenario: "watchOptions/with fallbackPolling option",
384                commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"],
385                sys: () => {
386                    const configFile: File = {
387                        path: "/a/b/tsconfig.json",
388                        content: JSON.stringify({
389                            watchOptions: {
390                                fallbackPolling: "PriorityInterval"
391                            }
392                        })
393                    };
394                    const files = [libFile, commonFile1, commonFile2, configFile];
395                    return createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
396                },
397                changes: emptyArray
398            });
399
400            verifyTscWatch({
401                scenario,
402                subScenario: "watchOptions/with watchFile as watch options to extend",
403                commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"],
404                sys: () => {
405                    const configFile: File = {
406                        path: "/a/b/tsconfig.json",
407                        content: "{}"
408                    };
409                    const files = [libFile, commonFile1, commonFile2, configFile];
410                    return createWatchedSystem(files);
411                },
412                changes: emptyArray
413            });
414
415            describe("exclude options", () => {
416                function sys(watchOptions: WatchOptions, runWithoutRecursiveWatches?: boolean): WatchedSystem {
417                    const configFile: File = {
418                        path: `${projectRoot}/tsconfig.json`,
419                        content: JSON.stringify({ exclude: ["node_modules"], watchOptions })
420                    };
421                    const main: File = {
422                        path: `${projectRoot}/src/main.ts`,
423                        content: `import { foo } from "bar"; foo();`
424                    };
425                    const bar: File = {
426                        path: `${projectRoot}/node_modules/bar/index.d.ts`,
427                        content: `export { foo } from "./foo";`
428                    };
429                    const foo: File = {
430                        path: `${projectRoot}/node_modules/bar/foo.d.ts`,
431                        content: `export function foo(): string;`
432                    };
433                    const fooBar: File = {
434                        path: `${projectRoot}/node_modules/bar/fooBar.d.ts`,
435                        content: `export function fooBar(): string;`
436                    };
437                    const temp: File = {
438                        path: `${projectRoot}/node_modules/bar/temp/index.d.ts`,
439                        content: "export function temp(): string;"
440                    };
441                    const files = [libFile, main, bar, foo, fooBar, temp, configFile];
442                    return createWatchedSystem(files, { currentDirectory: projectRoot, runWithoutRecursiveWatches });
443                }
444
445                function verifyWorker(...additionalFlags: string[]) {
446                    verifyTscWatch({
447                        scenario,
448                        subScenario: `watchOptions/with excludeFiles option${additionalFlags.join("")}`,
449                        commandLineArgs: ["-w", ...additionalFlags],
450                        sys: () => sys({ excludeFiles: ["node_modules/*"] }),
451                        changes: [
452                            {
453                                caption: "Change foo",
454                                change: sys => replaceFileText(sys, `${projectRoot}/node_modules/bar/foo.d.ts`, "foo", "fooBar"),
455                                timeouts: sys => sys.checkTimeoutQueueLength(0),
456                            }
457                        ]
458                    });
459
460                    verifyTscWatch({
461                        scenario,
462                        subScenario: `watchOptions/with excludeDirectories option${additionalFlags.join("")}`,
463                        commandLineArgs: ["-w", ...additionalFlags],
464                        sys: () => sys({ excludeDirectories: ["node_modules"] }),
465                        changes: [
466                            {
467                                caption: "delete fooBar",
468                                change: sys => sys.deleteFile(`${projectRoot}/node_modules/bar/fooBar.d.ts`),
469                                timeouts: sys => sys.checkTimeoutQueueLength(0),                            }
470                        ]
471                    });
472
473                    verifyTscWatch({
474                        scenario,
475                        subScenario: `watchOptions/with excludeDirectories option with recursive directory watching${additionalFlags.join("")}`,
476                        commandLineArgs: ["-w", ...additionalFlags],
477                        sys: () => sys({ excludeDirectories: ["**/temp"] }, /*runWithoutRecursiveWatches*/ true),
478                        changes: [
479                            {
480                                caption: "Directory watch updates because of main.js creation",
481                                change: noop,
482                                timeouts: sys => {
483                                    sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for main.js output
484                                    sys.checkTimeoutQueueLength(0);
485                                },
486                            },
487                            {
488                                caption: "add new folder to temp",
489                                change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }),
490                                timeouts: sys => sys.checkTimeoutQueueLength(0),
491                            }
492                        ]
493                    });
494                }
495
496                verifyWorker();
497                verifyWorker("-extendedDiagnostics");
498            });
499        });
500    });
501}
502