Skip to content

Commit

Permalink
feat: use angular compiler api to transform codes (#562)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ahnpnl committed Dec 12, 2020
1 parent 12189f1 commit 1468ceb
Show file tree
Hide file tree
Showing 16 changed files with 339 additions and 76 deletions.
44 changes: 19 additions & 25 deletions README.md
Expand Up @@ -83,7 +83,7 @@ module.exports = {

- or to your root `package.json`

```json
```json5
{
"jest": {
"preset": "jest-preset-angular",
Expand Down Expand Up @@ -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: '<rootDir>/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: {
Expand All @@ -139,11 +140,7 @@ module.exports = {
'^environments/(.*)$': '<rootDir>/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,
};
```

Expand Down Expand Up @@ -293,7 +290,6 @@ describe('Component snapshots', () => {
const configure: ConfigureFn = testBed => {
testBed.configureTestingModule({
declarations: [FooComponent],
imports: [...],
schemas: [NO_ERRORS_SCHEMA],
});
};
Expand Down Expand Up @@ -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": {
Expand All @@ -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": {
Expand Down Expand Up @@ -462,7 +458,7 @@ A default `tsconfig.spec.json` after modifying will look like this

#### Adjust your `transformIgnorePatterns` whitelist:

```json
```json5
{
"jest": {
"transformIgnorePatterns": [
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -505,21 +502,18 @@ 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!`*.

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"
},
}
```

Expand Down
36 changes: 36 additions & 0 deletions 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<HTMLElement>) { }
}
15 changes: 12 additions & 3 deletions 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',
Expand All @@ -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');
}
}
}
21 changes: 21 additions & 0 deletions 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);
});
1 change: 1 addition & 0 deletions e2e/test-app-v9/package.json
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions e2e/test-app-v9/src/app/app.module.ts
Expand Up @@ -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,
Expand All @@ -30,6 +33,7 @@ import { NgReflectAsTextComponent } from './ng-reflect-as-text/ng-reflect-as-tex
BrowserAnimationsModule,
FormsModule,
HttpClientModule,
Ng2GoogleChartsModule,
],
providers: [],
bootstrap: [AppComponent]
Expand Down
@@ -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<GeoChartComponent>;

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();
});
});
@@ -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: `
<google-chart
*ngIf="googleChartConfig$ | async as googleChartConfig; else loader"
[data]="googleChartConfig">
</google-chart>
<ng-template #loader>
<mat-spinner color="accent" diameter="80" strokeWidth="8"></mat-spinner>
</ng-template>
`,
})
export class GeoChartComponent implements OnInit, OnChanges {
@Input() columns: any;
@Input() config: GoogleChartInterface;
@Input() data: Array<Array<string | number>>;

private defaultConfig: GoogleChartInterface = {
chartType: 'GeoChart',
dataTable: [],
options: {
legend: false,
region: 155,
enableRegionInteractivity: true,
displayMode: 'region',
colors: [ '#e6e6e6', '#1672AD' ],
datalessRegionColor: '#e6e6e6',
},
};

constructor() {
}

_googleChartConfig = new BehaviorSubject<GoogleChartInterface | null>(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,
],
});
}
}
}
7 changes: 7 additions & 0 deletions e2e/test-app-v9/yarn.lock
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion jest-preset.js
Expand Up @@ -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: {
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -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",
Expand Down

0 comments on commit 1468ceb

Please sign in to comment.