From 1468ceb473073d1c68941fe2e106e4c686fcd3f7 Mon Sep 17 00:00:00 2001 From: Ahn Date: Sat, 12 Dec 2020 20:32:08 +0100 Subject: [PATCH] feat: use angular compiler api to transform codes (#562) Closes #108 Closes #288 Closes #322 Closes #353 Closes #622 BREAKING CHANGE: With the new jest transformer, `jest-preset-angular` now switches to default to use this new transformer and no longer uses `ts-jest` to transform codes. Users who are currently doing in jest config ``` // jest.config.js module.exports = { // [...] transform: { '^.+\\.(ts|js|html)$': 'ts-jest', }, } ``` should change to ``` // jest.config.js module.exports = { // [...] transform: { '^.+\\.(ts|js|html)$': 'jest-preset-angular', }, } ``` `isolatedModule: true` will still use `ts-jest` to compile `ts` to `js` but you won't get full compatibility with Ivy. --- README.md | 44 +++---- .../my-lib/src/lib/disableable.directive.ts | 36 ++++++ .../my-lib/src/lib/my-lib.component.ts | 15 ++- .../src/app/forward-ref/forward-ref.spec.ts | 21 +++ e2e/test-app-v9/package.json | 1 + e2e/test-app-v9/src/app/app.module.ts | 4 + .../ngc-compiled-lib.component.spec.ts | 34 +++++ .../ngc-compiled-lib.component.ts | 64 +++++++++ e2e/test-app-v9/yarn.lock | 7 + jest-preset.js | 2 +- package.json | 1 - src/__tests__/index.spec.ts | 121 ++++++++++++++---- src/compiler/ng-jest-compiler.ts | 4 +- src/config/ng-jest-config.ts | 2 +- src/index.ts | 25 +++- yarn.lock | 34 ++--- 16 files changed, 339 insertions(+), 76 deletions(-) create mode 100644 e2e/test-app-v10/projects/my-lib/src/lib/disableable.directive.ts create mode 100644 e2e/test-app-v10/src/app/forward-ref/forward-ref.spec.ts create mode 100644 e2e/test-app-v9/src/app/ngc-compiled-lib/ngc-compiled-lib.component.spec.ts create mode 100644 e2e/test-app-v9/src/app/ngc-compiled-lib/ngc-compiled-lib.component.ts diff --git a/README.md b/README.md index ba5fce1992..8de32f772b 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ module.exports = { - or to your root `package.json` -```json +```json5 { "jest": { "preset": "jest-preset-angular", @@ -115,21 +115,22 @@ By Angular CLI defaults you'll have a `src/test.ts` file which will be picked up ## Exposed [configuration](https://github.com/thymikee/jest-preset-angular/blob/master/jest-preset.js) ```js +const customTransformers = require('./build/transformers'); +const snapshotSerializers = require('./build/serializers'); + module.exports = { globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json', stringifyContentPathRegex: '\\.html$', astTransformers: { - before: [ - 'jest-preset-angular/build/InlineFilesTransformer', - 'jest-preset-angular/build/StripStylesTransformer', - ], + before: customTransformers, }, }, }, + testEnvironment: 'jsdom', transform: { - '^.+\\.(ts|js|html)$': 'ts-jest', + '^.+\\.(ts|js|html)$': 'jest-preset-angular', }, moduleFileExtensions: ['ts', 'html', 'js', 'json'], moduleNameMapper: { @@ -139,11 +140,7 @@ module.exports = { '^environments/(.*)$': '/src/environments/$1', }, transformIgnorePatterns: ['node_modules/(?!@ngrx)'], - snapshotSerializers: [ - 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', - 'jest-preset-angular/build/AngularSnapshotSerializer.js', - 'jest-preset-angular/build/HTMLCommentSerializer.js', - ], + snapshotSerializers, }; ``` @@ -293,7 +290,6 @@ describe('Component snapshots', () => { const configure: ConfigureFn = testBed => { testBed.configureTestingModule({ declarations: [FooComponent], - imports: [...], schemas: [NO_ERRORS_SCHEMA], }); }; @@ -388,7 +384,7 @@ import MyStuff from 'src/testing/my.stuff'; However, if your directory structure differ from that provided by `angular-cli` you can adjust `moduleNameMapper` in Jest config: -```json +```json5 { "jest": { "moduleNameMapper": { @@ -403,7 +399,7 @@ However, if your directory structure differ from that provided by `angular-cli` Override `globals` object in Jest config: -```json +```json5 { "jest": { "globals": { @@ -462,7 +458,7 @@ A default `tsconfig.spec.json` after modifying will look like this #### Adjust your `transformIgnorePatterns` whitelist: -```json +```json5 { "jest": { "transformIgnorePatterns": [ @@ -472,11 +468,11 @@ A default `tsconfig.spec.json` after modifying will look like this } ``` -By default Jest doesn't transform `node_modules`, because they should be valid JavaScript files. However, it happens that library authors assume that you'll compile their sources. So you have to tell this to Jest explicitly. Above snippet means that `@ngrx`, `angular2-ui-switch` and `ng-dynamic` will be transformed, even though they're `node_modules`. +By default, Jest doesn't transform `node_modules`, because they should be valid JavaScript files. However, it happens that library authors assume that you'll compile their sources. So you have to tell this to Jest explicitly. Above snippet means that `@ngrx`, `angular2-ui-switch` and `ng-dynamic` will be transformed, even though they're `node_modules`. #### Allow JS files in your TS `compilerOptions` -```json +```json5 { "compilerOptions": { "allowJs": true @@ -493,6 +489,7 @@ Some vendors publish their sources without transpiling. You need to say jest to 1. Install dependencies required by the official Jest documentation for [Babel integration](https://jest-bot.github.io/jest/docs/babel.html). 2. Install `@babel/preset-env` and add `babel.config.js` (or modify existing if needed) with the following content: + ```js module.exports = function(api) { api.cache(true); @@ -505,7 +502,6 @@ module.exports = function(api) { plugins, }; }; - ``` *Note: do not use a `.babelrc` file otherwise the packages that you specify in the next step will not be picked up. CF [Babel documentation](https://babeljs.io/docs/en/configuration#what-s-your-use-case) and the comment `You want to compile node_modules? babel.config.js is for you!`*. @@ -513,13 +509,11 @@ module.exports = function(api) { 3. Update Jest configuration (by default TypeScript process untranspiled JS files which is source of the problem): ```js -{ - "jest": { - "transform": { - "^.+\\.(ts|html)$": "ts-jest", - "^.+\\.js$": "babel-jest" - }, - } +module.exports = { + transform: { + "^.+\\.(ts|html)$": "ts-jest", + "^.+\\.js$": "babel-jest" + }, } ``` diff --git a/e2e/test-app-v10/projects/my-lib/src/lib/disableable.directive.ts b/e2e/test-app-v10/projects/my-lib/src/lib/disableable.directive.ts new file mode 100644 index 0000000000..e76023b55d --- /dev/null +++ b/e2e/test-app-v10/projects/my-lib/src/lib/disableable.directive.ts @@ -0,0 +1,36 @@ +import { Directive, ElementRef, Input } from '@angular/core'; + +/** + * A base class for components that can be disabled, and that store their disabled + * state in the HTML element. This prevents the HTML element from being focused or clicked, + * and can be used for CSS selectors. + */ +@Directive() +export abstract class DisableableDirective { + + /** Binds to the HTML disabled property OR disabled attribute, if present. */ + @Input() + public set disabled(v: boolean) { + const elt = this.elementRef.nativeElement; + const disabledProp = (elt as any).disabled; + if (typeof (disabledProp) === 'boolean') { + // Set disabled property + (elt as any).disabled = v; + return; + } + + // Set disabled attribute + elt.setAttribute('disabled', v.toString()); + } + public get disabled(): boolean { + const elt = this.elementRef.nativeElement; + const disabledProp = (elt as any).disabled; + if (typeof (disabledProp) === 'boolean') { + return disabledProp; + } + const disabledAttr = elt.getAttribute('disabled'); + return disabledAttr === 'true'; + } + + constructor(public elementRef: ElementRef) { } +} diff --git a/e2e/test-app-v10/projects/my-lib/src/lib/my-lib.component.ts b/e2e/test-app-v10/projects/my-lib/src/lib/my-lib.component.ts index d3581b5516..6042f24bad 100644 --- a/e2e/test-app-v10/projects/my-lib/src/lib/my-lib.component.ts +++ b/e2e/test-app-v10/projects/my-lib/src/lib/my-lib.component.ts @@ -1,4 +1,6 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, ElementRef, OnInit } from '@angular/core'; + +import { DisableableDirective } from './disableable.directive'; @Component({ selector: 'lib-my-lib', @@ -10,11 +12,18 @@ import { Component, OnInit } from '@angular/core'; styles: [ ] }) -export class MyLibComponent implements OnInit { +export class MyLibComponent extends DisableableDirective implements OnInit { - constructor() { } + constructor(public elementRef: ElementRef) { + super(elementRef); + } ngOnInit(): void { } + toggle(): void { + if (!super.disabled) { + console.log('test'); + } + } } diff --git a/e2e/test-app-v10/src/app/forward-ref/forward-ref.spec.ts b/e2e/test-app-v10/src/app/forward-ref/forward-ref.spec.ts new file mode 100644 index 0000000000..d16dd01f0c --- /dev/null +++ b/e2e/test-app-v10/src/app/forward-ref/forward-ref.spec.ts @@ -0,0 +1,21 @@ +import { forwardRef, Inject, Injector } from '@angular/core'; + +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. +class Lock {} + +test('should work', () => { + const injector = Injector.create({providers: [{provide: Lock, deps: []}, {provide: Door, deps: [Lock]}]}); + + expect(injector.get(Door) instanceof Door).toBe(true); + expect(injector.get(Door).lock instanceof Lock).toBe(true); +}); diff --git a/e2e/test-app-v9/package.json b/e2e/test-app-v9/package.json index 0d02db6b98..ac91251fcb 100644 --- a/e2e/test-app-v9/package.json +++ b/e2e/test-app-v9/package.json @@ -18,6 +18,7 @@ "@angular/platform-browser": "~9.1.12", "@angular/platform-browser-dynamic": "~9.1.12", "@angular/router": "~9.1.12", + "ng2-google-charts": "^6.1.0", "rxjs": "^6.6.3", "tslib": "^1.14.1", "zone.js": "~0.10.3" diff --git a/e2e/test-app-v9/src/app/app.module.ts b/e2e/test-app-v9/src/app/app.module.ts index 7d08f4e6fe..00cf650cfe 100644 --- a/e2e/test-app-v9/src/app/app.module.ts +++ b/e2e/test-app-v9/src/app/app.module.ts @@ -12,11 +12,14 @@ import { SimpleWithStylesComponent } from './simple-with-styles/simple-with-styl import { ChildComponent } from './medium/child.component'; import { MediumComponent } from './medium/medium.component'; import { NgReflectAsTextComponent } from './ng-reflect-as-text/ng-reflect-as-text.component'; +import { GeoChartComponent } from './ngc-compiled-lib/ngc-compiled-lib.component'; +import { Ng2GoogleChartsModule } from 'ng2-google-charts'; @NgModule({ declarations: [ AppComponent, CalcComponent, + GeoChartComponent, SimpleComponent, OnPushComponent, HeroesComponent, @@ -30,6 +33,7 @@ import { NgReflectAsTextComponent } from './ng-reflect-as-text/ng-reflect-as-tex BrowserAnimationsModule, FormsModule, HttpClientModule, + Ng2GoogleChartsModule, ], providers: [], bootstrap: [AppComponent] diff --git a/e2e/test-app-v9/src/app/ngc-compiled-lib/ngc-compiled-lib.component.spec.ts b/e2e/test-app-v9/src/app/ngc-compiled-lib/ngc-compiled-lib.component.spec.ts new file mode 100644 index 0000000000..a25d9d6cd3 --- /dev/null +++ b/e2e/test-app-v9/src/app/ngc-compiled-lib/ngc-compiled-lib.component.spec.ts @@ -0,0 +1,34 @@ +import { CommonModule } from '@angular/common'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Ng2GoogleChartsModule } from 'ng2-google-charts'; +import { GeoChartComponent } from './ngc-compiled-lib.component'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; + +describe('GeoChartComponent', () => { + let component: GeoChartComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + Ng2GoogleChartsModule, + ], + providers: [], + declarations: [ GeoChartComponent ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + ], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GeoChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/e2e/test-app-v9/src/app/ngc-compiled-lib/ngc-compiled-lib.component.ts b/e2e/test-app-v9/src/app/ngc-compiled-lib/ngc-compiled-lib.component.ts new file mode 100644 index 0000000000..9c8bc4f403 --- /dev/null +++ b/e2e/test-app-v9/src/app/ngc-compiled-lib/ngc-compiled-lib.component.ts @@ -0,0 +1,64 @@ +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { GoogleChartInterface } from 'ng2-google-charts'; +import { BehaviorSubject } from 'rxjs'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: 'influo-geo-chart', + template: ` + + + + + + `, +}) +export class GeoChartComponent implements OnInit, OnChanges { + @Input() columns: any; + @Input() config: GoogleChartInterface; + @Input() data: Array>; + + private defaultConfig: GoogleChartInterface = { + chartType: 'GeoChart', + dataTable: [], + options: { + legend: false, + region: 155, + enableRegionInteractivity: true, + displayMode: 'region', + colors: [ '#e6e6e6', '#1672AD' ], + datalessRegionColor: '#e6e6e6', + }, + }; + + constructor() { + } + + _googleChartConfig = new BehaviorSubject(null); + + set googleChartConfig(config: GoogleChartInterface) { + const value = this._googleChartConfig.getValue() || {}; + + this._googleChartConfig.next(Object.assign({}, value, config)); + } + + get googleChartConfig$() { + return this._googleChartConfig.asObservable(); + } + + ngOnInit() { + } + + ngOnChanges(changes: SimpleChanges): void { + if (this.columns && this.data) { + this.googleChartConfig = Object.assign({}, this.defaultConfig, this.config, { + dataTable: [ + this.columns, + ...this.data, + ], + }); + } + } +} diff --git a/e2e/test-app-v9/yarn.lock b/e2e/test-app-v9/yarn.lock index 83beddb2a3..2014dbb50a 100644 --- a/e2e/test-app-v9/yarn.lock +++ b/e2e/test-app-v9/yarn.lock @@ -6079,6 +6079,13 @@ neo-async@^2.5.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +ng2-google-charts@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ng2-google-charts/-/ng2-google-charts-6.1.0.tgz#50a28d358d0e9cc5733f500f0ee325f67a213eda" + integrity sha512-nbxG4QdXVM8/ZsbMeMuETHYDQ8JfGDcNYBw8GjeAyZTykVjQykrjUgY+KRGIquhLJIoGMY7myCjlR4YZeKBNcQ== + dependencies: + tslib "^1.9.0" + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" diff --git a/jest-preset.js b/jest-preset.js index 3515fcbc1a..e3dca4f946 100644 --- a/jest-preset.js +++ b/jest-preset.js @@ -13,7 +13,7 @@ module.exports = { }, testEnvironment: 'jsdom', transform: { - '^.+\\.(ts|js|html)$': 'ts-jest', + '^.+\\.(ts|js|html)$': 'jest-preset-angular', }, moduleFileExtensions: ['ts', 'html', 'js', 'json'], moduleNameMapper: { diff --git a/package.json b/package.json index 8412fa3c68..4acc1c4cc5 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "husky": "4.x", "jest": "26.x", "lint-staged": "latest", - "lodash.memoize": "4.x", "prettier": "2.x", "rxjs": "6.x", "typescript": "3.x", diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index 0f7b70227c..461ae55d93 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -1,6 +1,37 @@ import { TsJestTransformer } from 'ts-jest/dist/ts-jest-transformer'; +import { NgJestCompiler } from '../compiler/ng-jest-compiler'; + 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 = { globals: {}, testMatch: [], testRegex: [] }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const obj2 = { ...obj1, globals: Object.create(null) }; + // eslint-disable-next-line + const cs1 = require('../').configsFor(obj1); + // eslint-disable-next-line + const cs2 = require('../').configsFor(obj2); + + expect(cs2).toBe(cs1); + }, + ); + + test('should return the same config set for same values with jest config objects', () => { + const obj1 = { globals: {}, testMatch: [], testRegex: [] }; + const obj2 = { ...obj1 }; + // eslint-disable-next-line + const cs1 = require('../').configsFor(obj1); + // eslint-disable-next-line + const cs2 = require('../').configsFor(obj2); + + expect(cs2).toBe(cs1); + }); + }); + describe('getCacheKey', () => { test('should call getCacheKey method from parent class TsJestTransformer', () => { TsJestTransformer.prototype.getCacheKey = jest.fn(); @@ -26,34 +57,78 @@ 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: '/foo/.', rootDir: '/bar//dummy/..', globals: {}, testMatch: [], testRegex: [] }, - }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const obj2 = { ...obj1, config: { ...obj1.config, globals: Object.create(null) } }; - // eslint-disable-next-line - const cs1 = require('../').configsFor(obj1); - // eslint-disable-next-line - const cs2 = require('../').configsFor(obj2); + describe('process', () => { + beforeEach(() => { + jest.spyOn(NgJestCompiler.prototype, 'getCompiledOutput').mockReturnValueOnce(''); + }); - expect(cs2).toBe(cs1); - }, - ); + afterEach(() => { + jest.restoreAllMocks(); + }); - test('should return the same config set for same values with jest config objects', () => { - const obj1 = { config: { cwd: '/foo/.', rootDir: '/bar//dummy/..', globals: {}, testMatch: [], testRegex: [] } }; - const obj2 = { ...obj1 }; + test.each(['foo.ts', 'foo.js'])('should compile ts or js with allowJs by NgJestCompiler', (fileName) => { + const jestCfg = { + cwd: './', + testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], + testRegex: ['(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.[jt]sx?$'], + globals: { 'ts-jest': { tsconfig: { allowJs: true } } }, + }; + const input = { + fileContent: 'const foo = 1', + jestConfigStr: '{"cwd": "./"}', + // eslint-disable-next-line + options: { config: { ...jestCfg } as any, instrument: false, rootDir: '/foo' }, + }; // eslint-disable-next-line - const cs1 = require('../').configsFor(obj1); + const ngJestTransformer = require('../'); + ngJestTransformer.getCacheKey(input.fileContent, fileName, input.jestConfigStr, input.options); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ngJestTransformer.process(input.fileContent, fileName, jestCfg as any); + + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(NgJestCompiler.prototype.getCompiledOutput).toHaveBeenCalledWith(fileName, input.fileContent); + }); + + 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 = { + cwd: './', + testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], + testRegex: ['(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.[jt]sx?$'], + globals: { + 'ts-jest': { + tsconfig: { allowJs: false }, + stringifyContentPathRegex: '\\.html$', + }, + }, + }; + const input = { + jestConfigStr: '{"cwd": "./"}', + // eslint-disable-next-line + options: { config: { ...jestCfg } as any, instrument: false, rootDir: '/foo' }, + }; // eslint-disable-next-line - const cs2 = require('../').configsFor(obj2); + const ngJestTransformer = require('../'); + ngJestTransformer.getCacheKey(fileContent, fileName, input.jestConfigStr, input.options); - expect(cs2).toBe(cs1); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ngJestTransformer.process(fileContent, fileName, jestCfg as any); + + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(NgJestCompiler.prototype.getCompiledOutput).not.toHaveBeenCalled(); }); }); }); diff --git a/src/compiler/ng-jest-compiler.ts b/src/compiler/ng-jest-compiler.ts index b6fcd16b0b..49ddd40637 100644 --- a/src/compiler/ng-jest-compiler.ts +++ b/src/compiler/ng-jest-compiler.ts @@ -18,7 +18,7 @@ export class NgJestCompiler { private readonly _logger: Logger; private readonly _ts: TTypeScript; - constructor(private readonly ngJestConfig: NgJestConfig) { + constructor(readonly ngJestConfig: NgJestConfig) { this._logger = this.ngJestConfig.logger; this._ts = this.ngJestConfig.compilerModule; this._setupOptions(this.ngJestConfig); @@ -79,8 +79,10 @@ export class NgJestCompiler { const result: ts.TranspileOutput = this._ts.transpileModule(fileContent, { fileName, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment transformers: this.ngJestConfig.customTransformers, compilerOptions: this._compilerOptions, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment reportDiagnostics: this.ngJestConfig.shouldReportDiagnostics(fileName), }); if (result.diagnostics && this.ngJestConfig.shouldReportDiagnostics(fileName)) { diff --git a/src/config/ng-jest-config.ts b/src/config/ng-jest-config.ts index d516cef53f..43a671d0d3 100644 --- a/src/config/ng-jest-config.ts +++ b/src/config/ng-jest-config.ts @@ -9,7 +9,7 @@ export class NgJestConfig extends ConfigSet { */ parsedTsConfig!: ParsedConfiguration; - constructor(public readonly jestCfg: Config.ProjectConfig) { + constructor(readonly jestCfg: Config.ProjectConfig) { super(jestCfg); } diff --git a/src/index.ts b/src/index.ts index 3f696da20e..2921bc597b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,18 @@ import type { TransformedSource, Transformer, TransformOptions } 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 { stringify } from 'ts-jest/dist/utils/json'; import { JsonableValue } from 'ts-jest/dist/utils/jsonable-value'; -import { TsJestTransformer } from 'ts-jest/dist/ts-jest-transformer'; import { NgJestConfig } from './config/ng-jest-config'; +import { NgJestCompiler } from './compiler/ng-jest-compiler'; interface CachedConfigSet { ngJestConfig: NgJestConfig; jestConfig: JsonableValue; transformerCfgStr: string; + ngJestCompiler: NgJestCompiler; } class NgJestTransformer extends TsJestTransformer implements Transformer { @@ -17,16 +20,24 @@ class NgJestTransformer extends TsJestTransformer implements Transformer { * cache config set between each test run */ private static readonly _cachedConfigSets: CachedConfigSet[] = []; - // @ts-expect-error Temporarily use ts-expect-error because we will use this later - private _ngJestConfig!: NgJestConfig; + private _ngJestCompiler!: NgJestCompiler; process( - input: string, + fileContent: string, filePath: Config.Path, jestConfig: Config.ProjectConfig, transformOptions?: TransformOptions, ): TransformedSource | string { - return super.process(input, filePath, jestConfig, transformOptions); + 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(jestConfig); + // 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, jestConfig, transformOptions) + : this._ngJestCompiler.getCompiledOutput(filePath, fileContent); } /** @@ -39,6 +50,7 @@ class NgJestTransformer extends TsJestTransformer implements Transformer { let ngJestConfig: NgJestConfig; if (ccs) { this._transformCfgStr = ccs.transformerCfgStr; + this._ngJestCompiler = ccs.ngJestCompiler; ngJestConfig = ccs.ngJestConfig; } else { // try to look-it up by stringified version @@ -52,12 +64,14 @@ class NgJestTransformer extends TsJestTransformer implements Transformer { // the config, and then it calls the transformer with the proper object serializedCcs.jestConfig.value = jestConfig; this._transformCfgStr = serializedCcs.transformerCfgStr; + this._ngJestCompiler = serializedCcs.ngJestCompiler; ngJestConfig = serializedCcs.ngJestConfig; } else { // create the new record in the index this.logger.info('no matching config-set found, creating a new one'); ngJestConfig = new NgJestConfig(jestConfig); + this._ngJestCompiler = new NgJestCompiler(ngJestConfig); this._transformCfgStr = new JsonableValue({ ...jestConfig, ...ngJestConfig.parsedTsConfig, @@ -67,6 +81,7 @@ class NgJestTransformer extends TsJestTransformer implements Transformer { ngJestConfig, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment transformerCfgStr: this._transformCfgStr, + ngJestCompiler: this._ngJestCompiler, }); } } diff --git a/yarn.lock b/yarn.lock index 93b7f732d5..52bc511bc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -830,9 +830,9 @@ integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== "@types/yargs@^15.0.0": - version "15.0.5" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79" - integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w== + version "15.0.9" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.9.tgz#524cd7998fe810cdb02f26101b699cccd156ff19" + integrity sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g== dependencies: "@types/yargs-parser" "*" @@ -1179,7 +1179,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== @@ -1187,6 +1187,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: "@types/color-name" "^1.1.1" color-convert "^2.0.1" +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -1567,7 +1574,7 @@ browserify-zlib@^0.2.0: bs-logger@0.x: version "0.2.6" - resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" @@ -4644,7 +4651,7 @@ make-dir@^3.0.0: make-error@1.x: version "1.3.6" - resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== makeerror@1.0.x: @@ -4905,7 +4912,7 @@ mixin-deep@^1.2.0: mkdirp@1.x: version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mkdirp@^0.5.1, mkdirp@^0.5.3: @@ -7202,10 +7209,10 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== -yargs-parser@20.x: - version "20.2.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.0.tgz#944791ca2be2e08ddadd3d87e9de4c6484338605" - integrity sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A== +yargs-parser@20.x, yargs-parser@^20.2.3: + version "20.2.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.3.tgz#92419ba867b858c868acf8bae9bf74af0dd0ce26" + integrity sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww== yargs-parser@^18.1.0, yargs-parser@^18.1.2, yargs-parser@^18.1.3: version "18.1.3" @@ -7215,11 +7222,6 @@ yargs-parser@^18.1.0, yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.3: - version "20.2.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.3.tgz#92419ba867b858c868acf8bae9bf74af0dd0ce26" - integrity sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww== - yargs@15.3.0: version "15.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.0.tgz#403af6edc75b3ae04bf66c94202228ba119f0976"