diff --git a/package.json b/package.json index 056c6e9807..551af7b7f9 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dependencies": { "jest-environment-jsdom": "27.0.0-next.1", "pretty-format": "27.0.0-next.1", - "ts-jest": "^27.0.0-next.5" + "ts-jest": "^27.0.0-next.6" }, "peerDependencies": { "@angular-devkit/build-angular": ">=0.901.12", diff --git a/src/__tests__/__mocks__/foo.component.ts b/src/__tests__/__mocks__/foo.component.ts deleted file mode 100644 index 4290a3cdf1..0000000000 --- a/src/__tests__/__mocks__/foo.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'foo-root', - template: `

Hello World !!

`, -}) -export class FooComponent { - title: number = 'test-app-v10'; -} diff --git a/src/__tests__/__mocks__/foo.service.ts b/src/__tests__/__mocks__/foo.service.ts deleted file mode 100644 index bed2626d6f..0000000000 --- a/src/__tests__/__mocks__/foo.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable } from '@angular/core'; - -export class ClassInject {} - -@Injectable() -export class MyService { - // eslint-disable-next-line - constructor(_v: ClassInject) {} -} diff --git a/src/__tests__/__mocks__/foo.spec.ts b/src/__tests__/__mocks__/foo.spec.ts deleted file mode 100644 index 29818aa937..0000000000 --- a/src/__tests__/__mocks__/foo.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { jest } from '@jest/globals'; - -import { getFoo } from './foo'; - -jest.mock('./foo'); - -console.log(getFoo()); diff --git a/src/__tests__/__mocks__/foo.ts b/src/__tests__/__mocks__/foo.ts deleted file mode 100644 index d39beb2f5c..0000000000 --- a/src/__tests__/__mocks__/foo.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function getFoo(): string { - return 'foo'; -} diff --git a/src/__tests__/__mocks__/forward-ref.ts b/src/__tests__/__mocks__/forward-ref.ts deleted file mode 100644 index 0d8817c254..0000000000 --- a/src/__tests__/__mocks__/forward-ref.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { forwardRef, Inject } from '@angular/core'; - -export class Door { - lock: Lock; - - // Door attempts to inject Lock, despite it not being defined yet. - // forwardRef makes this possible. - constructor(@Inject(forwardRef(() => Lock)) lock: Lock) { - this.lock = lock; - } -} - -// Only at this point Lock is defined. -export class Lock {} diff --git a/src/__tests__/__snapshots__/downlevel-ctor.spec.ts.snap b/src/__tests__/__snapshots__/downlevel-ctor.spec.ts.snap deleted file mode 100644 index fd11978f99..0000000000 --- a/src/__tests__/__snapshots__/downlevel-ctor.spec.ts.snap +++ /dev/null @@ -1,47 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Downlevel ctor transformer should use downlevel ctor transformer from Angular for isolatedModules false/true 1`] = ` -"\\"use strict\\"; -Object.defineProperty(exports, \\"__esModule\\", { value: true }); -exports.Lock = exports.Door = void 0; -const core_1 = require(\\"@angular/core\\"); -class Door { - // Door attempts to inject Lock, despite it not being defined yet. - // forwardRef makes this possible. - constructor(lock) { - this.lock = lock; - } -} -exports.Door = Door; -Door.ctorParameters = () => [ - { type: Lock, decorators: [{ type: core_1.Inject, args: [core_1.forwardRef(() => Lock),] }] } -]; -// Only at this point Lock is defined. -class Lock { -} -exports.Lock = Lock; -//# " -`; - -exports[`Downlevel ctor transformer should use downlevel ctor transformer from Angular for isolatedModules false/true 2`] = ` -"\\"use strict\\"; -Object.defineProperty(exports, \\"__esModule\\", { value: true }); -exports.Lock = exports.Door = void 0; -const core_1 = require(\\"@angular/core\\"); -class Door { - // Door attempts to inject Lock, despite it not being defined yet. - // forwardRef makes this possible. - constructor(lock) { - this.lock = lock; - } -} -exports.Door = Door; -Door.ctorParameters = () => [ - { type: Lock, decorators: [{ type: core_1.Inject, args: [core_1.forwardRef(() => Lock),] }] } -]; -// Only at this point Lock is defined. -class Lock { -} -exports.Lock = Lock; -//# " -`; diff --git a/src/__tests__/__snapshots__/hoisting.spec.ts.snap b/src/__tests__/__snapshots__/hoisting.spec.ts.snap deleted file mode 100644 index 7adcdee381..0000000000 --- a/src/__tests__/__snapshots__/hoisting.spec.ts.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Hoisting should hoist correctly with isolatedModules false 1`] = ` -"\\"use strict\\"; -Object.defineProperty(exports, \\"__esModule\\", { value: true }); -const globals_1 = require(\\"@jest/globals\\"); -globals_1.jest.mock('./foo'); -const foo_1 = require(\\"./foo\\"); -console.log(foo_1.getFoo()); -//# " -`; - -exports[`Hoisting should hoist correctly with isolatedModules true 1`] = ` -"\\"use strict\\"; -Object.defineProperty(exports, \\"__esModule\\", { value: true }); -const globals_1 = require(\\"@jest/globals\\"); -globals_1.jest.mock('./foo'); -const foo_1 = require(\\"./foo\\"); -console.log(foo_1.getFoo()); -//# " -`; diff --git a/src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap b/src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap index 146be42f6f..39bccf0224 100644 --- a/src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap +++ b/src/__tests__/__snapshots__/ng-jest-compiler.spec.ts.snap @@ -1,69 +1,41 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`NgJestCompiler with isolatedModule false should compile codes with useESM true 1`] = ` -"import { __decorate } from \\"tslib\\"; -import __NG_CLI_RESOURCE__0 from \\"./app.component.html\\"; -import { Component } from '@angular/core'; -let AppComponent = class AppComponent { - constructor() { - this.title = 'test-app-v10'; - } -}; -AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: __NG_CLI_RESOURCE__0, - styles: [] - }) -], AppComponent); -export { AppComponent }; -//# " +exports[`NgJestCompiler with isolatedModules false should transform codes with useESM false using hoisting, replace resources and downlevel ctor transformers 1`] = ` +Object { + "before": Array [ + [Function], + [Function], + [Function], + ], +} `; -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 isolatedModules false should transform codes with useESM true using hoisting, replace resources and downlevel ctor transformers 1`] = ` +Object { + "before": Array [ + [Function], + [Function], + [Function], + ], +} `; -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 isolatedModules true should transform codes with useESM false using hoisting, replace resources and downlevel ctor transformers 1`] = ` +Object { + "before": Array [ + [Function], + [Function], + [Function], + ], +} `; -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'."`; +exports[`NgJestCompiler with isolatedModules true should transform codes with useESM true using hoisting, replace resources and downlevel ctor transformers 1`] = ` +Object { + "before": Array [ + [Function], + [Function], + [Function], + ], +} +`; diff --git a/src/__tests__/__snapshots__/replace-resources.spec.ts.snap b/src/__tests__/__snapshots__/replace-resources.spec.ts.snap deleted file mode 100644 index 853eaf6e61..0000000000 --- a/src/__tests__/__snapshots__/replace-resources.spec.ts.snap +++ /dev/null @@ -1,85 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Replace resources transformer with isolatedModules false should use replaceResources transformer from @angular/compiler-cli with useESM false 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[`Replace resources transformer with isolatedModules false should use replaceResources transformer from @angular/compiler-cli with useESM true 1`] = ` -"import { __decorate } from \\"tslib\\"; -import __NG_CLI_RESOURCE__0 from \\"./app.component.html\\"; -import { Component } from '@angular/core'; -let AppComponent = class AppComponent { - constructor() { - this.title = 'test-app-v10'; - } -}; -AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: __NG_CLI_RESOURCE__0, - styles: [] - }) -], AppComponent); -export { AppComponent }; -//# " -`; - -exports[`Replace resources transformer with isolatedModules true should use replaceResources transformer from @angular/compiler-cli with useESM false 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[`Replace resources transformer with isolatedModules true should use replaceResources transformer from @angular/compiler-cli with useESM true 1`] = ` -"import { __decorate } from \\"tslib\\"; -import __NG_CLI_RESOURCE__0 from \\"./app.component.html\\"; -import { Component } from '@angular/core'; -let AppComponent = class AppComponent { - constructor() { - this.title = 'test-app-v10'; - } -}; -AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: __NG_CLI_RESOURCE__0, - styles: [] - }) -], AppComponent); -export { AppComponent }; -//# " -`; diff --git a/src/__tests__/downlevel-ctor.spec.ts b/src/__tests__/downlevel-ctor.spec.ts deleted file mode 100644 index 83774f77a0..0000000000 --- a/src/__tests__/downlevel-ctor.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; - -import { SOURCE_MAPPING_PREFIX } from 'ts-jest/dist/compiler/compiler-utils'; - -import { NgJestCompiler } from '../compiler/ng-jest-compiler'; -import { NgJestConfig } from '../config/ng-jest-config'; - -import { jestCfgStub } from './__helpers__/test-constants'; -import { mockFolder } from './__helpers__/test-helpers'; - -describe('Downlevel ctor transformer', () => { - const fileName = join(mockFolder, 'forward-ref.ts'); - const fileContent = readFileSync(fileName, 'utf-8'); - - test.each([true, false])( - 'should use downlevel ctor transformer from Angular for isolatedModules false/true', - (isolatedModules) => { - const ngJestConfig = new NgJestConfig({ - ...jestCfgStub, - globals: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - 'ts-jest': { - ...jestCfgStub.globals['ts-jest'], - isolatedModules, - }, - }, - }); - const compiler = new NgJestCompiler(ngJestConfig, new Map()); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const emittedResult = compiler.getCompiledOutput(fileName, fileContent, false)!; - - // Source map is different based on file location which can fail on CI, so we only compare snapshot for js - expect(emittedResult.substring(0, emittedResult.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot(); - }, - ); -}); diff --git a/src/__tests__/hoisting.spec.ts b/src/__tests__/hoisting.spec.ts deleted file mode 100644 index 53034432b0..0000000000 --- a/src/__tests__/hoisting.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; - -import { SOURCE_MAPPING_PREFIX } from 'ts-jest/dist/compiler/compiler-utils'; - -import { NgJestCompiler } from '../compiler/ng-jest-compiler'; -import { NgJestConfig } from '../config/ng-jest-config'; - -import { jestCfgStub } from './__helpers__/test-constants'; -import { mockFolder } from './__helpers__/test-helpers'; - -describe('Hoisting', () => { - // Verify if we use `ts-jest` hoisting transformer - test.each([true, false])('should hoist correctly with isolatedModules %p', (isolatedModules) => { - const ngJestConfig = new NgJestConfig({ - ...jestCfgStub, - globals: { - 'ts-jest': { - ...jestCfgStub.globals['ts-jest'], - isolatedModules, - }, - }, - }); - const fileName = join(mockFolder, 'foo.spec.ts'); - const compiler = new NgJestCompiler(ngJestConfig, new Map()); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const emittedResult = compiler.getCompiledOutput(fileName, readFileSync(fileName, 'utf-8'), false)!; - - // Source map is different based on file location which can fail on CI, so we only compare snapshot for js - expect(emittedResult.substring(0, emittedResult.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot(); - }); -}); diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index 3ba5c7e42c..3c87eb5f67 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -1,137 +1,12 @@ -import { jest } from '@jest/globals'; -import { TsJestTransformer } from 'ts-jest/dist/ts-jest-transformer'; - -import { NgJestCompiler } from '../compiler/ng-jest-compiler'; +import * as ngJest from '..'; import { NgJestTransformer } from '../ng-jest-transformer'; -describe('NgJestTransformer', () => { - describe('_configsFor', () => { - test( - 'should return the same config set for same values with different jest config objects' + - ' but their serialized versions are the same', - () => { - const obj1 = { - config: { cwd: process.cwd(), extensionsToTreatAsEsm: [], globals: {}, testMatch: [], testRegex: [] }, - cacheFS: new Map(), - }; - const obj2 = { ...obj1, config: { ...obj1.config, globals: {} } }; - // @ts-expect-error testing purpose - const cs1 = new NgJestTransformer()._configsFor(obj1); - // @ts-expect-error testing purpose - const cs2 = new NgJestTransformer()._configsFor(obj2); - - expect(cs2).toBe(cs1); - }, - ); - - test('should return the same config set for same values with jest config objects', () => { - const obj1 = { - config: { cwd: process.cwd(), extensionsToTreatAsEsm: [], globals: {}, testMatch: [], testRegex: [] }, - cacheFS: new Map(), - }; - const obj2 = { ...obj1 }; - // @ts-expect-error testing purpose - const cs1 = new NgJestTransformer()._configsFor(obj1); - // @ts-expect-error testing purpose - const cs2 = new NgJestTransformer()._configsFor(obj2); - - expect(cs2).toBe(cs1); - }); - }); - - describe('getCacheKey', () => { - test('should call getCacheKey method from parent class TsJestTransformer', () => { - TsJestTransformer.prototype.getCacheKey = jest.fn(); - const input = { - fileContent: 'export default "foo"', - fileName: 'foo.ts', - jestConfigStr: '{"foo": "bar"}', - // eslint-disable-next-line - options: { config: { foo: 'bar' } as any, instrument: false, rootDir: '/foo' }, - }; - const tr = new NgJestTransformer(); - // @ts-expect-error testing purpose - tr.getCacheKey(input.fileContent, input.fileName, input.jestConfigStr, input.options); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(TsJestTransformer.prototype.getCacheKey).toHaveBeenCalledWith( - input.fileContent, - input.fileName, - input.jestConfigStr, - input.options, - ); - }); - }); - - describe('process', () => { - const baseJestCfg = { - cwd: './', - testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], - testRegex: ['(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.[jt]sx?$'], - extensionsToTreatAsEsm: [], - }; - - beforeEach(() => { - jest.spyOn(NgJestCompiler.prototype, 'getCompiledOutput').mockReturnValueOnce(''); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - test.each(['foo.ts', 'foo.js'])('should compile ts or js with allowJs by NgJestCompiler', (fileName) => { - const jestCfg = { - ...baseJestCfg, - globals: { 'ts-jest': { tsconfig: { allowJs: true } } }, - }; - const input = { - fileContent: 'const foo = 1', - // eslint-disable-next-line - options: { config: { ...jestCfg } as any, instrument: false, rootDir: '/foo', cacheFS: new Map(), }, - }; - const ngJestTransformer = new NgJestTransformer(); - - // @ts-expect-error testing purpose - ngJestTransformer.process(input.fileContent, fileName, input.options); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(NgJestCompiler.prototype.getCompiledOutput).toHaveBeenCalledWith(fileName, input.fileContent, undefined); - }); - - test.each([ - { - fileName: 'foo.html', - fileContent: '

Hello world

', - }, - { - fileName: 'foo.js', - fileContent: 'const foo = 1', - }, - { - fileName: 'foo.d.ts', - fileContent: 'type foo = number', - }, - ])('should compile other files with ts-jest', ({ fileName, fileContent }) => { - const jestCfg = { - ...baseJestCfg, - globals: { - 'ts-jest': { - tsconfig: { allowJs: false }, - stringifyContentPathRegex: '\\.html$', - }, - }, - }; - const input = { - // eslint-disable-next-line - options: { config: { ...jestCfg } as any, instrument: false, rootDir: '/foo', cacheFS: new Map(), }, - }; - const ngJestTransformer = new NgJestTransformer(); - - // @ts-expect-error testing purpose - ngJestTransformer.process(fileContent, fileName, input.options); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(NgJestCompiler.prototype.getCompiledOutput).not.toHaveBeenCalled(); - }); +describe('createTransformer', () => { + it('should create different instances', () => { + const tr1 = ngJest.createTransformer(); + const tr2 = ngJest.createTransformer(); + expect(tr1).toBeInstanceOf(NgJestTransformer); + expect(tr2).toBeInstanceOf(NgJestTransformer); + expect(tr1).not.toBe(tr2); }); }); diff --git a/src/__tests__/ng-jest-compiler.spec.ts b/src/__tests__/ng-jest-compiler.spec.ts index 764076ac59..e952f4fbfa 100644 --- a/src/__tests__/ng-jest-compiler.spec.ts +++ b/src/__tests__/ng-jest-compiler.spec.ts @@ -2,8 +2,6 @@ import { readFileSync } from 'fs'; import { join } from 'path'; import { jest } from '@jest/globals'; -import { SOURCE_MAPPING_PREFIX } from 'ts-jest/dist/compiler/compiler-utils'; -import ts from 'typescript'; import { NgJestCompiler } from '../compiler/ng-jest-compiler'; import { NgJestConfig } from '../config/ng-jest-config'; @@ -11,8 +9,11 @@ import { NgJestConfig } from '../config/ng-jest-config'; import { jestCfgStub } from './__helpers__/test-constants'; import { mockFolder } from './__helpers__/test-helpers'; +const fileName = join(mockFolder, 'app.component.ts'); +const fileContent = readFileSync(fileName, 'utf-8'); + describe('NgJestCompiler', () => { - describe('with isolatedModules true', () => { + describe.each([true, false])('with isolatedModules %p', (isolatedModules) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const baseJestCfg = { ...jestCfgStub, @@ -20,183 +21,36 @@ describe('NgJestCompiler', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 'ts-jest': { ...jestCfgStub.globals['ts-jest'], - isolatedModules: true, + isolatedModules, + tsconfig: { + esModuleInterop: false, + allowSyntheticDefaultImports: false, + }, }, }, }; - test.each([true, false])('should call transpileModule with useESM %p', (useESM) => { - const ngJestConfig = new NgJestConfig({ - ...baseJestCfg, - globals: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - 'ts-jest': { - ...baseJestCfg.globals['ts-jest'], - useESM, - tsconfig: { - esModuleInterop: false, - allowSyntheticDefaultImports: false, + test.each([true, false])( + 'should transform codes with useESM %p using hoisting, replace resources and downlevel ctor transformers', + (useESM) => { + const ngJestConfig = new NgJestConfig({ + ...baseJestCfg, + globals: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + 'ts-jest': { + ...baseJestCfg.globals['ts-jest'], + useESM, }, }, - }, - }); - const fileName = join(mockFolder, 'foo.service.ts'); - const compiler = new NgJestCompiler(ngJestConfig, new Map()); - // @ts-expect-error testing purpose - compiler._transpileModule = jest.fn().mockReturnValueOnce({ - outputText: 'var foo = 1', - diagnostics: [], - sourceMapText: '{}', - }); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - compiler.getCompiledOutput(fileName, readFileSync(fileName, 'utf-8'), useESM)!; - - // @ts-expect-error testing purpose - expect(compiler._transpileModule).toHaveBeenCalled(); - // @ts-expect-error testing purpose - const { module, esModuleInterop, allowSyntheticDefaultImports } = compiler._transpileModule.mock.calls[0][1] - .compilerOptions as ts.CompilerOptions; - if (useESM) { - expect(module).not.toEqual(ts.ModuleKind.CommonJS); - // verify if allowSyntheticDefaultImports is hardcoded to true - expect(allowSyntheticDefaultImports).toEqual(true); - // verify if esModuleInterop is hardcoded to true - expect(esModuleInterop).toEqual(true); - } else { - expect(module).toEqual(ts.ModuleKind.CommonJS); - } - // @ts-expect-error _initialCompilerOptions is a private property - expect(compiler._initialCompilerOptions.esModuleInterop).not.toEqual(true); - // @ts-expect-error _initialCompilerOptions is a private property - expect(compiler._initialCompilerOptions.allowSyntheticDefaultImports).not.toEqual(true); - }); - }); - - describe('with isolatedModule false', () => { - const sourceMapStub = - '{"version":3,"file":"app.component.js","sourceRoot":"","sources":["app.component.ts"],"names":[],"mappings":";;;;AAAA,wCAA0C;IAO7B,YAAY,SAAZ,YAAY;;QACvB,UAAK,GAAG,cAAc,CAAC;IACzB,CAAC;CAAA,CAAA;AAFY,YAAY;IALxB,gBAAS,CAAC;QACT,QAAQ,EAAE,UAAU;QACpB,WAAW,EAAE,sBAAsB;QACnC,SAAS,EAAE,CAAC,sBAAsB,CAAC;KACpC,CAAC;GACW,YAAY,CAExB;AAFY,oCAAY","sourcesContent":["import { Component } from \'@angular/core\';\\n\\n@Component({\\n selector: \'app-root\',\\n templateUrl: \'./app.component.html\',\\n styleUrls: [\'./app.component.scss\'],\\n})\\nexport class AppComponent {\\n title = \'test-app-v10\';\\n}\\n"]}'; - const compiledOutputStub = - '"use strict";\n' + - 'Object.defineProperty(exports, "__esModule", { value: true });\n' + - 'exports.AppComponent = void 0;\n' + - 'const tslib_1 = require("tslib");\n' + - 'const core_1 = require("@angular/core");\n' + - 'let AppComponent = class AppComponent {\n' + - ' constructor() {\n' + - " this.title = 'test-app-v10';\n" + - ' }\n' + - '};\n' + - 'AppComponent = tslib_1.__decorate([\n' + - ' core_1.Component({\n' + - " selector: 'app-root',\n" + - " templateUrl: './app.component.html',\n" + - " styleUrls: ['./app.component.scss'],\n" + - ' })\n' + - '], AppComponent);\n' + - 'exports.AppComponent = AppComponent;\n' + - '//# sourceMappingURL=app.component.js.map\n'; - - const noErrorFileName = join(mockFolder, 'app.component.ts'); - const noErrorFileContent = readFileSync(noErrorFileName, 'utf-8'); - const hasErrorFileName = join(mockFolder, 'foo.component.ts'); - const hasErrorFileContent = readFileSync(hasErrorFileName, 'utf-8'); - - test.each([noErrorFileName, undefined])( - 'should return compiled result for new file which is not known or known by Program', - (fileName) => { - const ngJestConfig = new NgJestConfig(jestCfgStub); - ngJestConfig.parsedTsConfig = { - ...ngJestConfig.parsedTsConfig, - rootNames: fileName ? [fileName] : [], - }; + }); const compiler = new NgJestCompiler(ngJestConfig, new Map()); - // @ts-expect-error bypass type checking to access private property - compiler._tsHost.getEmittedResult = jest.fn().mockReturnValueOnce([compiledOutputStub, sourceMapStub]); + // @ts-expect-error testing purpose + const makeTransformersSpy = jest.spyOn(compiler, '_makeTransformers'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - compiler.getCompiledOutput(noErrorFileName, noErrorFileContent, false)!; + compiler.getCompiledOutput(fileContent, fileName, true); - // @ts-expect-error bypass type checking to access private property - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(compiler._tsHost.getEmittedResult).toHaveBeenCalled(); + expect(makeTransformersSpy.mock.results[0].value).toMatchSnapshot(); }, ); - - 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, - globals: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - 'ts-jest': { - ...jestCfgStub.globals['ts-jest'], - useESM: true, - tsconfig: { - esModuleInterop: false, - allowSyntheticDefaultImports: false, - }, - }, - }, - }); - const compiler = new NgJestCompiler(ngJestConfig, new Map()); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const emittedResult = compiler.getCompiledOutput(noErrorFileName, noErrorFileContent, true)!; - - // Source map is different based on file location which can fail on CI, so we only compare snapshot for js - expect(emittedResult.substring(0, emittedResult.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot(); - // @ts-expect-error _compilerOptions is a private property - expect(compiler._compilerOptions.esModuleInterop).toEqual(true); - // @ts-expect-error _compilerOptions is a private property - expect(compiler._compilerOptions.allowSyntheticDefaultImports).toEqual(true); - // @ts-expect-error _initialCompilerOptions is a private property - expect(compiler._initialCompilerOptions.esModuleInterop).not.toEqual(true); - // @ts-expect-error _initialCompilerOptions is a private property - expect(compiler._initialCompilerOptions.allowSyntheticDefaultImports).not.toEqual(true); - }); - - test.each([hasErrorFileName, undefined])('should throw diagnostics error for new file which is', (fileName) => { - const ngJestConfig = new NgJestConfig(jestCfgStub); - ngJestConfig.parsedTsConfig = { - ...ngJestConfig.parsedTsConfig, - rootNames: fileName ? [fileName] : [], - }; - const compiler = new NgJestCompiler(ngJestConfig, new Map()); - - expect(() => - compiler.getCompiledOutput(hasErrorFileName, hasErrorFileContent, false), - ).toThrowErrorMatchingSnapshot(fileName ? 'known by Program' : 'not known by Program'); - }); - - test('should not throw diagnostics error when shouldReportDiagnostics return false', () => { - const ngJestConfig = new NgJestConfig(jestCfgStub); - ngJestConfig.parsedTsConfig = { - ...ngJestConfig.parsedTsConfig, - rootNames: [hasErrorFileName], - }; - // @ts-expect-error testing purpose - ngJestConfig.shouldReportDiagnostics = jest.fn().mockReturnValueOnce(false); - const compiler = new NgJestCompiler(ngJestConfig, new Map()); - - expect(() => compiler.getCompiledOutput(hasErrorFileName, hasErrorFileContent, false)).not.toThrowError(); - }); }); }); diff --git a/src/__tests__/ng-jest-transformer.spec.ts b/src/__tests__/ng-jest-transformer.spec.ts new file mode 100644 index 0000000000..7c64720bde --- /dev/null +++ b/src/__tests__/ng-jest-transformer.spec.ts @@ -0,0 +1,86 @@ +import { jest } from '@jest/globals'; +import { TsJestTransformer } from 'ts-jest/dist/ts-jest-transformer'; + +import { NgJestTransformer } from '../ng-jest-transformer'; + +describe('NgJestTransformer', () => { + describe('_configsFor', () => { + test( + 'should return the same config set for same values with different jest config objects' + + ' but their serialized versions are the same', + () => { + const obj1 = { + config: { cwd: process.cwd(), extensionsToTreatAsEsm: [], globals: {}, testMatch: [], testRegex: [] }, + cacheFS: new Map(), + }; + const obj2 = { ...obj1, config: { ...obj1.config, globals: {} } }; + // @ts-expect-error testing purpose + const cs1 = new NgJestTransformer()._configsFor(obj1); + // @ts-expect-error testing purpose + const cs2 = new NgJestTransformer()._configsFor(obj2); + + expect(cs2).toBe(cs1); + }, + ); + + test('should return the same config set for same values with jest config objects', () => { + const obj1 = { + config: { cwd: process.cwd(), extensionsToTreatAsEsm: [], globals: {}, testMatch: [], testRegex: [] }, + cacheFS: new Map(), + }; + const obj2 = { ...obj1 }; + // @ts-expect-error testing purpose + const cs1 = new NgJestTransformer()._configsFor(obj1); + // @ts-expect-error testing purpose + const cs2 = new NgJestTransformer()._configsFor(obj2); + + expect(cs2).toBe(cs1); + }); + }); + + describe('getCacheKey', () => { + test('should call getCacheKey method from parent class TsJestTransformer', () => { + TsJestTransformer.prototype.getCacheKey = jest.fn(); + const input = { + fileContent: 'export default "foo"', + fileName: 'foo.ts', + jestConfigStr: '{"foo": "bar"}', + // eslint-disable-next-line + options: { config: { foo: 'bar' } as any, instrument: false, rootDir: '/foo' }, + }; + const tr = new NgJestTransformer(); + // @ts-expect-error testing purpose + tr.getCacheKey(input.fileContent, input.fileName, input.jestConfigStr, input.options); + + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(TsJestTransformer.prototype.getCacheKey).toHaveBeenCalledWith( + input.fileContent, + input.fileName, + input.jestConfigStr, + input.options, + ); + }); + }); + + describe('process', () => { + test('should call getCacheKey method from parent class TsJestTransformer', () => { + TsJestTransformer.prototype.process = jest.fn(); + const input = { + fileContent: 'export default "foo"', + fileName: 'foo.ts', + // eslint-disable-next-line + options: { config: { foo: 'bar' } as any, instrument: false, rootDir: '/foo' }, + }; + const tr = new NgJestTransformer(); + // @ts-expect-error testing purpose + tr.process(input.fileContent, input.fileName, input.options); + + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(TsJestTransformer.prototype.process).toHaveBeenCalledWith( + input.fileContent, + input.fileName, + input.options, + ); + }); + }); +}); diff --git a/src/__tests__/replace-resources.spec.ts b/src/__tests__/replace-resources.spec.ts deleted file mode 100644 index 1a67ad8612..0000000000 --- a/src/__tests__/replace-resources.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; - -import { SOURCE_MAPPING_PREFIX } from 'ts-jest/dist/compiler/compiler-utils'; - -import { NgJestCompiler } from '../compiler/ng-jest-compiler'; -import { NgJestConfig } from '../config/ng-jest-config'; - -import { jestCfgStub } from './__helpers__/test-constants'; -import { mockFolder } from './__helpers__/test-helpers'; - -describe('Replace resources transformer', () => { - const fileName = join(mockFolder, 'app.component.ts'); - const fileContent = readFileSync(fileName, 'utf-8'); - - describe.each([true, false])('with isolatedModules %p', (isolatedModules) => { - test.each([true, false])( - 'should use replaceResources transformer from @angular/compiler-cli with useESM %p', - (useESM) => { - const ngJestConfig = new NgJestConfig({ - ...jestCfgStub, - globals: { - 'ts-jest': { - ...jestCfgStub.globals['ts-jest'], - isolatedModules, - useESM, - }, - }, - }); - const compiler = new NgJestCompiler(ngJestConfig, new Map()); - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const emittedResult = compiler.getCompiledOutput(fileName, fileContent, useESM)!; - - // Source map is different based on file location which can fail on CI, so we only compare snapshot for js - expect(emittedResult.substring(0, emittedResult.indexOf(SOURCE_MAPPING_PREFIX))).toMatchSnapshot(); - }, - ); - }); -}); diff --git a/src/compiler/ng-jest-compiler.ts b/src/compiler/ng-jest-compiler.ts index b284f310d4..f25618d67b 100644 --- a/src/compiler/ng-jest-compiler.ts +++ b/src/compiler/ng-jest-compiler.ts @@ -1,196 +1,32 @@ -import { CompilerHost, CompilerOptions } from '@angular/compiler-cli'; -import { createCompilerHost } from '@angular/compiler-cli/src/transformers/compiler_host'; -import type { Logger } from 'bs-logger'; -import { updateOutput } from 'ts-jest/dist/compiler/compiler-utils'; -import type { - CompilerInstance, - TTypeScript, - ResolvedModulesMap, - TsJestAstTransformer, - TsCompilerInstance, -} from 'ts-jest/dist/types'; +import { TsCompiler } from 'ts-jest/dist/compiler/ts-compiler'; +import type { TsJestAstTransformer, TTypeScript } from 'ts-jest/dist/types'; import type * as ts from 'typescript'; import type { NgJestConfig } from '../config/ng-jest-config'; -import { factory as constructorDownlevelCtor } from '../transformers/downlevel-ctor'; -import { factory as replaceResources } from '../transformers/replace-resources'; +import { constructorDownlevelCtor } from '../transformers/downlevel-ctor'; +import { replaceResources } from '../transformers/replace-resources'; -import { NgJestCompilerHost } from './compiler-host'; - -interface PatchedTranspileOptions { - fileName: string; - compilerOptions: ts.CompilerOptions; - moduleName?: string; - renamedDependencies?: ts.MapLike; -} - -export class NgJestCompiler implements CompilerInstance { - private _compilerOptions!: CompilerOptions; - private _program: ts.Program | undefined; - private _compilerHost: CompilerHost | undefined; - private _tsHost: NgJestCompilerHost | undefined; - private _rootNames: string[] = []; - private readonly _initialCompilerOptions: CompilerOptions; - private readonly _logger: Logger; - private readonly _ts: TTypeScript; +export class NgJestCompiler extends TsCompiler { + protected readonly _ts: TTypeScript; constructor(readonly ngJestConfig: NgJestConfig, readonly jestCacheFS: Map) { - this._logger = this.ngJestConfig.logger; + super(ngJestConfig, jestCacheFS); this._ts = this.ngJestConfig.compilerModule; - this._initialCompilerOptions = { ...this.ngJestConfig.parsedTsConfig.options }; - this._setupOptions(this.ngJestConfig); this._logger.debug('created NgJestCompiler'); } - getResolvedModulesMap(fileContent: string, fileName: string): ResolvedModulesMap { - this._tsHost?.updateMemoryHost(fileName, fileContent); - - // eslint-disable-next-line - return (this._program?.getSourceFile(fileName) as any)?.resolvedModules; - } - - getCompiledOutput(fileName: string, fileContent: string, supportsStaticESM: boolean): string { - const customTransformers = this.ngJestConfig.resolvedTransformers; - let moduleKind = this._initialCompilerOptions.module; - let esModuleInterop = this._initialCompilerOptions.esModuleInterop; - let allowSyntheticDefaultImports = this._initialCompilerOptions.allowSyntheticDefaultImports; - if (supportsStaticESM && this.ngJestConfig.useESM) { - moduleKind = - !moduleKind || - (moduleKind && - ![this._ts.ModuleKind.ES2015, this._ts.ModuleKind.ES2020, this._ts.ModuleKind.ESNext].includes(moduleKind)) - ? this._ts.ModuleKind.ESNext - : moduleKind; - // Make sure `esModuleInterop` and `allowSyntheticDefaultImports` true to support import CJS into ESM - esModuleInterop = true; - allowSyntheticDefaultImports = true; - } else { - moduleKind = this._ts.ModuleKind.CommonJS; - } - this._compilerOptions = { - ...this._compilerOptions, - allowSyntheticDefaultImports, - esModuleInterop, - module: moduleKind, - }; - - if (this._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]; - 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 emitResult = this._program.emit( - sourceFile, - undefined, - undefined, - undefined, - this.makeTransformers(customTransformers, this._program), - ); - - // 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'); - - allDiagnostics.push( - ...emitResult.diagnostics, - ...this._program.getSyntacticDiagnostics(sourceFile), - ...this._program.getSemanticDiagnostics(sourceFile), - ); - } - if (!allDiagnostics.length) { - this._logger.debug({ fileName }, 'getCompiledOutput: update compiled output including inline source map'); - - return updateOutput(compiledOutput[0], fileName, compiledOutput[1]); - } else { - this.ngJestConfig.raiseDiagnostics(allDiagnostics, fileName); - - return ''; - } - } else { - this._logger.debug({ fileName }, 'getCompiledOutput: compiling as isolated module'); - - const result: ts.TranspileOutput = this._transpileModule( - fileContent, - { - fileName, - compilerOptions: this._compilerOptions, - }, - customTransformers, - ); - if (result.diagnostics && this.ngJestConfig.shouldReportDiagnostics(fileName)) { - this.ngJestConfig.raiseDiagnostics(result.diagnostics, fileName, this._logger); - } - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return updateOutput(result.outputText, fileName, result.sourceMapText!); - } - } - - private _setupOptions({ parsedTsConfig }: NgJestConfig): void { - this._logger.debug({ parsedTsConfig }, '_setupOptions: initializing compiler config'); - - this._compilerOptions = { ...this._initialCompilerOptions }; - this._rootNames = parsedTsConfig.rootNames.filter((rootName) => !this.ngJestConfig.isTestFile(rootName)); - if (this._compilerOptions.strictMetadataEmit) { - this._logger.warn( - `Using Angular compiler option 'strictMetadataEmit' for applications might cause undefined behavior.`, - ); - } - if (!this.ngJestConfig.isolatedModules) { - this._logger.debug( - { compilerOptions: this._compilerOptions }, - '_setupOptions: creating Compiler Host using @angular/compiler-cli createCompilerHost', - ); - - this._tsHost = new NgJestCompilerHost(this._logger, this.ngJestConfig, this.jestCacheFS); - this._compilerHost = createCompilerHost({ - options: this._compilerOptions, - tsHost: this._tsHost, - }); - this._createOrUpdateProgram(); - } - } - - private _createOrUpdateProgram(): void { - const oldTsProgram = this._program; - - this._logger.debug(`_createOrUpdateProgram: ${oldTsProgram ? 'update' : 'create'} TypeScript Program`); - - this._program = this._ts.createProgram(this._rootNames, this._compilerOptions, this._compilerHost, oldTsProgram); - } - /** * Copy from https://github.com/microsoft/TypeScript/blob/master/src/services/transpile.ts * This is required because the exposed function `transpileModule` from TypeScript doesn't allow to access `Program` * and we need `Program` to be able to use Angular `replace-resources` transformer. */ - private _transpileModule( - fileContent: string, - transpileOptions: PatchedTranspileOptions, - customTransformers: TsJestAstTransformer, - ): ts.TranspileOutput { + protected _transpileOutput(fileContent: string, fileName: string): ts.TranspileOutput { const diagnostics: ts.Diagnostic[] = []; - const options: ts.CompilerOptions = transpileOptions.compilerOptions + const compilerOptions = { ...this._compilerOptions }; + const options: ts.CompilerOptions = compilerOptions ? // @ts-expect-error internal TypeScript API - this._ts.fixupCompilerOptions(transpileOptions.compilerOptions, diagnostics) + this._ts.fixupCompilerOptions(compilerOptions, diagnostics) : {}; // mix in default options @@ -217,19 +53,9 @@ export class NgJestCompiler implements CompilerInstance { options.allowNonTsExtensions = true; // if jsx is specified then treat file as .tsx - const inputFileName = - transpileOptions.fileName || - (transpileOptions.compilerOptions && transpileOptions.compilerOptions.jsx ? 'module.tsx' : 'module.ts'); + const inputFileName = fileName || (compilerOptions && compilerOptions.jsx ? 'module.tsx' : 'module.ts'); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const sourceFile = this._ts.createSourceFile(inputFileName, fileContent, options.target!); // TODO: GH#18217 - if (transpileOptions.moduleName) { - sourceFile.moduleName = transpileOptions.moduleName; - } - - if (transpileOptions.renamedDependencies) { - // @ts-expect-error internal TypeScript API - sourceFile.renamedDependencies = new Map(getEntries(transpileOptions.renamedDependencies)); - } // @ts-expect-error internal TypeScript API const newLine = this._ts.getNewLineCharacter(options); @@ -265,20 +91,20 @@ export class NgJestCompiler implements CompilerInstance { getDirectories: () => [], }; - const program = this._ts.createProgram([inputFileName], options, compilerHost); + this.program = this._ts.createProgram([inputFileName], options, compilerHost); if (this.ngJestConfig.shouldReportDiagnostics(inputFileName)) { // @ts-expect-error internal TypeScript API - this._ts.addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile)); + this._ts.addRange(/*to*/ diagnostics, /*from*/ this.program.getSyntacticDiagnostics(sourceFile)); // @ts-expect-error internal TypeScript API - this._ts.addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics()); + this._ts.addRange(/*to*/ diagnostics, /*from*/ this.program.getOptionsDiagnostics()); } // Emit - program.emit( + this.program.emit( /*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, - this.makeTransformers(customTransformers, program), + this._makeTransformers(this.ngJestConfig.resolvedTransformers), ); // @ts-expect-error internal TypeScript API @@ -287,21 +113,17 @@ export class NgJestCompiler implements CompilerInstance { return { outputText, diagnostics, sourceMapText }; } - private makeTransformers(customTransformers: TsJestAstTransformer, program: ts.Program): ts.CustomTransformers { + protected _makeTransformers(customTransformers: TsJestAstTransformer): ts.CustomTransformers { return { + ...super._makeTransformers(customTransformers).after, + ...super._makeTransformers(customTransformers).afterDeclarations, before: [ ...customTransformers.before.map((beforeTransformer) => - beforeTransformer.factory({ configSet: this.ngJestConfig, program }, beforeTransformer.options), + beforeTransformer.factory(this, beforeTransformer.options), ), - replaceResources({ program } as TsCompilerInstance), - constructorDownlevelCtor({ program } as TsCompilerInstance), + replaceResources(this), + constructorDownlevelCtor(this), ] as Array | ts.CustomTransformerFactory>, - after: customTransformers.after.map((afterTransformer) => - afterTransformer.factory({ configSet: this.ngJestConfig, program }, afterTransformer.options), - ) as Array | ts.CustomTransformerFactory>, - afterDeclarations: customTransformers.afterDeclarations.map((afterDeclarations) => - afterDeclarations.factory({ configSet: this.ngJestConfig, program }, afterDeclarations.options), - ) as Array>, }; } } diff --git a/src/config/ng-jest-config.ts b/src/config/ng-jest-config.ts index 3aa774fbd7..4324423c6f 100644 --- a/src/config/ng-jest-config.ts +++ b/src/config/ng-jest-config.ts @@ -2,14 +2,9 @@ import { NodeJSFileSystem, setFileSystem } from '@angular/compiler-cli/src/ngtsc import { formatDiagnostics, readConfiguration, ParsedConfiguration } from '@angular/compiler-cli/src/perform_compile'; import { ConfigSet } from 'ts-jest/dist/config/config-set'; import type { ProjectConfigTsJest } from 'ts-jest/dist/types'; -import type { CompilerOptions } from 'typescript'; +import type { CompilerOptions, ParsedCommandLine } from 'typescript'; export class NgJestConfig extends ConfigSet { - /** - * Override `ts-jest` property - */ - parsedTsConfig!: ParsedConfiguration; - constructor(readonly jestCfg: ProjectConfigTsJest) { super(jestCfg); } @@ -17,7 +12,7 @@ export class NgJestConfig extends ConfigSet { /** * Override `ts-jest` behavior because we use `readConfiguration` which will read and resolve tsconfig. */ - protected _resolveTsConfig(compilerOptions?: CompilerOptions, resolvedConfigFile?: string): ParsedConfiguration { + protected _resolveTsConfig(compilerOptions?: CompilerOptions, resolvedConfigFile?: string): ParsedCommandLine { /** * To be able to use `readConfiguration` function from `@angular/cli`, we need to setup file system. For `CommonJS`, * it is not a problem to import directly the function from `@angular/cli`. However, with `ESM`, we will get an @@ -56,6 +51,7 @@ export class NgJestConfig extends ConfigSet { return { ...result, + fileNames: result.rootNames, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment options: { ...result.options, diff --git a/src/ng-jest-transformer.ts b/src/ng-jest-transformer.ts index 66f01e98d3..31659c3913 100644 --- a/src/ng-jest-transformer.ts +++ b/src/ng-jest-transformer.ts @@ -1,6 +1,3 @@ -import type { TransformedSource } from '@jest/transform'; -import type { Config } from '@jest/types'; -import { DECLARATION_TYPE_EXT, JS_JSX_REGEX } from 'ts-jest/dist/constants'; import { TsJestTransformer } from 'ts-jest/dist/ts-jest-transformer'; import type { TransformOptionsTsJest, ProjectConfigTsJest } from 'ts-jest/dist/types'; import { stringify } from 'ts-jest/dist/utils/json'; @@ -23,23 +20,6 @@ export class NgJestTransformer extends TsJestTransformer { private static readonly _cachedConfigSets: CachedConfigSet[] = []; protected _compiler!: NgJestCompiler; - process( - fileContent: string, - filePath: Config.Path, - transformOptions: TransformOptionsTsJest, - ): TransformedSource | string { - const isDefinitionFile = filePath.endsWith(DECLARATION_TYPE_EXT); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const isJsFile = JS_JSX_REGEX.test(filePath); - const ngJestCfg = this._configsFor(transformOptions); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const shouldStringifyContent = ngJestCfg.shouldStringifyContent(filePath); - - return shouldStringifyContent || isDefinitionFile || (!ngJestCfg.parsedTsConfig.options.allowJs && isJsFile) - ? super.process(fileContent, filePath, transformOptions) - : this._compiler.getCompiledOutput(filePath, fileContent, transformOptions.supportsStaticESM); - } - /** * Override `ts-jest` method to load our `NgJestConfig` class */ diff --git a/src/transformers/downlevel-ctor.ts b/src/transformers/downlevel-ctor.ts index e52c32f39f..6672d4b9a0 100644 --- a/src/transformers/downlevel-ctor.ts +++ b/src/transformers/downlevel-ctor.ts @@ -9,11 +9,33 @@ * This will make a bit more effort in maintaining but it will be easier to just copy to work with ESM */ import { Decorator, ReflectionHost, TypeScriptReflectionHost } from '@angular/compiler-cli/src/ngtsc/reflection'; -import { TsCompilerInstance } from 'ts-jest/dist/types'; +import type { TsCompilerInstance } from 'ts-jest/dist/types'; import ts from 'typescript'; import { isAliasImportDeclaration, loadIsReferencedAliasDeclarationPatch } from './patch-alias-reference-resolution'; +/** + * Transform for downleveling Angular decorators and Angular-decorated class constructor + * parameters for dependency injection. This transform can be used by the CLI for JIT-mode + * compilation where constructor parameters and associated Angular decorators should be + * downleveled so that apps are not exposed to the ES2015 temporal dead zone limitation + * in TypeScript. See https://github.com/angular/angular-cli/pull/14473 for more details. + */ +export function constructorDownlevelCtor({ program }: TsCompilerInstance): ts.TransformerFactory { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const typeChecker = program!.getTypeChecker(); + const reflectionHost = new TypeScriptReflectionHost(typeChecker); + + return getDownlevelDecoratorsTransform( + typeChecker, + reflectionHost, + [], + /* isCore */ false, + /* enableClosureCompiler */ false, + /* skipClassDecorators */ true, + ); +} + /** * Whether a given decorator should be treated as an Angular decorator. * Either it's used in @angular/core, or it's imported from there. @@ -729,30 +751,3 @@ function getDownlevelDecoratorsTransform( }; }; } - -// this is a unique identifier for your transformer -export const name = 'downlevel-ctor'; -// increment this each time you change the behavior of your transformer -export const version = 1; - -/** - * Transform for downleveling Angular decorators and Angular-decorated class constructor - * parameters for dependency injection. This transform can be used by the CLI for JIT-mode - * compilation where constructor parameters and associated Angular decorators should be - * downleveled so that apps are not exposed to the ES2015 temporal dead zone limitation - * in TypeScript. See https://github.com/angular/angular-cli/pull/14473 for more details. - */ -export function factory({ program }: TsCompilerInstance): ts.TransformerFactory { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const typeChecker = program!.getTypeChecker(); - const reflectionHost = new TypeScriptReflectionHost(typeChecker); - - return getDownlevelDecoratorsTransform( - typeChecker, - reflectionHost, - [], - /* isCore */ false, - /* enableClosureCompiler */ false, - /* skipClassDecorators */ true, - ); -} diff --git a/src/transformers/replace-resources.ts b/src/transformers/replace-resources.ts index 4430681a23..5eb5dbfebe 100644 --- a/src/transformers/replace-resources.ts +++ b/src/transformers/replace-resources.ts @@ -5,16 +5,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { TsCompilerInstance } from 'ts-jest/dist/types'; +import type { TsCompilerInstance } from 'ts-jest/dist/types'; import ts from 'typescript'; import { STYLES, STYLE_URLS, TEMPLATE_URL, TEMPLATE, REQUIRE, COMPONENT } from '../constants'; -// this is a unique identifier for your transformer -export const name = 'replace-resources'; -// increment this each time you change the behavior of your transformer -export const version = 1; - const shouldTransform = (fileName: string) => !fileName.endsWith('.ngfactory.ts') && !fileName.endsWith('.ngstyle.ts'); /** * Source https://github.com/angular/angular-cli/blob/master/packages/ngtools/webpack/src/transformers/replace_resources.ts @@ -49,7 +44,7 @@ const shouldTransform = (fileName: string) => !fileName.endsWith('.ngfactory.ts' * styles: [], * }) */ -export function factory({ program }: TsCompilerInstance): ts.TransformerFactory { +export function replaceResources({ program }: TsCompilerInstance): ts.TransformerFactory { return (context: ts.TransformationContext) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const typeChecker = program!.getTypeChecker(); diff --git a/yarn.lock b/yarn.lock index 7bdf8a2bb9..066c173205 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10737,10 +10737,10 @@ trim-off-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= -ts-jest@^27.0.0-next.5: - version "27.0.0-next.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.0-next.5.tgz#5be205a300ab6266359f786ca067d1ec07a9a01e" - integrity sha512-NTix8kPJCPnOlIGpKmaKAh+W11OP4la5I2LdUuAfoPHn4ik8JK08mVLU+ATe9RN5xvKUgkAYYO5HwbtNpuESLA== +ts-jest@^27.0.0-next.6: + version "27.0.0-next.6" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.0-next.6.tgz#59c9392eff845e4ff90360938aa4d2b3a354a654" + integrity sha512-GCxbKghc6qlUnOLUMNan2inOcn+kHmVpHxWgAtsDhhxVNYmsOEK/lzK4XU6C2FDCThCKhBpZUT3sjIWNAvq3mQ== dependencies: "@types/jest" "26.x" bs-logger "0.x"