Skip to content

Commit b71b76f

Browse files
authored
refactor(config): deprecate import serializer paths (#855)
DEPRECATION - Import serializer via `'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js'` is deprecated in favor of `'jest-preset-angular/build/serializers/no-ng-attributes'` - Import serializer via `'jest-preset-angular/build/AngularSnapshotSerializer.js'` is deprecated in favor of `'jest-preset-angular/build/serializers/ng-snapshot'` - Import serializer via `'jest-preset-angular/build/HTMLCommentSerializer.js'` is deprecated in favor of `'jest-preset-angular/build/serializers/html-comment'`
1 parent 8a0bd65 commit b71b76f

File tree

10 files changed

+258
-4
lines changed

10 files changed

+258
-4
lines changed

jest-preset.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ module.exports = {
2323
},
2424
transformIgnorePatterns: ['node_modules/(?!@ngrx)'],
2525
snapshotSerializers: [
26-
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
27-
'jest-preset-angular/build/AngularSnapshotSerializer.js',
28-
'jest-preset-angular/build/HTMLCommentSerializer.js',
26+
'jest-preset-angular/build/serializers/html-comment',
27+
'jest-preset-angular/build/serializers/ng-snapshot',
28+
'jest-preset-angular/build/serializers/no-ng-attributes',
2929
],
3030
};

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
},
3535
"devDependencies": {
3636
"@angular-eslint/eslint-plugin": "latest",
37+
"@angular/core": "^9.1.13",
3738
"@commitlint/cli": "latest",
3839
"@commitlint/config-angular": "latest",
3940
"@types/jest": "26.x",
@@ -55,8 +56,11 @@
5556
"lint-staged": "latest",
5657
"lodash.memoize": "4.x",
5758
"prettier": "2.x",
59+
"rxjs": "^6.6.6",
5860
"ts-jest": "26.x",
59-
"typescript": "3.x"
61+
"tslib": "1.14.1",
62+
"typescript": "3.x",
63+
"zone.js": "0.10.3"
6064
},
6165
"husky": {
6266
"hooks": {

src/AngularNoNgAttributesSnapshotSerializer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
22

3+
const { rootLogger } = require('ts-jest/dist/utils/logger');
4+
5+
rootLogger.warn("The snapshot serializer under import path `'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js'` is deprecated and will be removed in v9.0.0. Please switch to `'jest-preset-angular/build/serializers/no-ng-attributes'`")
6+
37
const jestDOMElementSerializer = require('pretty-format').plugins.DOMElement;
48

59
const attributesToRemovePatterns = ['ng-reflect', '_nghost', '_ngcontent', 'ng-version'];

src/AngularSnapshotSerializer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
22

3+
const { rootLogger } = require('ts-jest/dist/utils/logger');
4+
5+
rootLogger.warn("The snapshot serializer under import path `'jest-preset-angular/build/AngularSnapshotSerializer.js'` is deprecated and will be removed in v9.0.0. Please switch to `'jest-preset-angular/build/serializers/ng-snapshot'`")
6+
37
const printAttributes = (val, attributes, print, indent, colors, opts) => {
48
return attributes
59
.sort()

src/HTMLCommentSerializer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
'use strict';
1111

12+
const { rootLogger } = require('ts-jest/dist/utils/logger');
13+
14+
rootLogger.warn("The snapshot serializer under import path `'jest-preset-angular/build/HTMLCommentSerializer.js'` is deprecated and will be removed in v9.0.0. Please switch to `'jest-preset-angular/build/serializers/html-comment'`")
15+
1216
const HTML_ELEMENT_REGEXP = /Comment/;
1317
const test = (value) =>
1418
value !== undefined &&

src/serializers/html-comment.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
3+
*
4+
* This source code is licensed under the BSD-style license found in the
5+
* LICENSE file in the root directory of this source tree. An additional grant
6+
* of patent rights can be found in the PATENTS file in the same directory.
7+
*/
8+
const HTML_ELEMENT_REGEXP = /Comment/;
9+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
10+
const test = (value: any): boolean =>
11+
value?.nodeType === 8 && value.constructor !== undefined && HTML_ELEMENT_REGEXP.test(value.constructor.name);
12+
13+
const print = (): string => '';
14+
15+
export = {
16+
print,
17+
test,
18+
};

src/serializers/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export = [
2+
'jest-preset-angular/build/serializers/html-comment',
3+
'jest-preset-angular/build/serializers/ng-snapshot',
4+
'jest-preset-angular/build/serializers/no-ng-attributes',
5+
];

src/serializers/ng-snapshot.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import type { Type, ComponentRef, ɵCssSelectorList, DebugNode } from '@angular/core';
2+
import type { ComponentFixture } from '@angular/core/testing';
3+
import type { Colors } from 'pretty-format';
4+
5+
/**
6+
* The follow interfaces are customized heavily inspired by @angular/core/core.d.ts
7+
*/
8+
interface ComponentDef {
9+
selectors: ɵCssSelectorList;
10+
}
11+
interface VEComponentType extends Type<unknown> {
12+
ngComponentDef: ComponentDef;
13+
}
14+
interface IvyComponentType extends Type<unknown> {
15+
ɵcmp: ComponentDef;
16+
}
17+
interface NgComponentRef extends ComponentRef<unknown> {
18+
_elDef: any; // eslint-disable-line @typescript-eslint/no-explicit-any
19+
_view: any; // eslint-disable-line @typescript-eslint/no-explicit-any
20+
}
21+
interface NgComponentFixture extends ComponentFixture<unknown> {
22+
componentRef: NgComponentRef;
23+
componentInstance: Record<string, unknown>;
24+
}
25+
interface VEDebugNode {
26+
renderElement: {
27+
childNodes: DebugNode[];
28+
};
29+
}
30+
31+
/**
32+
* The following types haven't been exported by jest so temporarily we copy typings from 'pretty-format'
33+
*/
34+
interface PluginOptions {
35+
edgeSpacing: string;
36+
min: boolean;
37+
spacing: string;
38+
}
39+
type Indent = (indentSpaces: string) => string;
40+
type Printer = (elementToSerialize: unknown) => string;
41+
42+
const printAttributes = (
43+
val: any, // eslint-disable-line @typescript-eslint/no-explicit-any
44+
attributes: string[],
45+
_print: Printer,
46+
indent: Indent,
47+
colors: Colors,
48+
opts: PluginOptions,
49+
): string => {
50+
return attributes
51+
.sort()
52+
.map((attribute) => {
53+
return (
54+
opts.spacing +
55+
indent(`${colors.prop.open}${attribute}${colors.prop.close}=`) +
56+
colors.value.open +
57+
(val.componentInstance[attribute] && val.componentInstance[attribute].constructor
58+
? `{[Function ${val.componentInstance[attribute].constructor.name}]}`
59+
: `"${val.componentInstance[attribute]}"`) +
60+
colors.value.close
61+
);
62+
})
63+
.join('');
64+
};
65+
66+
const ivyEnabled = (): boolean => {
67+
// Should be required lazily, since it will throw an exception
68+
// `Cannot resolve parameters...`.
69+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
70+
const { ɵivyEnabled } = require('@angular/core');
71+
72+
return !!ɵivyEnabled;
73+
};
74+
75+
// Ivy component definition was stored on the `ngComponentDef`
76+
// property before `9.0.0-next.10`. Since `9.0.0-next.10` it was
77+
// renamed to `ɵcmp`.
78+
const getComponentDef = (type: VEComponentType | IvyComponentType): ComponentDef =>
79+
(type as VEComponentType).ngComponentDef ?? (type as IvyComponentType).ɵcmp;
80+
81+
const print = (
82+
fixture: NgComponentFixture,
83+
print: Printer,
84+
indent: Indent,
85+
opts: PluginOptions,
86+
colors: Colors,
87+
): string => {
88+
let nodes = '';
89+
let componentAttrs = '';
90+
let componentName = '';
91+
92+
if (ivyEnabled()) {
93+
const componentDef = getComponentDef(fixture.componentRef.componentType as IvyComponentType);
94+
componentName = componentDef.selectors[0][0] as string;
95+
nodes = Array.from(fixture.componentRef.location.nativeElement.childNodes).map(print).join('');
96+
} else {
97+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
98+
componentName = fixture.componentRef._elDef.element?.name;
99+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
100+
nodes = (fixture.componentRef._view.nodes || [])
101+
// eslint-disable-next-line no-prototype-builtins
102+
.filter((node: VEDebugNode) => node?.hasOwnProperty('renderElement'))
103+
.map((node: VEDebugNode) => Array.from(node.renderElement.childNodes).map(print).join(''))
104+
.join(opts.edgeSpacing);
105+
}
106+
const attributes = Object.keys(fixture.componentInstance);
107+
if (attributes.length) {
108+
componentAttrs += printAttributes(fixture, attributes, print, indent, colors, opts);
109+
}
110+
111+
return (
112+
'<' +
113+
componentName +
114+
componentAttrs +
115+
(componentAttrs.length ? '\n' : '') +
116+
'>\n' +
117+
indent(nodes) +
118+
'\n</' +
119+
componentName +
120+
'>'
121+
);
122+
};
123+
124+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
125+
const test = (val: any): boolean =>
126+
val !== undefined &&
127+
val !== null &&
128+
typeof val === 'object' &&
129+
Object.prototype.hasOwnProperty.call(val, 'componentRef');
130+
131+
export = {
132+
print,
133+
test,
134+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { plugins } from 'pretty-format';
2+
3+
const jestDOMElementSerializer = plugins.DOMElement;
4+
5+
const attributesToRemovePatterns = ['ng-reflect', '_nghost', '_ngcontent', 'ng-version'];
6+
const attributesToClean: Record<string, RegExp[]> = {
7+
class: [/^(?:mat|cdk|ng).*-\w*\d+-\d+$/, /^ng-star-inserted$/], // e.g. "ng-tns-c25-1" or "ng-star-inserted", literally
8+
id: [/^(?:mat|cdk|ng).*-\d+$/], // e.g. "mat-input-4", "cdk-step-content-0-0"
9+
for: [/^(?:mat|cdk|ng).*-\d+$/], // e.g. "mat-form-field-label-9"
10+
'aria-owns': [/^(?:mat|cdk|ng).*-\d+$/], // e.g. "mat-input-4"
11+
'aria-labelledby': [/^(?:mat|cdk|ng).*-\d+$/], // e.g. "mat-input-4", "cdk-step-label-0-0"
12+
'aria-controls': [/^(?:mat|cdk|ng).*-\d+$/], // e.g. "cdk-step-content-2-0"
13+
};
14+
const hasAttributesToRemove = (attribute: Attr): boolean =>
15+
attributesToRemovePatterns.some((removePattern) => attribute.name.startsWith(removePattern));
16+
const hasAttributesToClean = (attribute: Attr): boolean =>
17+
Object.keys(attributesToClean).some((removePatternKey) => attribute.name === removePatternKey);
18+
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
20+
const serialize = (node: Element, ...rest: any): string => {
21+
const nodeCopy = node.cloneNode(true) as Element;
22+
// Remove angular-specific attributes
23+
Object.values(nodeCopy.attributes)
24+
.filter(hasAttributesToRemove)
25+
.forEach((attribute) => nodeCopy.attributes.removeNamedItem(attribute.name));
26+
// Remove angular auto-added classes
27+
Object.values(nodeCopy.attributes)
28+
.filter(hasAttributesToClean)
29+
.forEach((attribute: Attr) => {
30+
attribute.value = attribute.value
31+
.split(' ')
32+
.filter(
33+
(attrValue: string) =>
34+
!attributesToClean[attribute.name].some((attributeCleanRegex) => attributeCleanRegex.test(attrValue)),
35+
)
36+
.join(' ');
37+
if (attribute.value === '') {
38+
nodeCopy.attributes.removeNamedItem(attribute.name);
39+
} else {
40+
nodeCopy.attributes.setNamedItem(attribute);
41+
}
42+
});
43+
44+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
45+
// @ts-expect-error
46+
return jestDOMElementSerializer.serialize(nodeCopy, ...rest);
47+
};
48+
49+
const serializeTestFn = (val: Element): boolean =>
50+
val.attributes !== undefined &&
51+
Object.values(val.attributes).some(
52+
(attribute: Attr) => hasAttributesToRemove(attribute) || hasAttributesToClean(attribute),
53+
);
54+
const test = (val: Element): boolean => jestDOMElementSerializer.test(val) && serializeTestFn(val);
55+
56+
export = {
57+
serialize,
58+
test,
59+
};

yarn.lock

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
dependencies:
1010
"@typescript-eslint/experimental-utils" "4.3.0"
1111

12+
"@angular/core@^9.1.13":
13+
version "9.1.13"
14+
resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.1.13.tgz#a87bc5a7926a50aec0bfcead769078bc7f1b93fa"
15+
integrity sha512-mBm24Q9GjkAsxMAzqQ86U1078+yTEpr0+syMEruUtJ0HUH6Fzn3J+6xTLb+BVcGb9RkCkFaV9T5mcn6ZM0f++g==
16+
1217
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13":
1318
version "7.12.13"
1419
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
@@ -5239,6 +5244,13 @@ rxjs@^6.6.2:
52395244
dependencies:
52405245
tslib "^1.9.0"
52415246

5247+
rxjs@^6.6.6:
5248+
version "6.6.6"
5249+
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.6.tgz#14d8417aa5a07c5e633995b525e1e3c0dec03b70"
5250+
integrity sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==
5251+
dependencies:
5252+
tslib "^1.9.0"
5253+
52425254
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
52435255
version "5.2.1"
52445256
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@@ -5912,6 +5924,11 @@ tsconfig-paths@^3.9.0:
59125924
minimist "^1.2.0"
59135925
strip-bom "^3.0.0"
59145926

5927+
5928+
version "1.14.1"
5929+
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
5930+
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
5931+
59155932
tslib@^1.8.1, tslib@^1.9.0:
59165933
version "1.13.0"
59175934
resolved "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
@@ -6312,3 +6329,8 @@ yocto-queue@^0.1.0:
63126329
version "0.1.0"
63136330
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
63146331
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
6332+
6333+
6334+
version "0.10.3"
6335+
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.10.3.tgz#3e5e4da03c607c9dcd92e37dd35687a14a140c16"
6336+
integrity sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg==

0 commit comments

Comments
 (0)