diff --git a/.circleci/config.yml b/.circleci/config.yml index b39fd4acd6..85ad1852ff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,19 +43,21 @@ jobs: steps: - attach_workspace: at: . - - restore_cache: - keys: - - yarn-cache-example-{{ .Branch }}-{{ checksum "yarn.lock" }} - - yarn-cache-example-{{ .Branch }}- + # disabling cache here as it created issues with the preset in the root + # not being used, as older version in cache was available + #- restore_cache: + # keys: + # - yarn-cache-example-{{ .Branch }}-{{ checksum "yarn.lock" }} + # - yarn-cache-example-{{ .Branch }}- - run: name: Install Example Dependencies command: | cd example yarn install --frozen-lockfile - - save_cache: - key: yarn-cache-example-{{ .Branch }}-{{ checksum "yarn.lock" }} - paths: - - ./example/node_modules + #- save_cache: + # key: yarn-cache-example-{{ .Branch }}-{{ checksum "yarn.lock" }} + # paths: + # - ./example/node_modules - run: name: Test Example command: | diff --git a/.gitignore b/.gitignore index 744743d7d0..10aa828c4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules InlineHtmlStripStylesTransformer.js *.log +.idea diff --git a/AngularNoNgAttributesSnapshotSerializer.js b/AngularNoNgAttributesSnapshotSerializer.js new file mode 100644 index 0000000000..ed4b66aea7 --- /dev/null +++ b/AngularNoNgAttributesSnapshotSerializer.js @@ -0,0 +1,30 @@ +'use strict'; + +const jestDOMElementSerializer = require('pretty-format').plugins.DOMElement; + +const attributesToRemovePatterns = ['ng-reflect', '_nghost', '_ngcontent', 'ng-version']; + +const hasAttributesToRemove = (attribute) => + attributesToRemovePatterns + .some(removePattern => attribute.name.startsWith(removePattern)); + +const serialize = (node, ...rest) => { + const nodeCopy = node.cloneNode(true); + Object.values(nodeCopy.attributes) + .filter(hasAttributesToRemove) + .forEach(attribute => nodeCopy.attributes.removeNamedItem(attribute.name)); + + return jestDOMElementSerializer.serialize(nodeCopy, ...rest); +}; + +const serializeTestFn = (val) => { + return val.attributes !== undefined && Object.values(val.attributes) + .some(hasAttributesToRemove) +}; +const test = val => + jestDOMElementSerializer.test(val) && serializeTestFn(val); + +module.exports = { + serialize: serialize, + test: test +}; diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d0b962e3..b032817330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Changelog (master) +* Added `AngularNoNgAttributesSnapshotSerializer`. Using this serializer makes snapshots clearer and more human-readable. You have to apply this serializer manually by redefining `snapshotSerializers` `jest` option. +* Fixed a CI cache issue in the example app, which would not always use the current version of the preset in the test runs. + ### v7.0.0 #### Features diff --git a/README.md b/README.md index f9f9128be5..6724e1df8c 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,8 @@ module.exports = { - `"moduleFileExtensions"` – our modules are TypeScript and JavaScript files - `"moduleNameMapper"` – if you're using absolute imports here's how to tell Jest where to look for them; uses regex - `"setupFilesAfterEnv"` – this is the heart of our config, in this file we'll setup and patch environment within tests are running -- `"transformIgnorePatterns"` – unfortunately some modules (like @ngrx ) are released as TypeScript files, not pure JavaScript; in such cases we cannot ignore them (all node_modules are ignored by default), so they can be transformed through TS compiler like any other module in our project. +- `"transformIgnorePatterns"` – unfortunately some modules (like @ngrx) are released as TypeScript files, not pure JavaScript; in such cases we cannot ignore them (all node_modules are ignored by default), so they can be transformed through TS compiler like any other module in our project. +- `"snapshotSerializers"` - array of serializers which will be applied to snapshot the code. Note: by default angular adds some angular-specific attributes to the code (like `ng-reflect-*`, `ng-version="*"`, `_ngcontent-c*` etc). This package provides serializer to remove such attributes. This makes snapshots cleaner and more human-readable. To remove such specific attributes use `AngularNoNgAttributesSnapshotSerializer` serializer. You need to add `AngularNoNgAttributesSnapshotSerializer` serializer manually (see [`example` app configuration](https://github.com/thymikee/jest-preset-angular/blob/master/example/package.json#L47-L51)). ## [AST Transformer](https://github.com/thymikee/jest-preset-angular/blob/master/src/InlineHtmlStripStylesTransformer.ts) diff --git a/example/package.json b/example/package.json index 10e8e6ddef..605c8fe006 100644 --- a/example/package.json +++ b/example/package.json @@ -44,6 +44,11 @@ }, "jest": { "preset": "jest-preset-angular", + "snapshotSerializers": [ + "jest-preset-angular/AngularNoNgAttributesSnapshotSerializer.js", + "jest-preset-angular/AngularSnapshotSerializer.js", + "jest-preset-angular/HTMLCommentSerializer.js" + ], "moduleNameMapper": { "\\.(jpg|jpeg|png)$": "/__mocks__/image.js", "^@lib/(.*)$": "/src/lib/$1" diff --git a/example/src/app/app.component.css b/example/src/app/app.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/example/src/app/app.component.ts b/example/src/app/app.component.ts index 8490388337..30adc92126 100644 --- a/example/src/app/app.component.ts +++ b/example/src/app/app.component.ts @@ -1,19 +1,16 @@ -import {Component, HostBinding, OnInit} from '@angular/core'; +import {Component, HostBinding} from '@angular/core'; import {trigger, transition, style, animate} from '@angular/animations'; @Component({ selector: 'app-root', animations: [slideToLeft()], - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'], + templateUrl: './app.component.html' }) -export class AppComponent implements OnInit{ +export class AppComponent { @HostBinding('@routerTransition') title = 'app works!'; hasClass = true; variableWithPrecedingDolar = 1234; - - ngOnInit() {} } export function slideToLeft() { diff --git a/example/src/app/app.module.ts b/example/src/app/app.module.ts index babd47c6d6..f38223b6c2 100644 --- a/example/src/app/app.module.ts +++ b/example/src/app/app.module.ts @@ -8,6 +8,8 @@ import { CalcComponent } from './calc/calc.component'; import { SimpleComponent } from './simple/simple.component'; import { OnPushComponent } from './on-push/on-push.component'; import { HeroesComponent } from './heroes/heroes.component'; +import { SimpleWithStylesComponent } from './simple-with-styles/simple-with-styles.component'; +import { ChildComponent } from './medium/child.component'; @NgModule({ declarations: [ @@ -15,7 +17,9 @@ import { HeroesComponent } from './heroes/heroes.component'; CalcComponent, SimpleComponent, OnPushComponent, - HeroesComponent + HeroesComponent, + SimpleWithStylesComponent, + ChildComponent ], imports: [ BrowserModule, diff --git a/example/src/app/calc/__snapshots__/calc.component.spec.ts.snap b/example/src/app/calc/__snapshots__/calc.component.spec.ts.snap index 17a2085089..f97b8bfd3b 100644 --- a/example/src/app/calc/__snapshots__/calc.component.spec.ts.snap +++ b/example/src/app/calc/__snapshots__/calc.component.spec.ts.snap @@ -8,8 +8,6 @@ exports[`CalcComponent should snap 1`] = ` >

calc works! 1337 another text node test-image-stub

diff --git a/example/src/app/calc/calc.component.css b/example/src/app/calc/calc.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/example/src/app/calc/calc.component.ts b/example/src/app/calc/calc.component.ts index 84f5c5f88f..6a69cc6cc3 100644 --- a/example/src/app/calc/calc.component.ts +++ b/example/src/app/calc/calc.component.ts @@ -17,8 +17,7 @@ const image = require('assets/its_something.png'); another text node {{image}}

- `, - styleUrls: ['./calc.component.css'] + ` }) export class CalcComponent implements OnInit { @Input() hasAClass = false; diff --git a/example/src/app/heroes/heroes.component.css b/example/src/app/heroes/heroes.component.css deleted file mode 100644 index c9859da8e1..0000000000 --- a/example/src/app/heroes/heroes.component.css +++ /dev/null @@ -1 +0,0 @@ -/* HeroesComponent's private CSS styles */ diff --git a/example/src/app/heroes/heroes.component.ts b/example/src/app/heroes/heroes.component.ts index 53e952a145..caa1e435e2 100644 --- a/example/src/app/heroes/heroes.component.ts +++ b/example/src/app/heroes/heroes.component.ts @@ -5,8 +5,7 @@ import { HeroService } from '../service/hero.service'; @Component({ selector: 'app-heroes', - templateUrl: './heroes.component.html', - styleUrls: ['./heroes.component.css'] + templateUrl: './heroes.component.html' }) export class HeroesComponent implements OnInit { heroes: Hero[]; diff --git a/example/src/app/medium/__snapshots__/medium.component.spec.ts.snap b/example/src/app/medium/__snapshots__/medium.component.spec.ts.snap new file mode 100644 index 0000000000..ecd1ec1754 --- /dev/null +++ b/example/src/app/medium/__snapshots__/medium.component.spec.ts.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MediumComponent snapshot test 1`] = ` + +
+ diary works! + +
+ +
+ another case +
+ + some html + +
+
+ one more case case +
+
+ + stubbedBody + +
+
+
+`; diff --git a/example/src/app/medium/child.component.ts b/example/src/app/medium/child.component.ts new file mode 100644 index 0000000000..99a9e4bb7c --- /dev/null +++ b/example/src/app/medium/child.component.ts @@ -0,0 +1,20 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-child-component', + template: ` +
+ {{ someInput }} +
+ rly +

complex

+
component +
oh my god!
+
+
+
+ ` +}) +export class ChildComponent { + @Input() someInput = null; +} diff --git a/example/src/app/medium/medium.component.spec.ts b/example/src/app/medium/medium.component.spec.ts new file mode 100644 index 0000000000..03ae291b69 --- /dev/null +++ b/example/src/app/medium/medium.component.spec.ts @@ -0,0 +1,34 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MediumComponent } from './medium.component'; +import { ChildComponent } from './child.component'; + +describe('MediumComponent', () => { + let component: MediumComponent; + let fixture: ComponentFixture; + + beforeEach( + async(() => { + TestBed.configureTestingModule({ + declarations: [MediumComponent, ChildComponent], + }) + .overrideComponent(ChildComponent, {set: {template: 'stubbedBody'}}) + .compileComponents(); + }), + ); + + beforeEach(() => { + fixture = TestBed.createComponent(MediumComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('snapshot test', () => { + expect(fixture).toMatchSnapshot(); + }); +}); diff --git a/example/src/app/medium/medium.component.ts b/example/src/app/medium/medium.component.ts new file mode 100644 index 0000000000..1903fbda3f --- /dev/null +++ b/example/src/app/medium/medium.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-medium', + template: ` +
+ diary works! +
+
+
another case
+ some html +
+
one more case case
+
+
+ ` +}) +export class MediumComponent { + someVar = true; + anotherVar = false; +} diff --git a/example/src/app/ng-reflect-as-text/__snapshots__/ng-reflect-as-text.component.spec.ts.snap b/example/src/app/ng-reflect-as-text/__snapshots__/ng-reflect-as-text.component.spec.ts.snap new file mode 100644 index 0000000000..8d1f5a6626 --- /dev/null +++ b/example/src/app/ng-reflect-as-text/__snapshots__/ng-reflect-as-text.component.spec.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NgReflectAsTextComponent snapshots 1`] = ` + +

+ ng-reflect-as-text works! + +

+
+`; diff --git a/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.html b/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.html new file mode 100644 index 0000000000..7fe2ffc7e5 --- /dev/null +++ b/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.html @@ -0,0 +1,3 @@ +

+ ng-reflect-as-text works! +

diff --git a/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.spec.ts b/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.spec.ts new file mode 100644 index 0000000000..cc754ead58 --- /dev/null +++ b/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NgReflectAsTextComponent } from './ng-reflect-as-text.component'; + +describe('NgReflectAsTextComponent', () => { + let component: NgReflectAsTextComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NgReflectAsTextComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NgReflectAsTextComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('snapshots', () => { + expect(fixture).toMatchSnapshot(); + }); +}); diff --git a/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.ts b/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.ts new file mode 100644 index 0000000000..228a500796 --- /dev/null +++ b/example/src/app/ng-reflect-as-text/ng-reflect-as-text.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-ng-reflect-as-text', + templateUrl: './ng-reflect-as-text.component.html' +}) +export class NgReflectAsTextComponent {} diff --git a/example/src/app/simple-with-styles/__snapshots__/simple-with-styles.component.spec.ts.snap b/example/src/app/simple-with-styles/__snapshots__/simple-with-styles.component.spec.ts.snap new file mode 100644 index 0000000000..b602dac2bd --- /dev/null +++ b/example/src/app/simple-with-styles/__snapshots__/simple-with-styles.component.spec.ts.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SimpleWithStylesComponent should generate snapshot without nghost/ngcontent 1`] = ` + +

+ simple-with-styles works! + +

+ +
+ +
+ rly +

+ complex +

+
+ component +
+ oh my god! +
+
+
+
+
+
+
+`; diff --git a/example/src/app/simple-with-styles/simple-with-styles.component.html b/example/src/app/simple-with-styles/simple-with-styles.component.html new file mode 100644 index 0000000000..b11a0d21d6 --- /dev/null +++ b/example/src/app/simple-with-styles/simple-with-styles.component.html @@ -0,0 +1,6 @@ +

+ simple-with-styles works! +

+
+ +
diff --git a/example/src/app/simple-with-styles/simple-with-styles.component.spec.ts b/example/src/app/simple-with-styles/simple-with-styles.component.spec.ts new file mode 100644 index 0000000000..dac9bfa469 --- /dev/null +++ b/example/src/app/simple-with-styles/simple-with-styles.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SimpleWithStylesComponent } from './simple-with-styles.component'; +import { ChildComponent } from '../medium/child.component'; + +describe('SimpleWithStylesComponent', () => { + let component: SimpleWithStylesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SimpleWithStylesComponent, ChildComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleWithStylesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should generate snapshot without nghost/ngcontent', () => { + expect(fixture).toMatchSnapshot(); + }); +}); diff --git a/example/src/app/simple-with-styles/simple-with-styles.component.ts b/example/src/app/simple-with-styles/simple-with-styles.component.ts new file mode 100644 index 0000000000..9948242454 --- /dev/null +++ b/example/src/app/simple-with-styles/simple-with-styles.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-simple-with-styles', + templateUrl: './simple-with-styles.component.html', + // we have to setup styles this way, since simple styles/styleUrs properties will be removed (jest does not unit test styles) + ['styles']: [` + .some-class { color: red } + `] +}) +export class SimpleWithStylesComponent {} diff --git a/example/src/app/simple/__snapshots__/simple.component.spec.ts.snap b/example/src/app/simple/__snapshots__/simple.component.spec.ts.snap index 7c20ea59a6..eed2903adc 100644 --- a/example/src/app/simple/__snapshots__/simple.component.spec.ts.snap +++ b/example/src/app/simple/__snapshots__/simple.component.spec.ts.snap @@ -1,5 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`SimpleComponent snapshot on nativeElement should be without ng-version 1`] = ` +
+

+ simple works! + +

+
+`; + exports[`SimpleComponent snapshots 1`] = `

diff --git a/example/src/app/simple/simple.component.css b/example/src/app/simple/simple.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/example/src/app/simple/simple.component.spec.ts b/example/src/app/simple/simple.component.spec.ts index 96309e0fdb..e2f450d546 100644 --- a/example/src/app/simple/simple.component.spec.ts +++ b/example/src/app/simple/simple.component.spec.ts @@ -30,5 +30,9 @@ describe('SimpleComponent', () => { it('snapshots', () => { expect(fixture).toMatchSnapshot(); - }) + }); + + it('snapshot on nativeElement should be without ng-version', () => { + expect(fixture.nativeElement).toMatchSnapshot(); + }); }); diff --git a/example/src/app/simple/simple.component.ts b/example/src/app/simple/simple.component.ts index 3690844248..3eeef34e9a 100644 --- a/example/src/app/simple/simple.component.ts +++ b/example/src/app/simple/simple.component.ts @@ -1,15 +1,7 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'app-simple', - templateUrl: 'simple.component.html', - styleUrls: ['./simple.component.css'] + templateUrl: 'simple.component.html' }) -export class SimpleComponent implements OnInit { - - constructor() { } - - ngOnInit() { - } - -} +export class SimpleComponent {} diff --git a/example/yarn.lock b/example/yarn.lock index 20cbaf24ed..95e90a4775 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -3953,6 +3953,7 @@ jest-mock@^24.0.0: version "7.0.1" dependencies: jest-environment-jsdom-thirteen "^1.0.0" + pretty-format "^24.0.0" ts-jest "^24.0.0" jest-regex-util@^24.0.0: diff --git a/jest-preset.js b/jest-preset.js index 2864adddd4..da04315d42 100644 --- a/jest-preset.js +++ b/jest-preset.js @@ -19,6 +19,7 @@ module.exports = { }, transformIgnorePatterns: ['node_modules/(?!@ngrx)'], snapshotSerializers: [ + // 'jest-preset-angular/AngularNoNgAttributesSnapshotSerializer.js', 'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/HTMLCommentSerializer.js', ], diff --git a/package.json b/package.json index 6352892ccb..c7e0fc130e 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "license": "MIT", "dependencies": { "jest-environment-jsdom-thirteen": "^1.0.0", + "pretty-format": "^24.0.0", "ts-jest": "^24.0.0" }, "devDependencies": {