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