• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import * as ts from "../../_namespaces/ts";
2
3import projectsLocation = ts.TestFSWithWatch.tsbuildProjectsLocation;
4describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => {
5    const enum SubProject {
6        core = "core",
7        logic = "logic",
8        tests = "tests",
9        ui = "ui"
10    }
11    type ReadonlyFile = Readonly<ts.tscWatch.File>;
12    /** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */
13    type SubProjectFiles = [tsconfig: ReadonlyFile, index: ReadonlyFile] | [tsconfig: ReadonlyFile, index: ReadonlyFile, anotherModule: ReadonlyFile, someDecl: ReadonlyFile];
14    function projectFilePath(subProject: SubProject, baseFileName: string) {
15        return `${ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", subProject)}/${baseFileName.toLowerCase()}`;
16    }
17
18    function projectFile(subProject: SubProject, baseFileName: string): ts.tscWatch.File {
19        return ts.TestFSWithWatch.getTsBuildProjectFile("sample1", `${subProject}/${baseFileName}`);
20    }
21
22    function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles {
23        const tsconfig = projectFile(subProject, "tsconfig.json");
24        const index = projectFile(subProject, "index.ts");
25        if (!anotherModuleAndSomeDecl) {
26            return [tsconfig, index];
27        }
28        const anotherModule = projectFile(SubProject.core, "anotherModule.ts");
29        const someDecl = projectFile(SubProject.core, "some_decl.ts");
30        return [tsconfig, index, anotherModule, someDecl];
31    }
32
33    function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): ts.tscWatch.TscWatchCompileChange {
34        return {
35            caption,
36            change: sys => sys.writeFile(ts.isString(fileName) ? fileName : fileName(), ts.isString(content) ? content : content()),
37            timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds core
38        };
39    }
40
41    function changeCore(content: () => string, caption: string) {
42        return changeFile(() => core[1].path, content, caption);
43    }
44
45    let core: SubProjectFiles;
46    let logic: SubProjectFiles;
47    let tests: SubProjectFiles;
48    let ui: SubProjectFiles;
49    let allFiles: readonly ts.tscWatch.File[];
50
51    before(() => {
52        core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true);
53        logic = subProjectFiles(SubProject.logic);
54        tests = subProjectFiles(SubProject.tests);
55        ui = subProjectFiles(SubProject.ui);
56        allFiles = [ts.tscWatch.libFile, ...core, ...logic, ...tests, ...ui];
57    });
58
59    after(() => {
60        core = undefined!;
61        logic = undefined!;
62        tests = undefined!;
63        ui = undefined!;
64        allFiles = undefined!;
65    });
66
67    ts.tscWatch.verifyTscWatch({
68        scenario: "programUpdates",
69        subScenario: "creates solution in watch mode",
70        commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
71        sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
72        changes: ts.emptyArray
73    });
74
75    it("verify building references watches only those projects", () => {
76        const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }));
77        const host = ts.tscWatch.createSolutionBuilderWithWatchHostForBaseline(sys, cb);
78        const solutionBuilder = ts.createSolutionBuilderWithWatch(host, [`sample1/${SubProject.tests}`], { watch: true });
79        solutionBuilder.buildReferences(`sample1/${SubProject.tests}`);
80        ts.tscWatch.runWatchBaseline({
81            scenario: "programUpdates",
82            subScenario: "verify building references watches only those projects",
83            commandLineArgs: ["--b", "--w"],
84            sys,
85            baseline,
86            oldSnap,
87            getPrograms,
88            changes: ts.emptyArray,
89            watchOrSolution: solutionBuilder
90        });
91    });
92
93    describe("validates the changes and watched files", () => {
94        const newFileWithoutExtension = "newFile";
95        const newFile: ts.tscWatch.File = {
96            path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`),
97            content: `export const newFileConst = 30;`
98        };
99
100        function verifyProjectChanges(subScenario: string, allFilesGetter: () => readonly ts.tscWatch.File[]) {
101            const buildLogicAndTests: ts.tscWatch.TscWatchCompileChange = {
102                caption: "Build logic and tests",
103                change: ts.noop,
104                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
105            };
106
107            ts.tscWatch.verifyTscWatch({
108                scenario: "programUpdates",
109                subScenario: `${subScenario}/change builds changes and reports found errors message`,
110                commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
111                sys: () => ts.tscWatch.createWatchedSystem(
112                    allFilesGetter(),
113                    { currentDirectory: projectsLocation }
114                ),
115                changes: [
116                    changeCore(() => `${core[1].content}
117export class someClass { }`, "Make change to core"),
118                    buildLogicAndTests,
119                    // Another change requeues and builds it
120                    changeCore(() => core[1].content, "Revert core file"),
121                    buildLogicAndTests,
122                    {
123                        caption: "Make two changes",
124                        change: sys => {
125                            const change1 = `${core[1].content}
126export class someClass { }`;
127                            sys.writeFile(core[1].path, change1);
128                            assert.equal(sys.writtenFiles.size, 1);
129                            sys.writtenFiles.clear();
130                            sys.writeFile(core[1].path, `${change1}
131export class someClass2 { }`);
132                        },
133                        timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds core
134                    },
135                    buildLogicAndTests,
136                ]
137            });
138
139            ts.tscWatch.verifyTscWatch({
140                scenario: "programUpdates",
141                subScenario: `${subScenario}/non local change does not start build of referencing projects`,
142                commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
143                sys: () => ts.tscWatch.createWatchedSystem(
144                    allFilesGetter(),
145                    { currentDirectory: projectsLocation }
146                ),
147                changes: [
148                    changeCore(() => `${core[1].content}
149function foo() { }`, "Make local change to core"),
150                ]
151            });
152
153            function changeNewFile(newFileContent: string) {
154                return changeFile(newFile.path, newFileContent, "Change to new File and build core");
155            }
156            ts.tscWatch.verifyTscWatch({
157                scenario: "programUpdates",
158                subScenario: `${subScenario}/builds when new file is added, and its subsequent updates`,
159                commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
160                sys: () => ts.tscWatch.createWatchedSystem(
161                    allFilesGetter(),
162                    { currentDirectory: projectsLocation }
163                ),
164                changes: [
165                    changeNewFile(newFile.content),
166                    buildLogicAndTests,
167                    changeNewFile(`${newFile.content}
168export class someClass2 { }`),
169                    buildLogicAndTests,
170                ]
171            });
172        }
173
174        describe("with simple project reference graph", () => {
175            verifyProjectChanges(
176                "with simple project reference graph",
177                () => allFiles
178            );
179        });
180
181        describe("with circular project reference", () => {
182            verifyProjectChanges(
183                "with circular project reference",
184                () => {
185                    const [coreTsconfig, ...otherCoreFiles] = core;
186                    const circularCoreConfig: ts.tscWatch.File = {
187                        path: coreTsconfig.path,
188                        content: JSON.stringify({
189                            compilerOptions: { composite: true, declaration: true },
190                            references: [{ path: "../tests", circular: true }]
191                        })
192                    };
193                    return [ts.tscWatch.libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests];
194                }
195            );
196        });
197    });
198
199    ts.tscWatch.verifyTscWatch({
200        scenario: "programUpdates",
201        subScenario: "watches config files that are not present",
202        commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`],
203        sys: () => ts.tscWatch.createWatchedSystem(
204            [ts.tscWatch.libFile, ...core, logic[1], ...tests],
205            { currentDirectory: projectsLocation }
206        ),
207        changes: [
208            {
209                caption: "Write logic tsconfig and build logic",
210                change: sys => sys.writeFile(logic[0].path, logic[0].content),
211                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds logic
212            },
213            {
214                caption: "Build Tests",
215                change: ts.noop,
216                // Build tests
217                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
218            }
219        ]
220    });
221
222    describe("when referenced using prepend, builds referencing project even for non local change", () => {
223        let coreIndex: ts.tscWatch.File;
224        before(() => {
225            coreIndex = {
226                path: core[1].path,
227                content: `function foo() { return 10; }`
228            };
229        });
230        after(() => {
231            coreIndex = undefined!;
232        });
233        const buildLogic: ts.tscWatch.TscWatchCompileChange = {
234            caption: "Build logic",
235            change: ts.noop,
236            // Builds logic
237            timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
238        };
239        ts.tscWatch.verifyTscWatch({
240            scenario: "programUpdates",
241            subScenario: "when referenced using prepend builds referencing project even for non local change",
242            commandLineArgs: ["-b", "-w", `sample1/${SubProject.logic}`],
243            sys: () => {
244                const coreTsConfig: ts.tscWatch.File = {
245                    path: core[0].path,
246                    content: JSON.stringify({
247                        compilerOptions: { composite: true, declaration: true, outFile: "index.js" }
248                    })
249                };
250                const logicTsConfig: ts.tscWatch.File = {
251                    path: logic[0].path,
252                    content: JSON.stringify({
253                        compilerOptions: { composite: true, declaration: true, outFile: "index.js" },
254                        references: [{ path: "../core", prepend: true }]
255                    })
256                };
257                const logicIndex: ts.tscWatch.File = {
258                    path: logic[1].path,
259                    content: `function bar() { return foo() + 1 };`
260                };
261                return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, coreTsConfig, coreIndex, logicTsConfig, logicIndex], { currentDirectory: projectsLocation });
262            },
263            changes: [
264                changeCore(() => `${coreIndex.content}
265function myFunc() { return 10; }`, "Make non local change and build core"),
266                buildLogic,
267                changeCore(() => `${coreIndex.content}
268function myFunc() { return 100; }`, "Make local change and build core"),
269                buildLogic,
270            ]
271        });
272    });
273
274    describe("when referenced project change introduces error in the down stream project and then fixes it", () => {
275        const subProjectLibrary = `${projectsLocation}/sample1/Library`;
276        const libraryTs: ts.tscWatch.File = {
277            path: `${subProjectLibrary}/library.ts`,
278            content: `
279interface SomeObject
280{
281    message: string;
282}
283
284export function createSomeObject(): SomeObject
285{
286    return {
287        message: "new Object"
288    };
289}`
290        };
291        ts.tscWatch.verifyTscWatch({
292            scenario: "programUpdates",
293            subScenario: "when referenced project change introduces error in the down stream project and then fixes it",
294            commandLineArgs: ["-b", "-w", "App"],
295            sys: () => {
296                const libraryTsconfig: ts.tscWatch.File = {
297                    path: `${subProjectLibrary}/tsconfig.json`,
298                    content: JSON.stringify({ compilerOptions: { composite: true } })
299                };
300                const subProjectApp = `${projectsLocation}/sample1/App`;
301                const appTs: ts.tscWatch.File = {
302                    path: `${subProjectApp}/app.ts`,
303                    content: `import { createSomeObject } from "../Library/library";
304createSomeObject().message;`
305                };
306                const appTsconfig: ts.tscWatch.File = {
307                    path: `${subProjectApp}/tsconfig.json`,
308                    content: JSON.stringify({ references: [{ path: "../Library" }] })
309                };
310
311                const files = [ts.tscWatch.libFile, libraryTs, libraryTsconfig, appTs, appTsconfig];
312                return ts.tscWatch.createWatchedSystem(files, { currentDirectory: `${projectsLocation}/sample1` });
313            },
314            changes: [
315                {
316                    caption: "Introduce error",
317                    // Change message in library to message2
318                    change: sys => sys.writeFile(libraryTs.path, libraryTs.content.replace(/message/g, "message2")),
319                    timeouts: sys => {
320                        sys.checkTimeoutQueueLengthAndRun(1); // Build library
321                        sys.checkTimeoutQueueLengthAndRun(1); // Build App
322                    },
323                },
324                {
325                    caption: "Fix error",
326                    // Revert library changes
327                    change: sys => sys.writeFile(libraryTs.path, libraryTs.content),
328                    timeouts: sys => {
329                        sys.checkTimeoutQueueLengthAndRun(1); // Build library
330                        sys.checkTimeoutQueueLengthAndRun(1); // Build App
331                    },
332                },
333            ]
334        });
335
336    });
337
338    describe("reports errors in all projects on incremental compile", () => {
339        function verifyIncrementalErrors(subScenario: string, buildOptions: readonly string[]) {
340            ts.tscWatch.verifyTscWatch({
341                scenario: "programUpdates",
342                subScenario: `reportErrors/${subScenario}`,
343                commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, ...buildOptions],
344                sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
345                changes: [
346                    {
347                        caption: "change logic",
348                        change: sys => sys.writeFile(logic[1].path, `${logic[1].content}
349let y: string = 10;`),
350                        // Builds logic
351                        timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
352                    },
353                    {
354                        caption: "change core",
355                        change: sys => sys.writeFile(core[1].path, `${core[1].content}
356let x: string = 10;`),
357                        // Builds core
358                        timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
359                    }
360                ]
361            });
362        }
363        verifyIncrementalErrors("when preserveWatchOutput is not used", ts.emptyArray);
364        verifyIncrementalErrors("when preserveWatchOutput is passed on command line", ["--preserveWatchOutput"]);
365
366        describe("when declaration emit errors are present", () => {
367            const solution = "solution";
368            const subProject = "app";
369            const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`;
370            const fileWithError: ts.tscWatch.File = {
371                path: `${subProjectLocation}/fileWithError.ts`,
372                content: `export var myClassWithError = class {
373        tags() { }
374        private p = 12
375    };`
376            };
377            const fileWithFixedError: ts.tscWatch.File = {
378                path: fileWithError.path,
379                content: fileWithError.content.replace("private p = 12", "")
380            };
381            const fileWithoutError: ts.tscWatch.File = {
382                path: `${subProjectLocation}/fileWithoutError.ts`,
383                content: `export class myClass { }`
384            };
385            const tsconfig: ts.tscWatch.File = {
386                path: `${subProjectLocation}/tsconfig.json`,
387                content: JSON.stringify({ compilerOptions: { composite: true } })
388            };
389
390            function incrementalBuild(sys: ts.tscWatch.WatchedSystem) {
391                sys.checkTimeoutQueueLengthAndRun(1); // Build the app
392                sys.checkTimeoutQueueLength(0);
393            }
394
395            const fixError: ts.tscWatch.TscWatchCompileChange = {
396                caption: "Fix error in fileWithError",
397                // Fix error
398                change: sys => sys.writeFile(fileWithError.path, fileWithFixedError.content),
399                timeouts: incrementalBuild
400            };
401
402            const changeFileWithoutError: ts.tscWatch.TscWatchCompileChange = {
403                caption: "Change fileWithoutError",
404                change: sys => sys.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")),
405                timeouts: incrementalBuild
406            };
407
408            ts.tscWatch.verifyTscWatch({
409                scenario: "programUpdates",
410                subScenario: "reportErrors/declarationEmitErrors/when fixing error files all files are emitted",
411                commandLineArgs: ["-b", "-w", subProject],
412                sys: () => ts.tscWatch.createWatchedSystem(
413                    [ts.tscWatch.libFile, fileWithError, fileWithoutError, tsconfig],
414                    { currentDirectory: `${projectsLocation}/${solution}` }
415                ),
416                changes: [
417                    fixError
418                ]
419            });
420
421            ts.tscWatch.verifyTscWatch({
422                scenario: "programUpdates",
423                subScenario: "reportErrors/declarationEmitErrors/when file with no error changes",
424                commandLineArgs: ["-b", "-w", subProject],
425                sys: () => ts.tscWatch.createWatchedSystem(
426                    [ts.tscWatch.libFile, fileWithError, fileWithoutError, tsconfig],
427                    { currentDirectory: `${projectsLocation}/${solution}` }
428                ),
429                changes: [
430                    changeFileWithoutError
431                ]
432            });
433
434            describe("when reporting errors on introducing error", () => {
435                const introduceError: ts.tscWatch.TscWatchCompileChange = {
436                    caption: "Introduce error",
437                    change: sys => sys.writeFile(fileWithError.path, fileWithError.content),
438                    timeouts: incrementalBuild,
439                };
440
441                ts.tscWatch.verifyTscWatch({
442                    scenario: "programUpdates",
443                    subScenario: "reportErrors/declarationEmitErrors/introduceError/when fixing errors only changed file is emitted",
444                    commandLineArgs: ["-b", "-w", subProject],
445                    sys: () => ts.tscWatch.createWatchedSystem(
446                        [ts.tscWatch.libFile, fileWithFixedError, fileWithoutError, tsconfig],
447                        { currentDirectory: `${projectsLocation}/${solution}` }
448                    ),
449                    changes: [
450                        introduceError,
451                        fixError
452                    ]
453                });
454
455                ts.tscWatch.verifyTscWatch({
456                    scenario: "programUpdates",
457                    subScenario: "reportErrors/declarationEmitErrors/introduceError/when file with no error changes",
458                    commandLineArgs: ["-b", "-w", subProject],
459                    sys: () => ts.tscWatch.createWatchedSystem(
460                        [ts.tscWatch.libFile, fileWithFixedError, fileWithoutError, tsconfig],
461                        { currentDirectory: `${projectsLocation}/${solution}` }
462                    ),
463                    changes: [
464                        introduceError,
465                        changeFileWithoutError
466                    ]
467                });
468            });
469        });
470    });
471
472    ts.tscWatch.verifyTscWatch({
473        scenario: "programUpdates",
474        subScenario: "incremental updates in verbose mode",
475        commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, "-verbose"],
476        sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }),
477        changes: [
478            {
479                caption: "Make non dts change",
480                change: sys => sys.writeFile(logic[1].path, `${logic[1].content}
481function someFn() { }`),
482                timeouts: sys => {
483                    sys.checkTimeoutQueueLengthAndRun(1); // build logic and updates tests
484                    sys.checkTimeoutQueueLength(0);
485                },
486            },
487            {
488                caption: "Make dts change",
489                change: sys => sys.writeFile(logic[1].path, `${logic[1].content}
490export function someFn() { }`),
491                timeouts: sys => {
492                    sys.checkTimeoutQueueLengthAndRun(1); // build logic
493                    sys.checkTimeoutQueueLengthAndRun(1); // build tests
494                },
495            }
496        ],
497    });
498
499    ts.tscWatch.verifyTscWatch({
500        scenario: "programUpdates",
501        subScenario: "works when noUnusedParameters changes to false",
502        commandLineArgs: ["-b", "-w"],
503        sys: () => {
504            const index: ts.tscWatch.File = {
505                path: `${ts.tscWatch.projectRoot}/index.ts`,
506                content: `const fn = (a: string, b: string) => b;`
507            };
508            const configFile: ts.tscWatch.File = {
509                path: `${ts.tscWatch.projectRoot}/tsconfig.json`,
510                content: JSON.stringify({
511                    compilerOptions: {
512                        noUnusedParameters: true
513                    }
514                })
515            };
516            return ts.tscWatch.createWatchedSystem([index, configFile, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot });
517        },
518        changes: [
519            {
520                caption: "Change tsconfig to set noUnusedParameters to false",
521                change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({
522                    compilerOptions: {
523                        noUnusedParameters: false
524                    }
525                })),
526                timeouts: ts.tscWatch.runQueuedTimeoutCallbacks,
527            },
528        ]
529    });
530
531    ts.tscWatch.verifyTscWatch({
532        scenario: "programUpdates",
533        subScenario: "should not trigger recompilation because of program emit",
534        commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"],
535        sys: () => ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, ...core], { currentDirectory: projectsLocation }),
536        changes: [
537            ts.tscWatch.noopChange,
538            {
539                caption: "Add new file",
540                change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`),
541                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
542            },
543            ts.tscWatch.noopChange,
544        ]
545    });
546
547    ts.tscWatch.verifyTscWatch({
548        scenario: "programUpdates",
549        subScenario: "should not trigger recompilation because of program emit with outDir specified",
550        commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"],
551        sys: () => {
552            const [coreConfig, ...rest] = core;
553            const newCoreConfig: ts.tscWatch.File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) };
554            return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, newCoreConfig, ...rest], { currentDirectory: projectsLocation });
555        },
556        changes: [
557            ts.tscWatch.noopChange,
558            {
559                caption: "Add new file",
560                change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`),
561                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun
562            },
563            ts.tscWatch.noopChange
564        ]
565    });
566
567    ts.tscWatch.verifyTscWatch({
568        scenario: "programUpdates",
569        subScenario: "works with extended source files",
570        commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"],
571        sys: () => {
572            const alphaExtendedConfigFile: ts.tscWatch.File = {
573                path: "/a/b/alpha.tsconfig.json",
574                content: "{}"
575            };
576            const project1Config: ts.tscWatch.File = {
577                path: "/a/b/project1.tsconfig.json",
578                content: JSON.stringify({
579                    extends: "./alpha.tsconfig.json",
580                    compilerOptions: {
581                        composite: true,
582                    },
583                    files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path]
584                })
585            };
586            const bravoExtendedConfigFile: ts.tscWatch.File = {
587                path: "/a/b/bravo.tsconfig.json",
588                content: JSON.stringify({
589                    extends: "./alpha.tsconfig.json"
590                })
591            };
592            const otherFile: ts.tscWatch.File = {
593                path: "/a/b/other.ts",
594                content: "let z = 0;",
595            };
596            const project2Config: ts.tscWatch.File = {
597                path: "/a/b/project2.tsconfig.json",
598                content: JSON.stringify({
599                    extends: "./bravo.tsconfig.json",
600                    compilerOptions: {
601                        composite: true,
602                    },
603                    files: [otherFile.path]
604                })
605            };
606            return ts.tscWatch.createWatchedSystem([
607                ts.tscWatch.libFile,
608                alphaExtendedConfigFile, project1Config, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2,
609                bravoExtendedConfigFile, project2Config, otherFile
610            ], { currentDirectory: "/a/b" });
611        },
612        changes: [
613            {
614                caption: "Modify alpha config",
615                change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", JSON.stringify({
616                    compilerOptions: { strict: true }
617                })),
618                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build project1
619            },
620            {
621                caption: "Build project 2",
622                change: ts.noop,
623                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
624            },
625            {
626                caption: "change bravo config",
627                change: sys => sys.writeFile("/a/b/bravo.tsconfig.json", JSON.stringify({
628                    extends: "./alpha.tsconfig.json",
629                    compilerOptions: { strict: false }
630                })),
631                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
632            },
633            {
634                caption: "project 2 extends alpha",
635                change: sys => sys.writeFile("/a/b/project2.tsconfig.json", JSON.stringify({
636                    extends: "./alpha.tsconfig.json",
637                })),
638                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
639            },
640            {
641                caption: "update aplha config",
642                change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", "{}"),
643                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // build project1
644            },
645            {
646                caption: "Build project 2",
647                change: ts.noop,
648                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2
649            },
650        ]
651    });
652
653    ts.tscWatch.verifyTscWatch({
654        scenario: "programUpdates",
655        subScenario: "works correctly when project with extended config is removed",
656        commandLineArgs: ["-b", "-w", "-v"],
657        sys: () => {
658            const alphaExtendedConfigFile: ts.tscWatch.File = {
659                path: "/a/b/alpha.tsconfig.json",
660                content: JSON.stringify({
661                    compilerOptions: {
662                        strict: true
663                    }
664                })
665            };
666            const project1Config: ts.tscWatch.File = {
667                path: "/a/b/project1.tsconfig.json",
668                content: JSON.stringify({
669                    extends: "./alpha.tsconfig.json",
670                    compilerOptions: {
671                        composite: true,
672                    },
673                    files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path]
674                })
675            };
676            const bravoExtendedConfigFile: ts.tscWatch.File = {
677                path: "/a/b/bravo.tsconfig.json",
678                content: JSON.stringify({
679                    compilerOptions: {
680                        strict: true
681                    }
682                })
683            };
684            const otherFile: ts.tscWatch.File = {
685                path: "/a/b/other.ts",
686                content: "let z = 0;",
687            };
688            const project2Config: ts.tscWatch.File = {
689                path: "/a/b/project2.tsconfig.json",
690                content: JSON.stringify({
691                    extends: "./bravo.tsconfig.json",
692                    compilerOptions: {
693                        composite: true,
694                    },
695                    files: [otherFile.path]
696                })
697            };
698            const configFile: ts.tscWatch.File = {
699                path: "/a/b/tsconfig.json",
700                content: JSON.stringify({
701                    references: [
702                        {
703                            path: "./project1.tsconfig.json",
704                        },
705                        {
706                            path: "./project2.tsconfig.json",
707                        },
708                    ],
709                    files: [],
710                })
711            };
712            return ts.tscWatch.createWatchedSystem([
713                ts.tscWatch.libFile, configFile,
714                alphaExtendedConfigFile, project1Config, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2,
715                bravoExtendedConfigFile, project2Config, otherFile
716            ], { currentDirectory: "/a/b" });
717        },
718        changes: [
719            {
720                caption: "Remove project2 from base config",
721                change: sys => sys.modifyFile("/a/b/tsconfig.json", JSON.stringify({
722                    references: [
723                        {
724                            path: "./project1.tsconfig.json",
725                        },
726                    ],
727                    files: [],
728                })),
729                timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout,
730            }
731        ]
732    });
733
734    ts.tscWatch.verifyTscWatch({
735        scenario: "programUpdates",
736        subScenario: "tsbuildinfo has error",
737        sys: () => ts.tscWatch.createWatchedSystem({
738            "/src/project/main.ts": "export const x = 10;",
739            "/src/project/tsconfig.json": "{}",
740            "/src/project/tsconfig.tsbuildinfo": "Some random string",
741            [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content,
742        }),
743        commandLineArgs: ["--b", "src/project", "-i", "-w"],
744        changes: ts.emptyArray
745    });
746});