diff --git a/e2e/snapshot-serializers/__tests__/__snapshots__/foo.component.spec.ts.snap b/e2e/snapshot-serializers/__tests__/__snapshots__/foo.component.spec.ts.snap index 978fdb5cb1..665fb1ba9d 100644 --- a/e2e/snapshot-serializers/__tests__/__snapshots__/foo.component.spec.ts.snap +++ b/e2e/snapshot-serializers/__tests__/__snapshots__/foo.component.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`snapshot should work 1`] = ` +exports[`FooComponent should allow generating snapshot 1`] = ` `; -exports[`snapshot should work 2`] = ` +exports[`FooComponent should allow generating snapshot 2`] = ` +
+

+ Line 1 +

+
+
+ val1 +
+ + +
+
+`; + +exports[`FooComponent should allow generating snapshot with removed component attributes with snapshot serializer option 1`] = ` + +

+ Line 1 +

+
+ val1 +
+
+
+`; + +exports[`FooComponent should allow generating snapshot with removed component attributes with snapshot serializer option 2`] = `
diff --git a/e2e/snapshot-serializers/__tests__/foo.component.html b/e2e/snapshot-serializers/__tests__/foo.component.html index 26144b2b95..d2b85c3343 100644 --- a/e2e/snapshot-serializers/__tests__/foo.component.html +++ b/e2e/snapshot-serializers/__tests__/foo.component.html @@ -1,3 +1,4 @@ +

Line 1

diff --git a/e2e/snapshot-serializers/__tests__/foo.component.spec.ts b/e2e/snapshot-serializers/__tests__/foo.component.spec.ts index 648e7cf2b5..af377589ef 100644 --- a/e2e/snapshot-serializers/__tests__/foo.component.spec.ts +++ b/e2e/snapshot-serializers/__tests__/foo.component.spec.ts @@ -1,5 +1,7 @@ import { Component, Input } from '@angular/core'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; + +import serializer from '../../../build/serializers/ng-snapshot'; @Component({ selector: 'foo', @@ -22,14 +24,44 @@ class FooComponent { condition2 = false; } -test('snapshot should work', waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [FooComponent], +describe('FooComponent', () => { + test('should allow generating snapshot with removed component attributes with snapshot serializer option', () => { + expect.addSnapshotSerializer({ + print: (val, print, indent, options, colors) => + serializer.print( + val, + print, + indent, + { + ...options, + omitAllCompAttrs: true, + }, + colors, + ), + test: serializer.test, + }); + + TestBed.configureTestingModule({ + declarations: [FooComponent], + }); + + const fixture = TestBed.createComponent(FooComponent); + fixture.detectChanges(); + + expect(fixture).toMatchSnapshot(); + expect(fixture.debugElement.nativeElement).toMatchSnapshot(); }); - const fixture = TestBed.createComponent(FooComponent); - fixture.detectChanges(); + test('should allow generating snapshot', () => { + expect.addSnapshotSerializer(serializer); + TestBed.configureTestingModule({ + declarations: [FooComponent], + }); + + const fixture = TestBed.createComponent(FooComponent); + fixture.detectChanges(); - expect(fixture).toMatchSnapshot(); - expect(fixture.debugElement.nativeElement).toMatchSnapshot(); -})); + expect(fixture).toMatchSnapshot(); + expect(fixture.debugElement.nativeElement).toMatchSnapshot(); + }); +}); diff --git a/src/serializers/ng-snapshot.ts b/src/serializers/ng-snapshot.ts index b494e17063..b5c1500472 100644 --- a/src/serializers/ng-snapshot.ts +++ b/src/serializers/ng-snapshot.ts @@ -4,15 +4,35 @@ import type { OldPlugin } from 'pretty-format'; const attributesToRemovePatterns = ['__ngContext__']; -const print: OldPlugin['print'] = (fixture, printer, indent, opts, colors) => { - let componentAttrs = ''; +type PluginPrintFnArgs = Parameters; + +type NgSnapshotOptions = { + omitAllCompAttrs?: boolean; +}; + +type PluginPrintFn = ( + fixture: PluginPrintFnArgs[0], + printer: PluginPrintFnArgs[1], + indent: PluginPrintFnArgs[2], + opts: PluginPrintFnArgs[3] & NgSnapshotOptions, + colors: PluginPrintFnArgs[4], +) => string; + +const removeTrailingWhiteSpaces = (serializedComponent: string): string => { + return serializedComponent.replace(/\n^\s*\n/gm, '\n'); +}; + +const print: PluginPrintFn = (fixture, printer, indent, opts, colors) => { const { componentRef, componentInstance } = fixture as ComponentFixture>; const componentDef = (componentRef.componentType as ɵComponentType).ɵcmp as ɵDirectiveDef; const componentName = componentDef.selectors[0][0] as string; const nodes = Array.from(componentRef.location.nativeElement.childNodes).map(printer).join(''); - const attributes = Object.keys(componentInstance).filter((key) => !attributesToRemovePatterns.includes(key)); - if (attributes.length) { - componentAttrs += attributes + let serializedComponent = ''; + if (opts.omitAllCompAttrs) { + serializedComponent = '<' + componentName + '>\n' + indent(nodes) + '\n'; + } else { + const attributes = Object.keys(componentInstance).filter((key) => !attributesToRemovePatterns.includes(key)); + const componentAttrs = attributes .sort() .map((attribute) => { const compAttrVal = componentInstance[attribute]; @@ -28,19 +48,21 @@ const print: OldPlugin['print'] = (fixture, printer, indent, opts, colors) => { ); }) .join(''); + serializedComponent = + '<' + + componentName + + componentAttrs + + (componentAttrs.length ? '\n' : '') + + '>\n' + + indent(nodes) + + '\n'; } - return ( - '<' + - componentName + - componentAttrs + - (componentAttrs.length ? '\n' : '') + - '>\n' + - indent(nodes) + - '\n' - ).replace(/\n^\s*\n/gm, '\n'); + serializedComponent = removeTrailingWhiteSpaces(serializedComponent); + + return serializedComponent; }; const test: OldPlugin['test'] = (val) => !!val && typeof val === 'object' && 'componentRef' in val; diff --git a/website/docs/getting-started/options.md b/website/docs/getting-started/options.md index b9b6914649..91e53b4752 100644 --- a/website/docs/getting-started/options.md +++ b/website/docs/getting-started/options.md @@ -82,7 +82,7 @@ export default jestConfig; ```js tab // jest.config.js -const snapshotSerializers = require('../build/serializers'); +const snapshotSerializers = require('jest-preset-angular/build/serializers'); module.exports = { moduleFileExtensions: ['ts', 'html', 'js', 'json', 'mjs'], @@ -140,10 +140,7 @@ Jest runs with `jest-preset-angular` neither in browser nor through dev server. - `"moduleFileExtensions"` – our modules are TypeScript (`ts`), HTML (`html`), JavaScript (`js`), JSON (`json`) and ESM JavaScript (`mjs`) files. - `"moduleNameMapper"` – if you're using absolute imports here's how to tell Jest where to look for them; uses `RegExp`. - `"resolver"` - instruct Jest how to resolve entry file based on `package.json` definitions. -- `"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 `no-ng-attributes` serializer. You need to add `no-ng-attributes` serializer manually. +- `"snapshotSerializers"` - array of serializers which will be applied to snapshot the code. See more in [Snapshot testing](../guides/snapshot-testing.md) - `"testEnvironment"` – the test environment to run on. - `"transformIgnorePatterns"`: instruct Jest to transform any `.mjs` files which come from `node_modules`. - `"transform"` – run every `TS`, `JS`, `MJS`, `HTML`, or `SVG` file through so called _Jest transformer_; this lets Jest understand non-JS syntax. diff --git a/website/docs/guides/snapshot-testing.md b/website/docs/guides/snapshot-testing.md new file mode 100644 index 0000000000..e4431fba85 --- /dev/null +++ b/website/docs/guides/snapshot-testing.md @@ -0,0 +1,404 @@ +--- +id: snapshot-testing +title: Snapshot testing +--- + +`jest-preset-angular` provides several snapshot serializers to generate clearer and more human-readable snapshot. + +:::info + +**BY DEFAULT**, the [preset](../getting-started/presets.md) provides all of the snapshot serializers below. + +::: + +## Snapshot serializers + +import TOCInline from '@theme/TOCInline'; + + + +--- + +## Reference + +### Remove html comments (`html-comment`) + +Allow removing all the comments in the component HTML in snapshot. + +Examples: + +#### In Jest config + +```js tab title="jest.config.js" +/** @type {import('jest').Config} */ +module.exports = { + //[...] + snapshotSerializers: ['jest-preset-angular/build/serializers/html-comment'], + //[...] +}; +``` + +```ts tab title="jest.config.ts" +import type { Config } from 'jest'; + +const jestConfig: Config = { + //[...] + snapshotSerializers: ['jest-preset-angular/build/serializers/html-comment'], + //[...] +}; + +export default jestConfig; +``` + +#### Or in setup test environment file + +```js tab={"span":2} title="jest.config.js" +/** @type {import('jest').Config} */ +module.exports = { + //[...] + setupFilesAfterEnv: ['./setup-jest.js'], + //[...] +}; +``` + +```js title="setup-jest.js" +const removeHtmlCommentsSerializer = require('jest-preset-angular/build/serializers/html-comment'); + +expect.addSnapshotSerializer(removeHtmlCommentsSerializer); +``` + +```ts tab={"span":2} title="jest.config.ts" +import type { Config } from 'jest'; + +const jestConfig: Config = { + //[...] + setupFilesAfterEnv: ['./setup-jest.ts'], + //[...] +}; +``` + +```ts title="setup-jest.ts" +import removeHtmlCommentsSerializer from 'jest-preset-angular/build/serializers/html-comment'; + +expect.addSnapshotSerializer(removeHtmlCommentsSerializer); +``` + +#### Or in individual test files + +```js tab title="foo.component.spec.js" +const removeHtmlCommentsSerializer = require('jest-preset-angular/build/serializers/html-comment'); + +expect.addSnapshotSerializer(removeHtmlCommentsSerializer); + +it('should work', () => { + //[...] +}); +``` + +```ts tab title="foo.component.spec.ts" +import removeHtmlCommentsSerializer from 'jest-preset-angular/build/serializers/html-comment'; + +expect.addSnapshotSerializer(removeHtmlCommentsSerializer); + +it('should work', () => { + //[...] +}); +``` + +### Display component HTML (`ng-snapshot`) + +Allow displaying component HTML with data in snapshot. + +#### Configuration options + +```ts +type NgSnapshotOptions = { + omitAllCompAttrs?: boolean; +}; +``` + +Configure snapshot behavior + +Examples: + +#### In Jest config + +```js tab title="jest.config.js" +/** @type {import('jest').Config} */ +module.exports = { + //[...] + snapshotSerializers: ['jest-preset-angular/build/serializers/ng-snapshot'], + //[...] +}; +``` + +```ts tab title="jest.config.ts" +import type { Config } from 'jest'; + +const jestConfig: Config = { + //[...] + snapshotSerializers: ['jest-preset-angular/build/serializers/ng-snapshot'], + //[...] +}; + +export default jestConfig; +``` + +#### Or in setup test environment file + +```js tab={"span":2} title="jest.config.js" +/** @type {import('jest').Config} */ +module.exports = { + //[...] + setupFilesAfterEnv: ['./setup-jest.js'], + //[...] +}; +``` + +```js title="setup-jest.js" +const componentSnapshotSerializer = require('jest-preset-angular/build/serializers/ng-snapshot'); + +expect.addSnapshotSerializer(componentSnapshotSerializer); +``` + +```ts tab={"span":2} title="jest.config.ts" +import type { Config } from 'jest'; + +const jestConfig: Config = { + //[...] + setupFilesAfterEnv: ['./setup-jest.ts'], + //[...] +}; +``` + +```ts title="setup-jest.ts" +import componentSnapshotSerializer from 'jest-preset-angular/build/serializers/ng-snapshot'; + +expect.addSnapshotSerializer(componentSnapshotSerializer); +``` + +#### Or in individual test files + +```js tab title="foo.component.spec.js" +const componentSnapshotSerializer = require('jest-preset-angular/build/serializers/ng-snapshot'); + +expect.addSnapshotSerializer(componentSnapshotSerializer); + +it('should work', () => { + //[...] +}); +``` + +```ts tab title="foo.component.spec.ts" +import componentSnapshotSerializer from 'jest-preset-angular/build/serializers/ng-snapshot'; + +expect.addSnapshotSerializer(componentSnapshotSerializer); + +it('should work', () => { + //[...] +}); +``` + +#### With options + +:::info Effective priority + +The configured serializers will have affect in this order: + +`Jest config` -> `setup files` -> `test files` + +The later the higher priority. This means that with the same serializer, the later one will override the configuration +of the previous one in the chain. + +::: + +- In setup files: + +```js tab={"span":2} title="jest.config.js" +/** @type {import('jest').Config} */ +module.exports = { + //[...] + setupFilesAfterEnv: ['./setup-jest.js'], + //[...] +}; +``` + +```js tab title="setup-jest.js" +const componentSnapshotSerializer = require('jest-preset-angular/build/serializers/ng-snapshot'); + +expect.addSnapshotSerializer({ + print: (val, print, indent, options, colors) => + componentSnapshotSerializer.print( + val, + print, + indent, + { + ...options, + omitAllCompAttrs: true, + }, + colors, + ), + test: componentSnapshotSerializer.test, +}); +``` + +```ts tab={"span":2} title="jest.config.ts" +import type { Config } from 'jest'; + +const jestConfig: Config = { + //[...] + setupFilesAfterEnv: ['./setup-jest.ts'], + //[...] +}; +``` + +```ts tab title="setup-jest.ts" +import componentSnapshotSerializer from 'jest-preset-angular/build/serializers/ng-snapshot'; + +expect.addSnapshotSerializer({ + print: (val, print, indent, options, colors) => + componentSnapshotSerializer.print( + val, + print, + indent, + { + ...options, + omitAllCompAttrs: true, + }, + colors, + ), + test: componentSnapshotSerializer.test, +}); +``` + +- or in individual test files: + +```js tab title="foo.component.spec.js" +const componentSnapshotSerializer = require('jest-preset-angular/build/serializers/ng-snapshot'); + +expect.addSnapshotSerializer({ + print: (val, print, indent, options, colors) => + componentSnapshotSerializer.print( + val, + print, + indent, + { + ...options, + omitAllCompAttrs: true, + }, + colors, + ), + test: componentSnapshotSerializer.test, +}); + +it('should work', () => { + //[...] +}); +``` + +```ts tab title="foo.component.spec.ts" +import componentSnapshotSerializer from 'jest-preset-angular/build/serializers/ng-snapshot'; + +expect.addSnapshotSerializer({ + print: (val, print, indent, options, colors) => + componentSnapshotSerializer.print( + val, + print, + indent, + { + ...options, + omitAllCompAttrs: true, + }, + colors, + ), + test: componentSnapshotSerializer.test, +}); + +it('should work', () => { + //[...] +}); +``` + +### Remove Angular attributes (`no-ng-attributes`) + +Allow removing attributes generated by Angular fixture, like `ng-reflect-*`, `ng-version="*"`, `_ngcontent-c*` etc., from component snapshot + +Examples: + +#### In Jest config + +```js tab title="jest.config.js" +/** @type {import('jest').Config} */ +module.exports = { + //[...] + snapshotSerializers: ['jest-preset-angular/build/serializers/no-ng-attributes'], + //[...] +}; +``` + +```ts tab title="jest.config.ts" +import type { Config } from 'jest'; + +const jestConfig: Config = { + //[...] + snapshotSerializers: ['jest-preset-angular/build/serializers/no-ng-attributes'], + //[...] +}; + +export default jestConfig; +``` + +#### Or in setup test environment file + +```js tab={"span":2} title="jest.config.js" +/** @type {import('jest').Config} */ +module.exports = { + //[...] + setupFilesAfterEnv: ['./setup-jest.js'], + //[...] +}; +``` + +```js title="setup-jest.js" +const removeNgAttributes = require('jest-preset-angular/build/serializers/no-ng-attributes'); + +expect.addSnapshotSerializer(removeNgAttributes); +``` + +```ts tab={"span":2} title="jest.config.ts" +import type { Config } from 'jest'; + +const jestConfig: Config = { + //[...] + setupFilesAfterEnv: ['./setup-jest.ts'], + //[...] +}; +``` + +```ts title="setup-jest.ts" +import removeNgAttributes from 'jest-preset-angular/build/serializers/no-ng-attributes'; + +expect.addSnapshotSerializer(removeNgAttributes); +``` + +#### Or in individual test files + +```js tab title="foo.component.spec.js" +const removeNgAttributes = require('jest-preset-angular/build/serializers/no-ng-attributes'); + +expect.addSnapshotSerializer(removeNgAttributes); + +it('should work', () => { + //[...] +}); +``` + +```ts tab title="foo.component.spec.ts" +import removeNgAttributes from 'jest-preset-angular/build/serializers/no-ng-attributes'; + +expect.addSnapshotSerializer(removeNgAttributes); + +it('should work', () => { + //[...] +}); +``` diff --git a/website/sidebars.json b/website/sidebars.json index 2acc54fd9c..9fa6c495d2 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -15,9 +15,10 @@ "guides/angular-13+", "guides/esm-support", "guides/jsdom-version", + "guides/snapshot-testing", "guides/using-with-babel", "guides/absolute-imports", "guides/troubleshooting" ] } -} \ No newline at end of file +}