1namespace ts.tscWatch { 2 import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile; 3 describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedirect", () => { 4 interface VerifyWatchInput { 5 files: readonly TestFSWithWatch.FileOrFolderOrSymLink[]; 6 config: string; 7 expectedProgramFiles: readonly string[]; 8 } 9 function verifyWatch( 10 { files, config, expectedProgramFiles }: VerifyWatchInput, 11 alreadyBuilt: boolean 12 ) { 13 const sys = createWatchedSystem(files); 14 if (alreadyBuilt) { 15 const solutionBuilder = createSolutionBuilder(sys, [config], {}); 16 solutionBuilder.build(); 17 solutionBuilder.close(); 18 sys.clearOutput(); 19 } 20 const host = createWatchCompilerHostOfConfigFile({ 21 configFileName: config, 22 system: sys 23 }); 24 host.useSourceOfProjectReferenceRedirect = returnTrue; 25 const watch = createWatchProgram(host); 26 checkProgramActualFiles(watch.getCurrentProgram().getProgram(), expectedProgramFiles); 27 } 28 29 function verifyScenario(input: () => VerifyWatchInput) { 30 it("when solution is not built", () => { 31 verifyWatch(input(), /*alreadyBuilt*/ false); 32 }); 33 34 it("when solution is already built", () => { 35 verifyWatch(input(), /*alreadyBuilt*/ true); 36 }); 37 } 38 39 describe("with simple project", () => { 40 verifyScenario(() => { 41 const baseConfig = getFileFromProject("demo", "tsconfig-base.json"); 42 const coreTs = getFileFromProject("demo", "core/utilities.ts"); 43 const coreConfig = getFileFromProject("demo", "core/tsconfig.json"); 44 const animalTs = getFileFromProject("demo", "animals/animal.ts"); 45 const dogTs = getFileFromProject("demo", "animals/dog.ts"); 46 const indexTs = getFileFromProject("demo", "animals/index.ts"); 47 const animalsConfig = getFileFromProject("demo", "animals/tsconfig.json"); 48 return { 49 files: [{ path: libFile.path, content: libContent }, baseConfig, coreTs, coreConfig, animalTs, dogTs, indexTs, animalsConfig], 50 config: animalsConfig.path, 51 expectedProgramFiles: [libFile.path, indexTs.path, dogTs.path, animalTs.path, coreTs.path] 52 }; 53 }); 54 }); 55 56 describe("when references are monorepo like with symlinks", () => { 57 interface Packages { 58 bPackageJson: File; 59 aTest: File; 60 bFoo: File; 61 bBar: File; 62 bSymlink: SymLink; 63 } 64 function verifySymlinkScenario(packages: () => Packages) { 65 describe("when preserveSymlinks is turned off", () => { 66 verifySymlinkScenarioWorker(packages, {}); 67 }); 68 describe("when preserveSymlinks is turned on", () => { 69 verifySymlinkScenarioWorker(packages, { preserveSymlinks: true }); 70 }); 71 } 72 73 function verifySymlinkScenarioWorker(packages: () => Packages, extraOptions: CompilerOptions) { 74 verifyScenario(() => { 75 const { bPackageJson, aTest, bFoo, bBar, bSymlink } = packages(); 76 const aConfig = config("A", extraOptions, ["../B"]); 77 const bConfig = config("B", extraOptions); 78 return { 79 files: [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink], 80 config: aConfig.path, 81 expectedProgramFiles: [libFile.path, aTest.path, bFoo.path, bBar.path] 82 }; 83 }); 84 } 85 86 function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { 87 return { 88 path: `${projectRoot}/packages/${packageName}/tsconfig.json`, 89 content: JSON.stringify({ 90 compilerOptions: { 91 outDir: "lib", 92 rootDir: "src", 93 composite: true, 94 ...extraOptions 95 }, 96 include: ["src"], 97 ...(references ? { references: references.map(path => ({ path })) } : {}) 98 }) 99 }; 100 } 101 102 function file(packageName: string, fileName: string, content: string): File { 103 return { 104 path: `${projectRoot}/packages/${packageName}/src/${fileName}`, 105 content 106 }; 107 } 108 109 function verifyMonoRepoLike(scope = "") { 110 describe("when packageJson has types field", () => { 111 verifySymlinkScenario(() => ({ 112 bPackageJson: { 113 path: `${projectRoot}/packages/B/package.json`, 114 content: JSON.stringify({ 115 main: "lib/index.js", 116 types: "lib/index.d.ts" 117 }) 118 }, 119 aTest: file("A", "index.ts", `import { foo } from '${scope}b'; 120import { bar } from '${scope}b/lib/bar'; 121foo(); 122bar(); 123`), 124 bFoo: file("B", "index.ts", `export function foo() { }`), 125 bBar: file("B", "bar.ts", `export function bar() { }`), 126 bSymlink: { 127 path: `${projectRoot}/node_modules/${scope}b`, 128 symLink: `${projectRoot}/packages/B` 129 } 130 })); 131 }); 132 133 describe("when referencing file from subFolder", () => { 134 verifySymlinkScenario(() => ({ 135 bPackageJson: { 136 path: `${projectRoot}/packages/B/package.json`, 137 content: "{}" 138 }, 139 aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; 140import { bar } from '${scope}b/lib/bar/foo'; 141foo(); 142bar(); 143`), 144 bFoo: file("B", "foo.ts", `export function foo() { }`), 145 bBar: file("B", "bar/foo.ts", `export function bar() { }`), 146 bSymlink: { 147 path: `${projectRoot}/node_modules/${scope}b`, 148 symLink: `${projectRoot}/packages/B` 149 } 150 })); 151 }); 152 } 153 describe("when package is not scoped", () => { 154 verifyMonoRepoLike(); 155 }); 156 describe("when package is scoped", () => { 157 verifyMonoRepoLike("@issue/"); 158 }); 159 }); 160 }); 161} 162