1namespace ts.tscWatch { 2 describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => { 3 const config: File = { 4 path: `${projectRoot}/tsconfig.json`, 5 content: `{}` 6 }; 7 interface VerifyEmitAndErrorUpdates { 8 subScenario: string 9 files: () => File[]; 10 currentDirectory?: string; 11 changes: TscWatchCompileChange[]; 12 } 13 function verifyEmitAndErrorUpdates({ 14 subScenario, 15 files, 16 currentDirectory, 17 changes, 18 }: VerifyEmitAndErrorUpdates) { 19 verifyTscWatch({ 20 scenario: "emitAndErrorUpdates", 21 subScenario: `default/${subScenario}`, 22 commandLineArgs: ["--w"], 23 sys: () => createWatchedSystem( 24 files(), 25 { currentDirectory: currentDirectory || projectRoot } 26 ), 27 changes, 28 baselineIncremental: true 29 }); 30 31 verifyTscWatch({ 32 scenario: "emitAndErrorUpdates", 33 subScenario: `defaultAndD/${subScenario}`, 34 commandLineArgs: ["--w", "--d"], 35 sys: () => createWatchedSystem( 36 files(), 37 { currentDirectory: currentDirectory || projectRoot } 38 ), 39 changes, 40 baselineIncremental: true 41 }); 42 43 verifyTscWatch({ 44 scenario: "emitAndErrorUpdates", 45 subScenario: `isolatedModules/${subScenario}`, 46 commandLineArgs: ["--w", "--isolatedModules"], 47 sys: () => createWatchedSystem( 48 files(), 49 { currentDirectory: currentDirectory || projectRoot } 50 ), 51 changes, 52 baselineIncremental: true 53 }); 54 55 verifyTscWatch({ 56 scenario: "emitAndErrorUpdates", 57 subScenario: `isolatedModulesAndD/${subScenario}`, 58 commandLineArgs: ["--w", "--isolatedModules", "--d"], 59 sys: () => createWatchedSystem( 60 files(), 61 { currentDirectory: currentDirectory || projectRoot } 62 ), 63 changes, 64 baselineIncremental: true 65 }); 66 67 verifyTscWatch({ 68 scenario: "emitAndErrorUpdates", 69 subScenario: `assumeChangesOnlyAffectDirectDependencies/${subScenario}`, 70 commandLineArgs: ["--w", "--assumeChangesOnlyAffectDirectDependencies"], 71 sys: () => createWatchedSystem( 72 files(), 73 { currentDirectory: currentDirectory || projectRoot } 74 ), 75 changes, 76 baselineIncremental: true 77 }); 78 79 verifyTscWatch({ 80 scenario: "emitAndErrorUpdates", 81 subScenario: `assumeChangesOnlyAffectDirectDependenciesAndD/${subScenario}`, 82 commandLineArgs: ["--w", "--assumeChangesOnlyAffectDirectDependencies", "--d"], 83 sys: () => createWatchedSystem( 84 files(), 85 { currentDirectory: currentDirectory || projectRoot } 86 ), 87 changes, 88 baselineIncremental: true 89 }); 90 } 91 92 describe("deep import changes", () => { 93 const aFile: File = { 94 path: `${projectRoot}/a.ts`, 95 content: `import {B} from './b'; 96declare var console: any; 97let b = new B(); 98console.log(b.c.d);` 99 }; 100 101 function verifyDeepImportChange(subScenario: string, bFile: File, cFile: File) { 102 verifyEmitAndErrorUpdates({ 103 subScenario: `deepImportChanges/${subScenario}`, 104 files: () => [aFile, bFile, cFile, config, libFile], 105 changes: [ 106 { 107 caption: "Rename property d to d2 of class C to initialize signatures", 108 change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), 109 timeouts: runQueuedTimeoutCallbacks, 110 }, 111 { 112 caption: "Rename property d2 to d of class C to revert back to original text", 113 change: sys => sys.writeFile(cFile.path, cFile.content.replace("d2", "d")), 114 timeouts: runQueuedTimeoutCallbacks, 115 }, 116 { 117 caption: "Rename property d to d2 of class C", 118 change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), 119 timeouts: runQueuedTimeoutCallbacks, 120 } 121 ], 122 }); 123 } 124 describe("updates errors when deep import file changes", () => { 125 const bFile: File = { 126 path: `${projectRoot}/b.ts`, 127 content: `import {C} from './c'; 128export class B 129{ 130 c = new C(); 131}` 132 }; 133 const cFile: File = { 134 path: `${projectRoot}/c.ts`, 135 content: `export class C 136{ 137 d = 1; 138}` 139 }; 140 verifyDeepImportChange( 141 "errors for .ts change", 142 bFile, 143 cFile 144 ); 145 }); 146 describe("updates errors when deep import through declaration file changes", () => { 147 const bFile: File = { 148 path: `${projectRoot}/b.d.ts`, 149 content: `import {C} from './c'; 150export class B 151{ 152 c: C; 153}` 154 }; 155 const cFile: File = { 156 path: `${projectRoot}/c.d.ts`, 157 content: `export class C 158{ 159 d: number; 160}` 161 }; 162 verifyDeepImportChange( 163 "errors for .d.ts change", 164 bFile, 165 cFile 166 ); 167 }); 168 }); 169 170 describe("updates errors in file not exporting a deep multilevel import that changes", () => { 171 const aFile: File = { 172 path: `${projectRoot}/a.ts`, 173 content: `export interface Point { 174 name: string; 175 c: Coords; 176} 177export interface Coords { 178 x2: number; 179 y: number; 180}` 181 }; 182 const bFile: File = { 183 path: `${projectRoot}/b.ts`, 184 content: `import { Point } from "./a"; 185export interface PointWrapper extends Point { 186}` 187 }; 188 const cFile: File = { 189 path: `${projectRoot}/c.ts`, 190 content: `import { PointWrapper } from "./b"; 191export function getPoint(): PointWrapper { 192 return { 193 name: "test", 194 c: { 195 x: 1, 196 y: 2 197 } 198 } 199};` 200 }; 201 const dFile: File = { 202 path: `${projectRoot}/d.ts`, 203 content: `import { getPoint } from "./c"; 204getPoint().c.x;` 205 }; 206 const eFile: File = { 207 path: `${projectRoot}/e.ts`, 208 content: `import "./d";` 209 }; 210 verifyEmitAndErrorUpdates({ 211 subScenario: "file not exporting a deep multilevel import that changes", 212 files: () => [aFile, bFile, cFile, dFile, eFile, config, libFile], 213 changes: [ 214 { 215 caption: "Rename property x2 to x of interface Coords to initialize signatures", 216 change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), 217 timeouts: runQueuedTimeoutCallbacks, 218 }, 219 { 220 caption: "Rename property x to x2 of interface Coords to revert back to original text", 221 change: sys => sys.writeFile(aFile.path, aFile.content.replace("x: number", "x2: number")), 222 timeouts: runQueuedTimeoutCallbacks, 223 }, 224 { 225 caption: "Rename property x2 to x of interface Coords", 226 change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), 227 timeouts: runQueuedTimeoutCallbacks, 228 }, 229 ] 230 }); 231 }); 232 describe("updates errors when file transitively exported file changes", () => { 233 const config: File = { 234 path: `${projectRoot}/tsconfig.json`, 235 content: JSON.stringify({ 236 files: ["app.ts"], 237 compilerOptions: { baseUrl: "." } 238 }) 239 }; 240 const app: File = { 241 path: `${projectRoot}/app.ts`, 242 content: `import { Data } from "lib2/public"; 243export class App { 244 public constructor() { 245 new Data().test(); 246 } 247}` 248 }; 249 const lib2Public: File = { 250 path: `${projectRoot}/lib2/public.ts`, 251 content: `export * from "./data";` 252 }; 253 const lib2Data: File = { 254 path: `${projectRoot}/lib2/data.ts`, 255 content: `import { ITest } from "lib1/public"; 256export class Data { 257 public test() { 258 const result: ITest = { 259 title: "title" 260 } 261 return result; 262 } 263}` 264 }; 265 const lib1Public: File = { 266 path: `${projectRoot}/lib1/public.ts`, 267 content: `export * from "./tools/public";` 268 }; 269 const lib1ToolsPublic: File = { 270 path: `${projectRoot}/lib1/tools/public.ts`, 271 content: `export * from "./tools.interface";` 272 }; 273 const lib1ToolsInterface: File = { 274 path: `${projectRoot}/lib1/tools/tools.interface.ts`, 275 content: `export interface ITest { 276 title: string; 277}` 278 }; 279 280 function verifyTransitiveExports(subScenario: string, files: readonly File[]) { 281 verifyEmitAndErrorUpdates({ 282 subScenario: `transitive exports/${subScenario}`, 283 files: () => [lib1ToolsInterface, lib1ToolsPublic, app, lib2Public, lib1Public, ...files, config, libFile], 284 changes: [ 285 { 286 caption: "Rename property title to title2 of interface ITest to initialize signatures", 287 change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), 288 timeouts: runQueuedTimeoutCallbacks, 289 }, 290 { 291 caption: "Rename property title2 to title of interface ITest to revert back to original text", 292 change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title2", "title")), 293 timeouts: runQueuedTimeoutCallbacks, 294 }, 295 { 296 caption: "Rename property title to title2 of interface ITest", 297 change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), 298 timeouts: runQueuedTimeoutCallbacks, 299 } 300 ] 301 }); 302 } 303 describe("when there are no circular import and exports", () => { 304 verifyTransitiveExports( 305 "no circular import/export", 306 [lib2Data] 307 ); 308 }); 309 describe("when there are circular import and exports", () => { 310 const lib2Data: File = { 311 path: `${projectRoot}/lib2/data.ts`, 312 content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2"; 313export class Data { 314 public dat?: Data2; public test() { 315 const result: ITest = { 316 title: "title" 317 } 318 return result; 319 } 320}` 321 }; 322 const lib2Data2: File = { 323 path: `${projectRoot}/lib2/data2.ts`, 324 content: `import { Data } from "./data"; 325export class Data2 { 326 public dat?: Data; 327}` 328 }; 329 verifyTransitiveExports( 330 "yes circular import/exports", 331 [lib2Data, lib2Data2] 332 ); 333 }); 334 }); 335 336 describe("with noEmitOnError", () => { 337 function change(caption: string, content: string): TscWatchCompileChange { 338 return { 339 caption, 340 change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), 341 // build project 342 timeouts: checkSingleTimeoutQueueLengthAndRun 343 }; 344 } 345 const noChange: TscWatchCompileChange = { 346 caption: "No change", 347 change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), 348 // build project 349 timeouts: checkSingleTimeoutQueueLengthAndRun, 350 }; 351 verifyEmitAndErrorUpdates({ 352 subScenario: "with noEmitOnError", 353 currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError`, 354 files: () => ["shared/types/db.ts", "src/main.ts", "src/other.ts", "tsconfig.json"] 355 .map(f => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)).concat({ path: libFile.path, content: libContent }), 356 changes: [ 357 noChange, 358 change("Fix Syntax error", `import { A } from "../shared/types/db"; 359const a = { 360 lastName: 'sdsd' 361};`), 362 change("Semantic Error", `import { A } from "../shared/types/db"; 363const a: string = 10;`), 364 noChange, 365 change("Fix Semantic Error", `import { A } from "../shared/types/db"; 366const a: string = "hello";`), 367 noChange, 368 ], 369 }); 370 }); 371 }); 372} 373