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