• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    describe("unittests:: tsbuild:: on 'sample1' project", () => {
3        let projFs: vfs.FileSystem;
4        let projFsWithBuild: vfs.FileSystem;
5        before(() => {
6            projFs = loadProjectFromDisk("tests/projects/sample1");
7        });
8
9        after(() => {
10            projFs = undefined!; // Release the contents
11            projFsWithBuild = undefined!;
12        });
13
14        function getTsBuildProjectFile(project: string, file: string): tscWatch.File {
15            return {
16                path: TestFSWithWatch.getTsBuildProjectFilePath(project, file),
17                content: projFs.readFileSync(`/src/${project}/${file}`, "utf8")!
18            };
19        }
20
21        function getSampleFsAfterBuild() {
22            if (projFsWithBuild) return projFsWithBuild;
23            const fs = projFs.shadow();
24            const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" });
25            const host = createSolutionBuilderHostForBaseline(sys as TscCompileSystem);
26            const builder = createSolutionBuilder(host, ["/src/tests"], {});
27            builder.build();
28            fs.makeReadonly();
29            return projFsWithBuild = fs;
30        }
31
32        describe("sanity check of clean build of 'sample1' project", () => {
33            verifyTsc({
34                scenario: "sample1",
35                subScenario: "builds correctly when outDir is specified",
36                fs: () => projFs,
37                commandLineArgs: ["--b", "/src/tests"],
38                modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({
39                    compilerOptions: { composite: true, declaration: true, sourceMap: true, outDir: "outDir" },
40                    references: [{ path: "../core" }]
41                })),
42            });
43
44            verifyTsc({
45                scenario: "sample1",
46                subScenario: "builds correctly when declarationDir is specified",
47                fs: () => projFs,
48                commandLineArgs: ["--b", "/src/tests"],
49                modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({
50                    compilerOptions: { composite: true, declaration: true, sourceMap: true, declarationDir: "out/decls" },
51                    references: [{ path: "../core" }]
52                })),
53            });
54
55            verifyTsc({
56                scenario: "sample1",
57                subScenario: "builds correctly when project is not composite or doesnt have any references",
58                fs: () => projFs,
59                commandLineArgs: ["--b", "/src/core", "--verbose"],
60                modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, ""),
61            });
62        });
63
64        describe("dry builds", () => {
65            verifyTsc({
66                scenario: "sample1",
67                subScenario: "does not write any files in a dry build",
68                fs: () => projFs,
69                commandLineArgs: ["--b", "/src/tests", "--dry"],
70            });
71        });
72
73        describe("clean builds", () => {
74            verifyTscWithEdits({
75                scenario: "sample1",
76                subScenario: "removes all files it built",
77                fs: getSampleFsAfterBuild,
78                commandLineArgs: ["--b", "/src/tests", "--clean"],
79                edits: noChangeOnlyRuns
80            });
81
82            verifyTscCompileLike(testTscCompileLike, {
83                scenario: "sample1",
84                subScenario: "cleans till project specified",
85                fs: getSampleFsAfterBuild,
86                commandLineArgs: ["--b", "/src/logic", "--clean"],
87                compile: sys => {
88                    const buildHost = createSolutionBuilderHostForBaseline(sys);
89                    const builder = createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {});
90                    sys.exit(builder.clean("/src/logic"));
91                }
92            });
93
94            verifyTscCompileLike(testTscCompileLike, {
95                scenario: "sample1",
96                subScenario: "cleaning project in not build order doesnt throw error",
97                fs: getSampleFsAfterBuild,
98                commandLineArgs: ["--b", "/src/logic2", "--clean"],
99                compile: sys => {
100                    const buildHost = createSolutionBuilderHostForBaseline(sys);
101                    const builder = createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {});
102                    sys.exit(builder.clean("/src/logic2"));
103                }
104            });
105        });
106
107        describe("force builds", () => {
108            verifyTscWithEdits({
109                scenario: "sample1",
110                subScenario: "always builds under with force option",
111                fs: () => projFs,
112                commandLineArgs: ["--b", "/src/tests", "--force"],
113                edits: noChangeOnlyRuns
114            });
115        });
116
117        describe("can detect when and what to rebuild", () => {
118            verifyTscWithEdits({
119                scenario: "sample1",
120                subScenario: "can detect when and what to rebuild",
121                fs: getSampleFsAfterBuild,
122                commandLineArgs: ["--b", "/src/tests", "--verbose"],
123                edits: [
124                    // Update a file in the leaf node (tests), only it should rebuild the last one
125                    {
126                        subScenario: "Only builds the leaf node project",
127                        modifyFs: fs => fs.writeFileSync("/src/tests/index.ts", "const m = 10;"),
128                    },
129                    // Update a file in the parent (without affecting types), should get fast downstream builds
130                    {
131                        subScenario: "Detects type-only changes in upstream projects",
132                        modifyFs: fs => replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"),
133                    },
134                    {
135                        subScenario: "rebuilds when tsconfig changes",
136                        modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es3"`),
137                    },
138                ]
139            });
140
141            verifyTscWithEdits({
142                scenario: "sample1",
143                subScenario: "when input file text does not change but its modified time changes",
144                fs: () => projFs,
145                commandLineArgs: ["--b", "/src/tests", "--verbose"],
146                edits: [
147                    {
148                        subScenario: "upstream project changes without changing file text",
149                        modifyFs: fs => {
150                            const time = new Date(fs.time());
151                            fs.utimesSync("/src/core/index.ts", time, time);
152                        },
153                    },
154                ]
155            });
156
157            verifyTsc({
158                scenario: "sample1",
159                subScenario: "indicates that it would skip builds during a dry build",
160                fs: getSampleFsAfterBuild,
161                commandLineArgs: ["--b", "/src/tests", "--dry"],
162            });
163
164            verifyTsc({
165                scenario: "sample1",
166                subScenario: "rebuilds from start if force option is set",
167                fs: getSampleFsAfterBuild,
168                commandLineArgs: ["--b", "/src/tests", "--verbose", "--force"],
169            });
170
171            verifyTscWithEdits({
172                scenario: "sample1",
173                subScenario: "tsbuildinfo has error",
174                fs: () => loadProjectFromFiles({
175                    "/src/project/main.ts": "export const x = 10;",
176                    "/src/project/tsconfig.json": "{}",
177                    "/src/project/tsconfig.tsbuildinfo": "Some random string",
178                }),
179                commandLineArgs: ["--b", "src/project", "-i", "-v"],
180                edits: [{
181                    subScenario: "tsbuildinfo written has error",
182                    modifyFs: fs => prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"),
183                }]
184            });
185
186            verifyTscCompileLike(testTscCompileLike, {
187                scenario: "sample1",
188                subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version",
189                fs: getSampleFsAfterBuild,
190                commandLineArgs: ["--b", "/src/tests", "--verbose"],
191                compile: sys => {
192                    // Buildinfo will have version which does not match with current ts version
193                    const buildHost = createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion");
194                    const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
195                    sys.exit(builder.build());
196                }
197            });
198
199            verifyTscCompileLike(testTscCompileLike, {
200                scenario: "sample1",
201                subScenario: "does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version",
202                fs: () => {
203                    const fs = projFs.shadow();
204                    const host = fakes.SolutionBuilderHost.create(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder);
205                    const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
206                    builder.build();
207                    fs.makeReadonly();
208                    return fs;
209                },
210                commandLineArgs: ["--b", "/src/tests", "--verbose"],
211                compile: sys => {
212                    // Buildinfo will have version which does not match with current ts version
213                    const buildHost = createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion");
214                    const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
215                    sys.exit(builder.build());
216                },
217            });
218
219            verifyTscWithEdits({
220                scenario: "sample1",
221                subScenario: "rebuilds when extended config file changes",
222                fs: () => projFs,
223                commandLineArgs: ["--b", "/src/tests", "--verbose"],
224                modifyFs: fs => {
225                    fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: { target: "es3" } }));
226                    replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`);
227                },
228                edits: [{
229                    subScenario: "incremental-declaration-changes",
230                    modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: {} }))
231                }]
232            });
233
234            verifyTscCompileLike(testTscCompileLike, {
235                scenario: "sample1",
236                subScenario: "builds till project specified",
237                fs: () => projFs,
238                commandLineArgs: ["--build", "/src/logic/tsconfig.json"],
239                compile: sys => {
240                    const buildHost = createSolutionBuilderHostForBaseline(sys);
241                    const builder = createSolutionBuilder(buildHost, ["/src/tests"], {});
242                    sys.exit(builder.build("/src/logic/tsconfig.json"));
243                }
244            });
245
246            verifyTscCompileLike(testTscCompileLike, {
247                scenario: "sample1",
248                subScenario: "building project in not build order doesnt throw error",
249                fs: () => projFs,
250                commandLineArgs: ["--build", "/src/logic2/tsconfig.json"],
251                compile: sys => {
252                    const buildHost = createSolutionBuilderHostForBaseline(sys);
253                    const builder = createSolutionBuilder(buildHost, ["/src/tests"], {});
254                    sys.exit(builder.build("/src/logic2/tsconfig.json"));
255                }
256            });
257
258            it("building using getNextInvalidatedProject", () => {
259                const coreConfig = getTsBuildProjectFile("core", "tsconfig.json");
260                const coreIndex = getTsBuildProjectFile("core", "index.ts");
261                const coreDecl = getTsBuildProjectFile("core", "some_decl.d.ts");
262                const coreAnotherModule = getTsBuildProjectFile("core", "anotherModule.ts");
263                const logicConfig = getTsBuildProjectFile("logic", "tsconfig.json");
264                const logicIndex = getTsBuildProjectFile("logic", "index.ts");
265                const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json");
266                const testsIndex = getTsBuildProjectFile("tests", "index.ts");
267                const baseline: string[] = [];
268                let oldSnap: ReturnType<TestFSWithWatch.TestServerHost["snap"]> | undefined;
269                const system = TestFSWithWatch.changeToHostTrackingWrittenFiles(
270                    fakes.patchHostForBuildInfoReadWrite(
271                        tscWatch.createWatchedSystem([
272                            coreConfig, coreIndex, coreDecl, coreAnotherModule,
273                            logicConfig, logicIndex,
274                            testsConfig, testsIndex,
275                            tscWatch.libFile
276                        ])
277                    )
278                );
279
280                const host = createSolutionBuilderHostForBaseline(system);
281                const builder = createSolutionBuilder(host, [testsConfig.path], {});
282                baseline.push("Input::");
283                baselineState();
284                verifyBuildNextResult(); // core
285                verifyBuildNextResult(); // logic
286                verifyBuildNextResult();// tests
287                verifyBuildNextResult(); // All Done
288                Harness.Baseline.runBaseline(`tsbuild/sample1/building-using-getNextInvalidatedProject.js`, baseline.join("\r\n"));
289
290                function verifyBuildNextResult() {
291                    const project = builder.getNextInvalidatedProject();
292                    const result = project && project.done();
293                    baseline.push(`Project Result:: ${JSON.stringify({ project: project?.project, result })}`);
294                    baselineState();
295                }
296
297                function baselineState() {
298                    system.serializeOutput(baseline);
299                    system.diff(baseline, oldSnap);
300                    system.writtenFiles.clear();
301                    oldSnap = system.snap();
302                }
303            });
304
305            verifyTscCompileLike(testTscCompileLike, {
306                scenario: "sample1",
307                subScenario: "building using buildReferencedProject",
308                fs: () => projFs,
309                commandLineArgs: ["--build", "/src/logic2/tsconfig.json"],
310                compile: sys => {
311                    const buildHost = createSolutionBuilderHostForBaseline(sys);
312                    const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true });
313                    sys.exit(builder.buildReferences("/src/tests"));
314                }
315            });
316        });
317
318        describe("downstream-blocked compilations", () => {
319            verifyTscWithEdits({
320                scenario: "sample1",
321                subScenario: "does not build downstream projects if upstream projects have errors",
322                fs: () => projFs,
323                commandLineArgs: ["--b", "/src/tests", "--verbose"],
324                modifyFs: fs => replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`),
325                edits: noChangeOnlyRuns
326            });
327        });
328
329        describe("project invalidation", () => {
330            it("invalidates projects correctly", () => {
331                const coreConfig = getTsBuildProjectFile("core", "tsconfig.json");
332                const coreIndex = getTsBuildProjectFile("core", "index.ts");
333                const coreDecl = getTsBuildProjectFile("core", "some_decl.d.ts");
334                const coreAnotherModule = getTsBuildProjectFile("core", "anotherModule.ts");
335                const logicConfig = getTsBuildProjectFile("logic", "tsconfig.json");
336                const logicIndex = getTsBuildProjectFile("logic", "index.ts");
337                const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json");
338                const testsIndex = getTsBuildProjectFile("tests", "index.ts");
339                const baseline: string[] = [];
340                let oldSnap: ReturnType<TestFSWithWatch.TestServerHost["snap"]> | undefined;
341                const system = TestFSWithWatch.changeToHostTrackingWrittenFiles(
342                    fakes.patchHostForBuildInfoReadWrite(
343                        tscWatch.createWatchedSystem([
344                            coreConfig, coreIndex, coreDecl, coreAnotherModule,
345                            logicConfig, logicIndex,
346                            testsConfig, testsIndex,
347                            tscWatch.libFile
348                        ])
349                    )
350                );
351
352                const host = createSolutionBuilderHostForBaseline(system);
353                const builder = createSolutionBuilder(host, [testsConfig.path], { dry: false, force: false, verbose: false });
354                builder.build();
355                baselineState("Build of project");
356
357                // Update a timestamp in the middle project
358                system.appendFile(logicIndex.path, "function foo() {}");
359
360                // Because we haven't reset the build context, the builder should assume there's nothing to do right now
361                const status = builder.getUpToDateStatusOfProject(logicConfig.path);
362                baseline.push(`Project should still be upto date: ${UpToDateStatusType[status.type]}`);
363                verifyInvalidation("non Dts change to logic");
364
365                // Rebuild this project
366                system.appendFile(logicIndex.path, `export class cNew {}`);
367                verifyInvalidation("Dts change to Logic");
368                Harness.Baseline.runBaseline(`tsbuild/sample1/invalidates-projects-correctly.js`, baseline.join("\r\n"));
369
370                function verifyInvalidation(heading: string) {
371                    // Rebuild this project
372                    builder.invalidateProject(logicConfig.path as ResolvedConfigFilePath);
373                    builder.getNextInvalidatedProject()?.done();
374                    baselineState(`${heading}:: After rebuilding logicConfig`);
375
376                    // Build downstream projects should update 'tests', but not 'core'
377                    builder.getNextInvalidatedProject()?.done();
378                    baselineState(`${heading}:: After building next project`);
379                }
380
381                function baselineState(heading: string) {
382                    baseline.push(heading);
383                    system.serializeOutput(baseline);
384                    system.diff(baseline, oldSnap);
385                    system.writtenFiles.clear();
386                    oldSnap = system.snap();
387                }
388            });
389        });
390
391        const coreChanges: TestTscEdit[] = [
392            {
393                subScenario: "incremental-declaration-changes",
394                modifyFs: fs => appendText(fs, "/src/core/index.ts", `
395export class someClass { }`),
396            },
397            {
398                subScenario: "incremental-declaration-doesnt-change",
399                modifyFs: fs => appendText(fs, "/src/core/index.ts", `
400class someClass2 { }`),
401            },
402            noChangeRun,
403        ];
404
405        describe("lists files", () => {
406            verifyTscWithEdits({
407                scenario: "sample1",
408                subScenario: "listFiles",
409                fs: () => projFs,
410                commandLineArgs: ["--b", "/src/tests", "--listFiles"],
411                edits: coreChanges
412            });
413            verifyTscWithEdits({
414                scenario: "sample1",
415                subScenario: "listEmittedFiles",
416                fs: () => projFs,
417                commandLineArgs: ["--b", "/src/tests", "--listEmittedFiles"],
418                edits: coreChanges
419            });
420            verifyTscWithEdits({
421                scenario: "sample1",
422                subScenario: "explainFiles",
423                fs: () => projFs,
424                commandLineArgs: ["--b", "/src/tests", "--explainFiles", "--v"],
425                edits: coreChanges
426            });
427        });
428
429        describe("emit output", () => {
430            verifyTscWithEdits({
431                subScenario: "sample",
432                fs: () => projFs,
433                scenario: "sample1",
434                commandLineArgs: ["--b", "/src/tests", "--verbose"],
435                baselineSourceMap: true,
436                baselineReadFileCalls: true,
437                edits: [
438                    ...coreChanges,
439                    {
440                        subScenario: "when logic config changes declaration dir",
441                        modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true,
442        "declarationDir": "decls",`),
443                    },
444                    noChangeRun,
445                ],
446            });
447
448            verifyTsc({
449                scenario: "sample1",
450                subScenario: "when logic specifies tsBuildInfoFile",
451                fs: () => projFs,
452                modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true,
453        "tsBuildInfoFile": "ownFile.tsbuildinfo",`),
454                commandLineArgs: ["--b", "/src/tests", "--verbose"],
455                baselineSourceMap: true,
456                baselineReadFileCalls: true
457            });
458
459            verifyTscWithEdits({
460                subScenario: "when declaration option changes",
461                fs: () => projFs,
462                scenario: "sample1",
463                commandLineArgs: ["--b", "/src/core", "--verbose"],
464                modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{
465    "compilerOptions": {
466        "incremental": true,
467        "skipDefaultLibCheck": true
468    }
469}`),
470                edits: [{
471                    subScenario: "incremental-declaration-changes",
472                    modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`),
473                }],
474            });
475
476            verifyTscWithEdits({
477                subScenario: "when target option changes",
478                fs: () => projFs,
479                scenario: "sample1",
480                commandLineArgs: ["--b", "/src/core", "--verbose"],
481                modifyFs: fs => {
482                    fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// <reference no-default-lib="true"/>
483/// <reference lib="esnext" />`);
484                    fs.writeFileSync("/lib/lib.esnext.d.ts", libContent);
485                    fs.writeFileSync("/lib/lib.d.ts", `/// <reference no-default-lib="true"/>
486/// <reference lib="esnext" />`);
487                    fs.writeFileSync("/src/core/tsconfig.json", `{
488    "compilerOptions": {
489        "incremental": true,
490"listFiles": true,
491"listEmittedFiles": true,
492        "target": "esnext",
493    }
494}`);
495                },
496                edits: [{
497                    subScenario: "incremental-declaration-changes",
498                    modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", "esnext", "es5"),
499                }],
500            });
501
502            verifyTscWithEdits({
503                subScenario: "when module option changes",
504                fs: () => projFs,
505                scenario: "sample1",
506                commandLineArgs: ["--b", "/src/core", "--verbose"],
507                modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{
508    "compilerOptions": {
509        "incremental": true,
510        "module": "commonjs"
511    }
512}`),
513                edits: [{
514                    subScenario: "incremental-declaration-changes",
515                    modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"module": "commonjs"`, `"module": "amd"`),
516                }],
517            });
518
519            verifyTscWithEdits({
520                subScenario: "when esModuleInterop option changes",
521                fs: () => projFs,
522                scenario: "sample1",
523                commandLineArgs: ["--b", "/src/tests", "--verbose"],
524                modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.json", `{
525    "references": [
526        { "path": "../core" },
527        { "path": "../logic" }
528    ],
529    "files": ["index.ts"],
530    "compilerOptions": {
531        "composite": true,
532        "declaration": true,
533        "forceConsistentCasingInFileNames": true,
534        "skipDefaultLibCheck": true,
535        "esModuleInterop": false
536    }
537}`),
538                edits: [{
539                    subScenario: "incremental-declaration-changes",
540                    modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`),
541                }],
542            });
543
544            verifyTsc({
545                scenario: "sample1",
546                subScenario: "reports error if input file is missing",
547                fs: () => projFs,
548                commandLineArgs: ["--b", "/src/tests", "--v"],
549                modifyFs: fs => {
550                    fs.writeFileSync("/src/core/tsconfig.json", JSON.stringify({
551                        compilerOptions: { composite: true },
552                        files: ["anotherModule.ts", "index.ts", "some_decl.d.ts"]
553                    }));
554                    fs.unlinkSync("/src/core/anotherModule.ts");
555                }
556            });
557
558            verifyTsc({
559                scenario: "sample1",
560                subScenario: "reports error if input file is missing with force",
561                fs: () => projFs,
562                commandLineArgs: ["--b", "/src/tests", "--v", "--f"],
563                modifyFs: fs => {
564                    fs.writeFileSync("/src/core/tsconfig.json", JSON.stringify({
565                        compilerOptions: { composite: true },
566                        files: ["anotherModule.ts", "index.ts", "some_decl.d.ts"]
567                    }));
568                    fs.unlinkSync("/src/core/anotherModule.ts");
569                }
570            });
571        });
572    });
573}
574