From c54e20ab7ae9b7b08af827587dc30fd281291ecc Mon Sep 17 00:00:00 2001 From: John Crim Date: Fri, 29 Jan 2021 03:31:04 -0800 Subject: [PATCH] fix(compiler): update `SourceFile` with correct file content (#765) Previously SourceFile.text was the old contents of the file - however the file was during startup. This change updates the SourceFile text so that emit() receives the changed file contents. Closes #764 Co-authored-by: Ahn --- .../ng-jest-compiler.spec.ts.snap | 44 +++++++++++++++++++ src/__tests__/ng-jest-compiler.spec.ts | 18 ++++++++ src/compiler/ng-jest-compiler.ts | 21 ++++++--- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap b/src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap index 2036a9d0db..146be42f6f 100644 --- a/src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap +++ b/src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap @@ -20,6 +20,50 @@ export { AppComponent }; //# " `; +exports[`NgJestCompiler with isolatedModule false should compile new code when file changes: from hasErrorFileContent 1`] = ` +"\\"use strict\\"; +Object.defineProperty(exports, \\"__esModule\\", { value: true }); +exports.AppComponent = void 0; +const tslib_1 = require(\\"tslib\\"); +const core_1 = require(\\"@angular/core\\"); +let = class { + constructor() { + this. = ; + } +}; + = tslib_1.__decorate([ + core_1.Component({ + selector: 'app-root', + template: require(\\"./app.component.html\\"), + styles: [] + }) +], ); +exports.AppComponent = ; +//# " +`; + +exports[`NgJestCompiler with isolatedModule false should compile new code when file changes: from noErrorFileContent 1`] = ` +"\\"use strict\\"; +Object.defineProperty(exports, \\"__esModule\\", { value: true }); +exports.AppComponent = void 0; +const tslib_1 = require(\\"tslib\\"); +const core_1 = require(\\"@angular/core\\"); +let AppComponent = class AppComponent { + constructor() { + this.title = 'test-app-v10'; + } +}; +AppComponent = tslib_1.__decorate([ + core_1.Component({ + selector: 'app-root', + template: require(\\"./app.component.html\\"), + styles: [] + }) +], AppComponent); +exports.AppComponent = AppComponent; +//# " +`; + exports[`NgJestCompiler with isolatedModule false should throw diagnostics error for new file which is: known by Program 1`] = `"src/__tests__/__mocks__/foo.component.ts(8,3): error TS2322: Type 'string' is not assignable to type 'number'."`; exports[`NgJestCompiler with isolatedModule false should throw diagnostics error for new file which is: not known by Program 1`] = `"src/__tests__/__mocks__/foo.component.ts(8,3): error TS2322: Type 'string' is not assignable to type 'number'."`; diff --git a/src/__tests__/ng-jest-compiler.spec.ts b/src/__tests__/ng-jest-compiler.spec.ts index 0982b5cb51..764076ac59 100644 --- a/src/__tests__/ng-jest-compiler.spec.ts +++ b/src/__tests__/ng-jest-compiler.spec.ts @@ -123,6 +123,24 @@ describe('NgJestCompiler', () => { }, ); + test('should compile new code when file changes', () => { + const ngJestConfig = new NgJestConfig(jestCfgStub); + const compiler = new NgJestCompiler(ngJestConfig, new Map()); + + // Compile the same file with 2 versions of content: noErrorFileContent and hasErrorFileContent + const fileName = noErrorFileName; + const emittedResult1 = compiler.getCompiledOutput(fileName, noErrorFileContent, true); + expect(emittedResult1.substring(0, emittedResult1.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot( + 'from noErrorFileContent', + ); + const emittedResult2 = compiler.getCompiledOutput(fileName, hasErrorFileContent, true); + expect(emittedResult2.substring(0, emittedResult2.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot( + 'from hasErrorFileContent', + ); + + expect(emittedResult1).not.toEqual(emittedResult2); + }); + test('should compile codes with useESM true', () => { const ngJestConfig = new NgJestConfig({ ...jestCfgStub, diff --git a/src/compiler/ng-jest-compiler.ts b/src/compiler/ng-jest-compiler.ts index d4caf04017..a1b4bb0e2e 100644 --- a/src/compiler/ng-jest-compiler.ts +++ b/src/compiler/ng-jest-compiler.ts @@ -70,20 +70,27 @@ export class NgJestCompiler implements CompilerInstance { esModuleInterop, module: moduleKind, }; + if (this._program) { - const allDiagnostics: ts.Diagnostic[] = []; - if (!this._rootNames.includes(fileName)) { - this._logger.debug({ fileName }, 'getCompiledOutput: update memory host, rootFiles and Program'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._tsHost!.updateMemoryHost(fileName, fileContent); + let sourceFile: ts.SourceFile | undefined; + if (!this._rootNames.includes(fileName)) { + this._logger.debug({ fileName }, 'getCompiledOutput: adding file to rootNames and updating Program'); this._rootNames = [...this._rootNames, fileName]; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this._tsHost!.updateMemoryHost(fileName, fileContent); this._createOrUpdateProgram(); + sourceFile = this._program.getSourceFile(fileName); + } else { + sourceFile = this._program.getSourceFile(fileName); + if (sourceFile) { + const replaceSpan: ts.TextSpan = { start: 0, length: sourceFile.text.length }; + sourceFile.update(fileContent, { span: replaceSpan, newLength: fileContent.length }); + } } this._logger.debug({ fileName }, 'getCompiledOutput: compiling using Program'); - const sourceFile = this._program.getSourceFile(fileName); const emitResult = this._program.emit(sourceFile, undefined, undefined, undefined, { ...customTransformers, before: [ @@ -98,8 +105,10 @@ export class NgJestCompiler implements CompilerInstance { replaceResources(this.isAppPath, this._program.getTypeChecker), ], }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const compiledOutput: [string, string] = this._tsHost!.getEmittedResult(); + const allDiagnostics: ts.Diagnostic[] = []; if (this.ngJestConfig.shouldReportDiagnostics(fileName)) { this._logger.debug({ fileName }, 'getCompiledOutput: getting diagnostics');