1namespace ts.tscWatch { 2 const scenario = "emit"; 3 describe("unittests:: tsc-watch:: emit with outFile or out setting", () => { 4 function verifyOutAndOutFileSetting(subScenario: string, out?: string, outFile?: string) { 5 verifyTscWatch({ 6 scenario, 7 subScenario: `emit with outFile or out setting/${subScenario}`, 8 commandLineArgs: ["--w", "-p", "/a/tsconfig.json"], 9 sys: () => { 10 const config: File = { 11 path: "/a/tsconfig.json", 12 content: JSON.stringify({ compilerOptions: { out, outFile } }) 13 }; 14 const f1: File = { 15 path: "/a/a.ts", 16 content: "let x = 1" 17 }; 18 const f2: File = { 19 path: "/a/b.ts", 20 content: "let y = 1" 21 }; 22 return createWatchedSystem([f1, f2, config, libFile]); 23 }, 24 changes: [ 25 { 26 caption: "Make change in the file", 27 change: sys => sys.writeFile("/a/a.ts", "let x = 11"), 28 timeouts: runQueuedTimeoutCallbacks 29 } 30 ] 31 }); 32 } 33 verifyOutAndOutFileSetting("config does not have out or outFile"); 34 verifyOutAndOutFileSetting("config has out", "/a/out.js"); 35 verifyOutAndOutFileSetting("config has outFile", /*out*/ undefined, "/a/out.js"); 36 37 function verifyFilesEmittedOnce(subScenario: string, useOutFile: boolean) { 38 verifyTscWatch({ 39 scenario, 40 subScenario: `emit with outFile or out setting/${subScenario}`, 41 commandLineArgs: ["--w", "-p", "/a/b/project/tsconfig.json"], 42 sys: () => { 43 const file1: File = { 44 path: "/a/b/output/AnotherDependency/file1.d.ts", 45 content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }" 46 }; 47 const file2: File = { 48 path: "/a/b/dependencies/file2.d.ts", 49 content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }" 50 }; 51 const file3: File = { 52 path: "/a/b/project/src/main.ts", 53 content: "namespace Main { export function fooBar() {} }" 54 }; 55 const file4: File = { 56 path: "/a/b/project/src/main2.ts", 57 content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }" 58 }; 59 const configFile: File = { 60 path: "/a/b/project/tsconfig.json", 61 content: JSON.stringify({ 62 compilerOptions: useOutFile ? 63 { outFile: "../output/common.js", target: "es5" } : 64 { outDir: "../output", target: "es5" }, 65 files: [file1.path, file2.path, file3.path, file4.path] 66 }) 67 }; 68 return createWatchedSystem([file1, file2, file3, file4, libFile, configFile]); 69 }, 70 changes: emptyArray 71 }); 72 } 73 verifyFilesEmittedOnce("with --outFile and multiple declaration files in the program", /*useOutFile*/ true); 74 verifyFilesEmittedOnce("without --outFile and multiple declaration files in the program", /*useOutFile*/ false); 75 }); 76 77 describe("unittests:: tsc-watch:: emit for configured projects", () => { 78 const file1Consumer1Path = "/a/b/file1Consumer1.ts"; 79 const file1Consumer2Path = "/a/b/file1Consumer2.ts"; 80 const moduleFile1Path = "/a/b/moduleFile1.ts"; 81 const moduleFile2Path = "/a/b/moduleFile2.ts"; 82 const globalFilePath = "/a/b/globalFile3.ts"; 83 const configFilePath = "/a/b/tsconfig.json"; 84 interface VerifyTscWatchEmit { 85 subScenario: string; 86 /** custom config file options */ 87 configObj?: any; 88 /** Additional files and folders to add */ 89 getAdditionalFileOrFolder?: () => File[]; 90 /** initial list of files to emit if not the default list */ 91 firstReloadFileList?: string[]; 92 changes: TscWatchCompileChange[] 93 } 94 function verifyTscWatchEmit({ 95 subScenario, 96 configObj, 97 getAdditionalFileOrFolder, 98 firstReloadFileList, 99 changes 100 }: VerifyTscWatchEmit) { 101 verifyTscWatch({ 102 scenario, 103 subScenario: `emit for configured projects/${subScenario}`, 104 commandLineArgs: ["--w", "-p", configFilePath], 105 sys: () => { 106 const moduleFile1: File = { 107 path: moduleFile1Path, 108 content: "export function Foo() { };", 109 }; 110 111 const file1Consumer1: File = { 112 path: file1Consumer1Path, 113 content: `import {Foo} from "./moduleFile1"; export var y = 10;`, 114 }; 115 116 const file1Consumer2: File = { 117 path: file1Consumer2Path, 118 content: `import {Foo} from "./moduleFile1"; let z = 10;`, 119 }; 120 121 const moduleFile2: File = { 122 path: moduleFile2Path, 123 content: `export var Foo4 = 10;`, 124 }; 125 126 const globalFile3: File = { 127 path: globalFilePath, 128 content: `interface GlobalFoo { age: number }` 129 }; 130 const configFile: File = { 131 path: configFilePath, 132 content: JSON.stringify(configObj || {}) 133 }; 134 const additionalFiles = getAdditionalFileOrFolder?.() || emptyArray; 135 const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles]; 136 return createWatchedSystem(firstReloadFileList ? 137 map(firstReloadFileList, fileName => find(files, file => file.path === fileName)!) : 138 files 139 ); 140 }, 141 changes 142 }); 143 } 144 145 function modifyModuleFile1Shape(sys: WatchedSystem) { 146 sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { };`); 147 } 148 const changeModuleFile1Shape: TscWatchCompileChange = { 149 caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { };`", 150 change: modifyModuleFile1Shape, 151 timeouts: checkSingleTimeoutQueueLengthAndRun, 152 }; 153 154 verifyTscWatchEmit({ 155 subScenario: "should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", 156 changes: [ 157 changeModuleFile1Shape, 158 { 159 caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`", 160 change: sys => sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { console.log('hi'); };`), 161 timeouts: checkSingleTimeoutQueueLengthAndRun, 162 } 163 ] 164 }); 165 166 verifyTscWatchEmit({ 167 subScenario: "should be up-to-date with the reference map changes", 168 changes: [ 169 { 170 caption: "Change file1Consumer1 content to `export let y = Foo();`", 171 change: sys => sys.writeFile(file1Consumer1Path, `export let y = Foo();`), 172 timeouts: checkSingleTimeoutQueueLengthAndRun, 173 }, 174 changeModuleFile1Shape, 175 { 176 caption: "Add the import statements back to file1Consumer1", 177 change: sys => sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`), 178 timeouts: checkSingleTimeoutQueueLengthAndRun, 179 }, 180 { 181 caption: "Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`", 182 change: sys => sys.writeFile(moduleFile1Path, `export let y = Foo();`), 183 timeouts: checkSingleTimeoutQueueLengthAndRun, 184 }, 185 { 186 caption: "Multiple file edits in one go", 187 // Change file1Consumer1 content to `export let y = Foo();` 188 // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` 189 change: sys => { 190 sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`); 191 modifyModuleFile1Shape(sys); 192 }, 193 timeouts: checkSingleTimeoutQueueLengthAndRun, 194 } 195 ] 196 }); 197 198 verifyTscWatchEmit({ 199 subScenario: "should be up-to-date with deleted files", 200 changes: [ 201 { 202 caption: "change moduleFile1 shape and delete file1Consumer2", 203 change: sys => { 204 modifyModuleFile1Shape(sys); 205 sys.deleteFile(file1Consumer2Path); 206 }, 207 timeouts: checkSingleTimeoutQueueLengthAndRun, 208 } 209 ] 210 }); 211 212 verifyTscWatchEmit({ 213 subScenario: "should be up-to-date with newly created files", 214 changes: [ 215 { 216 caption: "change moduleFile1 shape and create file1Consumer3", 217 change: sys => { 218 sys.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); 219 modifyModuleFile1Shape(sys); 220 }, 221 timeouts: checkSingleTimeoutQueueLengthAndRun, 222 } 223 ] 224 }); 225 226 verifyTscWatchEmit({ 227 subScenario: "should detect changes in non-root files", 228 configObj: { files: [file1Consumer1Path] }, 229 changes: [ 230 changeModuleFile1Shape, 231 { 232 caption: "change file1 internal, and verify only file1 is affected", 233 change: sys => sys.appendFile(moduleFile1Path, "var T1: number;"), 234 timeouts: checkSingleTimeoutQueueLengthAndRun, 235 } 236 ] 237 }); 238 239 verifyTscWatchEmit({ 240 subScenario: "should return all files if a global file changed shape", 241 changes: [ 242 { 243 caption: "change shape of global file", 244 change: sys => sys.appendFile(globalFilePath, "var T2: string;"), 245 timeouts: checkSingleTimeoutQueueLengthAndRun, 246 } 247 ] 248 }); 249 250 verifyTscWatchEmit({ 251 subScenario: "should always return the file itself if '--isolatedModules' is specified", 252 configObj: { compilerOptions: { isolatedModules: true } }, 253 changes: [ 254 changeModuleFile1Shape 255 ] 256 }); 257 258 verifyTscWatchEmit({ 259 subScenario: "should always return the file itself if '--out' or '--outFile' is specified", 260 configObj: { compilerOptions: { module: "system", outFile: "/a/b/out.js" } }, 261 changes: [ 262 changeModuleFile1Shape 263 ] 264 }); 265 266 verifyTscWatchEmit({ 267 subScenario: "should return cascaded affected file list", 268 getAdditionalFileOrFolder: () => [{ 269 path: "/a/b/file1Consumer1Consumer1.ts", 270 content: `import {y} from "./file1Consumer1";` 271 }], 272 changes: [ 273 { 274 caption: "change file1Consumer1", 275 change: sys => sys.appendFile(file1Consumer1Path, "export var T: number;"), 276 timeouts: checkSingleTimeoutQueueLengthAndRun, 277 }, 278 changeModuleFile1Shape, 279 { 280 caption: "change file1Consumer1 and moduleFile1", 281 change: sys => { 282 sys.appendFile(file1Consumer1Path, "export var T2: number;"); 283 sys.writeFile(moduleFile1Path, `export var T2: number;export function Foo() { };`); 284 }, 285 timeouts: checkSingleTimeoutQueueLengthAndRun, 286 } 287 ] 288 }); 289 290 verifyTscWatchEmit({ 291 subScenario: "should work fine for files with circular references", 292 getAdditionalFileOrFolder: () => [ 293 { 294 path: "/a/b/file1.ts", 295 content: `/// <reference path="./file2.ts" /> 296export var t1 = 10;` 297 }, 298 { 299 path: "/a/b/file2.ts", 300 content: `/// <reference path="./file1.ts" /> 301export var t2 = 10;` 302 } 303 ], 304 firstReloadFileList: [libFile.path, "/a/b/file1.ts", "/a/b/file2.ts", configFilePath], 305 changes: [ 306 { 307 caption: "change file1", 308 change: sys => sys.appendFile("/a/b/file1.ts", "export var t3 = 10;"), 309 timeouts: checkSingleTimeoutQueueLengthAndRun, 310 } 311 ] 312 }); 313 314 verifyTscWatchEmit({ 315 subScenario: "should detect removed code file", 316 getAdditionalFileOrFolder: () => [{ 317 path: "/a/b/referenceFile1.ts", 318 content: `/// <reference path="./moduleFile1.ts" /> 319export var x = Foo();` 320 }], 321 firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", moduleFile1Path, configFilePath], 322 changes: [ 323 { 324 caption: "delete moduleFile1", 325 change: sys => sys.deleteFile(moduleFile1Path), 326 timeouts: checkSingleTimeoutQueueLengthAndRun, 327 } 328 ] 329 }); 330 331 verifyTscWatchEmit({ 332 subScenario: "should detect non existing code file", 333 getAdditionalFileOrFolder: () => [{ 334 path: "/a/b/referenceFile1.ts", 335 content: `/// <reference path="./moduleFile2.ts" /> 336export var x = Foo();` 337 }], 338 firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", configFilePath], 339 changes: [ 340 { 341 caption: "edit refereceFile1", 342 change: sys => sys.appendFile("/a/b/referenceFile1.ts", "export var yy = Foo();"), 343 timeouts: checkSingleTimeoutQueueLengthAndRun, 344 }, 345 { 346 caption: "create moduleFile2", 347 change: sys => sys.writeFile(moduleFile2Path, "export var Foo4 = 10;"), 348 timeouts: checkSingleTimeoutQueueLengthAndRun, 349 } 350 ] 351 }); 352 }); 353 354 describe("unittests:: tsc-watch:: emit file content", () => { 355 function verifyNewLine(subScenario: string, newLine: string) { 356 verifyTscWatch({ 357 scenario, 358 subScenario: `emit file content/${subScenario}`, 359 commandLineArgs: ["--w", "/a/app.ts"], 360 sys: () => createWatchedSystem( 361 [ 362 { 363 path: "/a/app.ts", 364 content: ["var x = 1;", "var y = 2;"].join(newLine) 365 }, 366 libFile 367 ], 368 { newLine } 369 ), 370 changes: [ 371 { 372 caption: "Append a line", 373 change: sys => sys.appendFile("/a/app.ts", newLine + "var z = 3;"), 374 timeouts: checkSingleTimeoutQueueLengthAndRun, 375 } 376 ], 377 }); 378 } 379 verifyNewLine("handles new lines lineFeed", "\n"); 380 verifyNewLine("handles new lines carriageReturn lineFeed", "\r\n"); 381 382 verifyTscWatch({ 383 scenario, 384 subScenario: "emit file content/should emit specified file", 385 commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], 386 sys: () => { 387 const file1 = { 388 path: "/a/b/f1.ts", 389 content: `export function Foo() { return 10; }` 390 }; 391 392 const file2 = { 393 path: "/a/b/f2.ts", 394 content: `import {Foo} from "./f1"; export let y = Foo();` 395 }; 396 397 const file3 = { 398 path: "/a/b/f3.ts", 399 content: `import {y} from "./f2"; let x = y;` 400 }; 401 402 const configFile = { 403 path: "/a/b/tsconfig.json", 404 content: "{}" 405 }; 406 return createWatchedSystem([file1, file2, file3, configFile, libFile]); 407 }, 408 changes: [ 409 { 410 caption: "Append content to f1", 411 change: sys => sys.appendFile("/a/b/f1.ts", "export function foo2() { return 2; }"), 412 timeouts: checkSingleTimeoutQueueLengthAndRun, 413 } 414 ], 415 }); 416 417 verifyTscWatch({ 418 scenario, 419 subScenario: "emit file content/elides const enums correctly in incremental compilation", 420 commandLineArgs: ["-w", "/user/someone/projects/myproject/file3.ts"], 421 sys: () => { 422 const currentDirectory = "/user/someone/projects/myproject"; 423 const file1: File = { 424 path: `${currentDirectory}/file1.ts`, 425 content: "export const enum E1 { V = 1 }" 426 }; 427 const file2: File = { 428 path: `${currentDirectory}/file2.ts`, 429 content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }` 430 }; 431 const file3: File = { 432 path: `${currentDirectory}/file3.ts`, 433 content: `import { E2 } from "./file2"; const v: E2 = E2.V;` 434 }; 435 return createWatchedSystem([file1, file2, file3, libFile]); 436 }, 437 changes: [ 438 { 439 caption: "Append content to file3", 440 change: sys => sys.appendFile("/user/someone/projects/myproject/file3.ts", "function foo2() { return 2; }"), 441 timeouts: checkSingleTimeoutQueueLengthAndRun, 442 } 443 ], 444 }); 445 446 verifyTscWatch({ 447 scenario, 448 subScenario: "emit file content/file is deleted and created as part of change", 449 commandLineArgs: ["-w"], 450 sys: () => { 451 const projectLocation = "/home/username/project"; 452 const file: File = { 453 path: `${projectLocation}/app/file.ts`, 454 content: "var a = 10;" 455 }; 456 const configFile: File = { 457 path: `${projectLocation}/tsconfig.json`, 458 content: JSON.stringify({ 459 include: [ 460 "app/**/*.ts" 461 ] 462 }) 463 }; 464 const files = [file, configFile, libFile]; 465 return createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true }); 466 }, 467 changes: [ 468 { 469 caption: "file is deleted and then created to modify content", 470 change: sys => sys.appendFile("/home/username/project/app/file.ts", "\nvar b = 10;", { invokeFileDeleteCreateAsPartInsteadOfChange: true }), 471 timeouts: checkSingleTimeoutQueueLengthAndRun, 472 } 473 ] 474 }); 475 }); 476 477 describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => { 478 verifyTscWatch({ 479 scenario, 480 subScenario: "when module emit is specified as node/when instead of filechanged recursive directory watcher is invoked", 481 commandLineArgs: ["--w", "--p", "/a/rootFolder/project/tsconfig.json"], 482 sys: () => { 483 const configFile: File = { 484 path: "/a/rootFolder/project/tsconfig.json", 485 content: JSON.stringify({ 486 compilerOptions: { 487 module: "none", 488 allowJs: true, 489 outDir: "Static/scripts/" 490 }, 491 include: [ 492 "Scripts/**/*" 493 ], 494 }) 495 }; 496 const file1: File = { 497 path: "/a/rootFolder/project/Scripts/TypeScript.ts", 498 content: "var z = 10;" 499 }; 500 const file2: File = { 501 path: "/a/rootFolder/project/Scripts/Javascript.js", 502 content: "var zz = 10;" 503 }; 504 return createWatchedSystem([configFile, file1, file2, libFile]); 505 }, 506 changes: [ 507 { 508 caption: "Modify typescript file", 509 change: sys => sys.modifyFile( 510 "/a/rootFolder/project/Scripts/TypeScript.ts", 511 "var zz30 = 100;", 512 { invokeDirectoryWatcherInsteadOfFileChanged: true }, 513 ), 514 timeouts: runQueuedTimeoutCallbacks, 515 } 516 ], 517 }); 518 }); 519} 520