refactor(core): add debug ranges to LViewDebug
with matchers (#38359)
This change provides better typing for the `LView.debug` property which is intended to be used by humans while debugging the application with `ngDevMode` turned on. In addition this chang also adds jasmine matchers for better asserting that `LView` is in the correct state. PR Close #38359
This commit is contained in:

committed by
Andrew Kushnir

parent
df7f3b04b5
commit
702958e968
@ -11,10 +11,13 @@ ts_library(
|
||||
"**/*_perf.ts",
|
||||
"domino.d.ts",
|
||||
"load_domino.ts",
|
||||
"is_shape_of.ts",
|
||||
"jit_spec.ts",
|
||||
"matchers.ts",
|
||||
],
|
||||
),
|
||||
deps = [
|
||||
":matchers",
|
||||
"//packages:types",
|
||||
"//packages/animations",
|
||||
"//packages/animations/browser",
|
||||
@ -34,6 +37,18 @@ ts_library(
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "matchers",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"is_shape_of.ts",
|
||||
"matchers.ts",
|
||||
],
|
||||
deps = [
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "domino",
|
||||
testonly = True,
|
||||
|
22
packages/core/test/render3/interfaces/node_spec.ts
Normal file
22
packages/core/test/render3/interfaces/node_spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {TNodeType, TNodeTypeAsString} from '@angular/core/src/render3/interfaces/node';
|
||||
|
||||
describe('node interfaces', () => {
|
||||
describe('TNodeType', () => {
|
||||
it('should agree with TNodeTypeAsString', () => {
|
||||
expect(TNodeTypeAsString[TNodeType.Container]).toEqual('Container');
|
||||
expect(TNodeTypeAsString[TNodeType.Projection]).toEqual('Projection');
|
||||
expect(TNodeTypeAsString[TNodeType.View]).toEqual('View');
|
||||
expect(TNodeTypeAsString[TNodeType.Element]).toEqual('Element');
|
||||
expect(TNodeTypeAsString[TNodeType.ElementContainer]).toEqual('ElementContainer');
|
||||
expect(TNodeTypeAsString[TNodeType.IcuContainer]).toEqual('IcuContainer');
|
||||
});
|
||||
});
|
||||
});
|
186
packages/core/test/render3/is_shape_of.ts
Normal file
186
packages/core/test/render3/is_shape_of.ts
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {TI18n} from '@angular/core/src/render3/interfaces/i18n';
|
||||
import {TNode} from '@angular/core/src/render3/interfaces/node';
|
||||
import {TView} from '@angular/core/src/render3/interfaces/view';
|
||||
|
||||
/**
|
||||
* A type used to create a runtime representation of a shape of object which matches the declared
|
||||
* interface at compile time.
|
||||
*
|
||||
* The purpose of this type is to ensure that the object must match all of the properties of a type.
|
||||
* This is later used by `isShapeOf` method to ensure that a particular object has a particular
|
||||
* shape.
|
||||
*
|
||||
* ```
|
||||
* interface MyShape {
|
||||
* foo: string,
|
||||
* bar: number
|
||||
* }
|
||||
*
|
||||
* const myShapeObj: {foo: '', bar: 0};
|
||||
* const ExpectedPropertiesOfShape = {foo: true, bar: true};
|
||||
*
|
||||
* isShapeOf(myShapeObj, ExpectedPropertiesOfShape);
|
||||
* ```
|
||||
*
|
||||
* The above code would verify that `myShapeObj` has `foo` and `bar` properties. However if later
|
||||
* `MyShape` is refactored to change a set of properties we would like to have a compile time error
|
||||
* that the `ExpectedPropertiesOfShape` also needs to be changed.
|
||||
*
|
||||
* ```
|
||||
* const ExpectedPropertiesOfShape = <ShapeOf<MyShape>>{foo: true, bar: true};
|
||||
* ```
|
||||
* The above code will force through compile time checks that the `ExpectedPropertiesOfShape` match
|
||||
* that of `MyShape`.
|
||||
*
|
||||
* See: `isShapeOf`
|
||||
*
|
||||
*/
|
||||
export type ShapeOf<T> = {
|
||||
[P in keyof T]: true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a particular object is of a given shape (duck-type version of `instanceof`.)
|
||||
*
|
||||
* ```
|
||||
* isShapeOf(someObj, {foo: true, bar: true});
|
||||
* ```
|
||||
*
|
||||
* The above code will be true if the `someObj` has both `foo` and `bar` property
|
||||
*
|
||||
* @param obj Object to test for.
|
||||
* @param shapeOf Desired shape.
|
||||
*/
|
||||
export function isShapeOf<T>(obj: any, shapeOf: ShapeOf<T>): obj is T {
|
||||
if (typeof obj === 'object' && obj) {
|
||||
return Object.keys(shapeOf).reduce(
|
||||
(prev, key) => prev && obj.hasOwnProperty(key), true as boolean);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `obj` matches the shape `TI18n`.
|
||||
* @param obj
|
||||
*/
|
||||
export function isTI18n(obj: any): obj is TI18n {
|
||||
return isShapeOf<TI18n>(obj, ShapeOfTI18n);
|
||||
}
|
||||
const ShapeOfTI18n: ShapeOf<TI18n> = {
|
||||
vars: true,
|
||||
create: true,
|
||||
update: true,
|
||||
icus: true,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if `obj` matches the shape `TView`.
|
||||
* @param obj
|
||||
*/
|
||||
export function isTView(obj: any): obj is TView {
|
||||
return isShapeOf<TView>(obj, ShapeOfTView);
|
||||
}
|
||||
const ShapeOfTView: ShapeOf<TView> = {
|
||||
type: true,
|
||||
id: true,
|
||||
blueprint: true,
|
||||
template: true,
|
||||
viewQuery: true,
|
||||
node: true,
|
||||
firstCreatePass: true,
|
||||
firstUpdatePass: true,
|
||||
data: true,
|
||||
bindingStartIndex: true,
|
||||
expandoStartIndex: true,
|
||||
staticViewQueries: true,
|
||||
staticContentQueries: true,
|
||||
firstChild: true,
|
||||
expandoInstructions: true,
|
||||
directiveRegistry: true,
|
||||
pipeRegistry: true,
|
||||
preOrderHooks: true,
|
||||
preOrderCheckHooks: true,
|
||||
contentHooks: true,
|
||||
contentCheckHooks: true,
|
||||
viewHooks: true,
|
||||
viewCheckHooks: true,
|
||||
destroyHooks: true,
|
||||
cleanup: true,
|
||||
components: true,
|
||||
queries: true,
|
||||
contentQueries: true,
|
||||
schemas: true,
|
||||
consts: true,
|
||||
incompleteFirstPass: true,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if `obj` matches the shape `TI18n`.
|
||||
* @param obj
|
||||
*/
|
||||
export function isTNode(obj: any): obj is TNode {
|
||||
return isShapeOf<TNode>(obj, ShapeOfTNode);
|
||||
}
|
||||
const ShapeOfTNode: ShapeOf<TNode> = {
|
||||
type: true,
|
||||
index: true,
|
||||
injectorIndex: true,
|
||||
directiveStart: true,
|
||||
directiveEnd: true,
|
||||
directiveStylingLast: true,
|
||||
propertyBindings: true,
|
||||
flags: true,
|
||||
providerIndexes: true,
|
||||
tagName: true,
|
||||
attrs: true,
|
||||
mergedAttrs: true,
|
||||
localNames: true,
|
||||
initialInputs: true,
|
||||
inputs: true,
|
||||
outputs: true,
|
||||
tViews: true,
|
||||
next: true,
|
||||
projectionNext: true,
|
||||
child: true,
|
||||
parent: true,
|
||||
projection: true,
|
||||
styles: true,
|
||||
stylesWithoutHost: true,
|
||||
residualStyles: true,
|
||||
classes: true,
|
||||
classesWithoutHost: true,
|
||||
residualClasses: true,
|
||||
classBindings: true,
|
||||
styleBindings: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if `obj` is DOM `Node`.
|
||||
*/
|
||||
export function isDOMNode(obj: any): obj is Node {
|
||||
return obj instanceof Node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `obj` is DOM `Text`.
|
||||
*/
|
||||
export function isDOMElement(obj: any): obj is Element {
|
||||
return obj instanceof Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `obj` is DOM `Text`.
|
||||
*/
|
||||
export function isDOMText(obj: any): obj is Text {
|
||||
return obj instanceof Text;
|
||||
}
|
37
packages/core/test/render3/is_shape_of_spec.ts
Normal file
37
packages/core/test/render3/is_shape_of_spec.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isShapeOf, ShapeOf} from './is_shape_of';
|
||||
|
||||
describe('isShapeOf', () => {
|
||||
const ShapeOfEmptyObject: ShapeOf<{}> = {};
|
||||
it('should not match for non objects', () => {
|
||||
expect(isShapeOf(null, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(0, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(1, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(true, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(false, ShapeOfEmptyObject)).toBeFalse();
|
||||
expect(isShapeOf(undefined, ShapeOfEmptyObject)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should match on empty object', () => {
|
||||
expect(isShapeOf({}, ShapeOfEmptyObject)).toBeTrue();
|
||||
expect(isShapeOf({extra: 'is ok'}, ShapeOfEmptyObject)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should match on shape', () => {
|
||||
expect(isShapeOf({required: 1}, {required: true})).toBeTrue();
|
||||
expect(isShapeOf({required: true, extra: 'is ok'}, {required: true})).toBeTrue();
|
||||
});
|
||||
|
||||
it('should not match if missing property', () => {
|
||||
expect(isShapeOf({required: 1}, {required: true, missing: true})).toBeFalse();
|
||||
expect(isShapeOf({required: true, extra: 'is ok'}, {required: true, missing: true}))
|
||||
.toBeFalse();
|
||||
});
|
||||
});
|
218
packages/core/test/render3/matchers.ts
Normal file
218
packages/core/test/render3/matchers.ts
Normal file
@ -0,0 +1,218 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {TI18n} from '@angular/core/src/render3/interfaces/i18n';
|
||||
import {TNode} from '@angular/core/src/render3/interfaces/node';
|
||||
import {TView} from '@angular/core/src/render3/interfaces/view';
|
||||
|
||||
import {isDOMElement, isDOMText, isTI18n, isTNode, isTView} from './is_shape_of';
|
||||
|
||||
|
||||
/**
|
||||
* Generic matcher which asserts that an object is of a given shape (`shapePredicate`) and that it
|
||||
* contains a subset of properties.
|
||||
*
|
||||
* @param name Name of `shapePredicate` to display when assertion fails.
|
||||
* @param shapePredicate Predicate which verifies that the object is of correct shape.
|
||||
* @param expected Expected set of properties to be found on the object.
|
||||
*/
|
||||
export function matchObjectShape<T>(
|
||||
name: string, shapePredicate: (obj: any) => obj is T,
|
||||
expected: Partial<T> = {}): jasmine.AsymmetricMatcher<T> {
|
||||
const matcher = function() {};
|
||||
let _actual: any = null;
|
||||
|
||||
matcher.asymmetricMatch = function(actual: any) {
|
||||
_actual = actual;
|
||||
if (!shapePredicate(actual)) return false;
|
||||
for (const key in expected) {
|
||||
if (expected.hasOwnProperty(key) && !jasmine.matchersUtil.equals(actual[key], expected[key]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
matcher.jasmineToString = function() {
|
||||
return `${toString(_actual, false)} != ${toString(expected, true)})`;
|
||||
};
|
||||
|
||||
function toString(obj: any, isExpected: boolean) {
|
||||
if (isExpected || shapePredicate(obj)) {
|
||||
const props =
|
||||
Object.keys(expected).map(key => `${key}: ${JSON.stringify((obj as any)[key])}`);
|
||||
if (isExpected === false) {
|
||||
// Push something to let the user know that there may be other ignored properties in actual
|
||||
props.push('...');
|
||||
}
|
||||
return `${name}({${props.length === 0 ? '' : '\n ' + props.join(',\n ') + '\n'}})`;
|
||||
} else {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches a `TView` of a given shape.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(tNode).toEqual(matchTView({type: TViewType.Root}));
|
||||
* expect({
|
||||
* node: tNode
|
||||
* }).toEqual({
|
||||
* node: matchTNode({type: TViewType.Root})
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expected optional properties which the `TView` must contain.
|
||||
*/
|
||||
export function matchTView(expected?: Partial<TView>): jasmine.AsymmetricMatcher<TView> {
|
||||
return matchObjectShape('TView', isTView, expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches a `TNode` of a given shape.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(tNode).toEqual(matchTNode({type: TNodeType.Element}));
|
||||
* expect({
|
||||
* node: tNode
|
||||
* }).toEqual({
|
||||
* node: matchTNode({type: TNodeType.Element})
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expected optional properties which the `TNode` must contain.
|
||||
*/
|
||||
export function matchTNode(expected?: Partial<TNode>): jasmine.AsymmetricMatcher<TNode> {
|
||||
return matchObjectShape('TNode', isTNode, expected);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches a `T18n` of a given shape.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(tNode).toEqual(matchT18n({vars: 0}));
|
||||
* expect({
|
||||
* node: tNode
|
||||
* }).toEqual({
|
||||
* node: matchT18n({vars: 0})
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expected optional properties which the `TI18n` must contain.
|
||||
*/
|
||||
export function matchTI18n(expected?: Partial<TI18n>): jasmine.AsymmetricMatcher<TI18n> {
|
||||
return matchObjectShape('TI18n', isTI18n, expected);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches a DOM Element.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(div).toEqual(matchT18n('div', {id: '123'}));
|
||||
* expect({
|
||||
* node: div
|
||||
* }).toEqual({
|
||||
* node: matchT18n('div', {id: '123'})
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expectedTagName optional DOM tag name.
|
||||
* @param expectedAttributes optional DOM element properties.
|
||||
*/
|
||||
export function matchDomElement(
|
||||
expectedTagName: string|undefined = undefined,
|
||||
expectedAttrs: {[key: string]: string|null} = {}): jasmine.AsymmetricMatcher<Element> {
|
||||
const matcher = function() {};
|
||||
let _actual: any = null;
|
||||
|
||||
matcher.asymmetricMatch = function(actual: any) {
|
||||
_actual = actual;
|
||||
if (!isDOMElement(actual)) return false;
|
||||
if (expectedTagName && (expectedTagName.toUpperCase() !== actual.tagName.toUpperCase())) {
|
||||
return false;
|
||||
}
|
||||
if (expectedAttrs) {
|
||||
for (const attrName in expectedAttrs) {
|
||||
if (expectedAttrs.hasOwnProperty(attrName)) {
|
||||
const expectedAttrValue = expectedAttrs[attrName];
|
||||
const actualAttrValue = actual.getAttribute(attrName);
|
||||
if (expectedAttrValue !== actualAttrValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
matcher.jasmineToString = function() {
|
||||
let actualStr = isDOMElement(_actual) ? `<${_actual.tagName}${toString(_actual.attributes)}>` :
|
||||
JSON.stringify(_actual);
|
||||
let expectedStr = `<${expectedTagName || '*'}${
|
||||
Object.keys(expectedAttrs).map(key => ` ${key}=${JSON.stringify(expectedAttrs[key])}`)}>`;
|
||||
return `[${actualStr} != ${expectedStr}]`;
|
||||
};
|
||||
|
||||
function toString(attrs: NamedNodeMap) {
|
||||
let text = '';
|
||||
for (let i = 0; i < attrs.length; i++) {
|
||||
const attr = attrs[i];
|
||||
text += ` ${attr.name}=${JSON.stringify(attr.value)}`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asymmetric matcher which matches DOM text node.
|
||||
*
|
||||
* Expected usage:
|
||||
* ```
|
||||
* expect(div).toEqual(matchDomText('text'));
|
||||
* expect({
|
||||
* node: div
|
||||
* }).toEqual({
|
||||
* node: matchDomText('text')
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param expectedText optional DOM text.
|
||||
*/
|
||||
export function matchDomText(expectedText: string|undefined = undefined):
|
||||
jasmine.AsymmetricMatcher<Text> {
|
||||
const matcher = function() {};
|
||||
let _actual: any = null;
|
||||
|
||||
matcher.asymmetricMatch = function(actual: any) {
|
||||
_actual = actual;
|
||||
if (!isDOMText(actual)) return false;
|
||||
if (expectedText && (expectedText !== actual.textContent)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
matcher.jasmineToString = function() {
|
||||
let actualStr = isDOMText(_actual) ? `#TEXT: ${JSON.stringify(_actual.textContent)}` :
|
||||
JSON.stringify(_actual);
|
||||
let expectedStr = `#TEXT: ${JSON.stringify(expectedText)}`;
|
||||
return `[${actualStr} != ${expectedStr}]`;
|
||||
};
|
||||
|
||||
return matcher;
|
||||
}
|
101
packages/core/test/render3/matchers_spec.ts
Normal file
101
packages/core/test/render3/matchers_spec.ts
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {createTNode, createTView} from '@angular/core/src/render3/instructions/shared';
|
||||
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
|
||||
import {TViewType} from '@angular/core/src/render3/interfaces/view';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
import {isShapeOf, ShapeOf} from './is_shape_of';
|
||||
import {matchDomElement, matchDomText, matchObjectShape, matchTNode, matchTView} from './matchers';
|
||||
import {dedent} from './utils';
|
||||
|
||||
describe('render3 matchers', () => {
|
||||
describe('matchObjectShape', () => {
|
||||
interface MyShape {
|
||||
propA: any;
|
||||
propB: any;
|
||||
}
|
||||
|
||||
const myShape: MyShape = {propA: 'value', propB: 3};
|
||||
function isMyShape(obj: any): obj is MyShape {
|
||||
return isShapeOf<MyShape>(obj, ShapeOfMyShape);
|
||||
}
|
||||
const ShapeOfMyShape: ShapeOf<MyShape> = {propA: true, propB: true};
|
||||
function matchMyShape(expected?: Partial<MyShape>): jasmine.AsymmetricMatcher<MyShape> {
|
||||
return matchObjectShape('MyShape', isMyShape, expected);
|
||||
}
|
||||
|
||||
it('should match', () => {
|
||||
expect(isMyShape(myShape)).toBeTrue();
|
||||
expect(myShape).toEqual(matchMyShape());
|
||||
expect(myShape).toEqual(matchMyShape({propA: 'value'}));
|
||||
expect({node: myShape}).toEqual({node: matchMyShape({propA: 'value'})});
|
||||
});
|
||||
|
||||
it('should produce human readable errors', () => {
|
||||
const matcher = matchMyShape({propA: 'different'});
|
||||
expect(matcher.asymmetricMatch(myShape, [])).toEqual(false);
|
||||
expect(matcher.jasmineToString!()).toEqual(dedent`
|
||||
MyShape({
|
||||
propA: "value",
|
||||
...
|
||||
}) != MyShape({
|
||||
propA: "different"
|
||||
}))`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchTView', () => {
|
||||
const tView = createTView(TViewType.Root, 1, null, 2, 3, null, null, null, null, null);
|
||||
it('should match', () => {
|
||||
expect(tView).toEqual(matchTView());
|
||||
expect(tView).toEqual(matchTView({type: TViewType.Root}));
|
||||
expect({node: tView}).toEqual({node: matchTView({type: TViewType.Root})});
|
||||
});
|
||||
});
|
||||
describe('matchTNode', () => {
|
||||
const tView = createTView(TViewType.Root, 1, null, 2, 3, null, null, null, null, null);
|
||||
const tNode = createTNode(tView, null, TNodeType.Element, 1, 'tagName', []);
|
||||
|
||||
it('should match', () => {
|
||||
expect(tNode).toEqual(matchTNode());
|
||||
expect(tNode).toEqual(matchTNode({type: TNodeType.Element, tagName: 'tagName'}));
|
||||
expect({node: tNode}).toEqual({node: matchTNode({type: TNodeType.Element})});
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchDomElement', () => {
|
||||
const div = document.createElement('div');
|
||||
div.setAttribute('name', 'Name');
|
||||
it('should match', () => {
|
||||
expect(div).toEqual(matchDomElement());
|
||||
expect(div).toEqual(matchDomElement('div', {name: 'Name'}));
|
||||
});
|
||||
|
||||
it('should produce human readable error', () => {
|
||||
const matcher = matchDomElement('div', {name: 'other'});
|
||||
expect(matcher.asymmetricMatch(div, [])).toEqual(false);
|
||||
expect(matcher.jasmineToString!()).toEqual(`[<DIV name="Name"> != <div name="other">]`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchDomText', () => {
|
||||
const text = document.createTextNode('myText');
|
||||
it('should match', () => {
|
||||
expect(text).toEqual(matchDomText());
|
||||
expect(text).toEqual(matchDomText('myText'));
|
||||
});
|
||||
|
||||
it('should produce human readable error', () => {
|
||||
const matcher = matchDomText('other text');
|
||||
expect(matcher.asymmetricMatch(text, [])).toEqual(false);
|
||||
expect(matcher.jasmineToString!()).toEqual(`[#TEXT: "myText" != #TEXT: "other text"]`);
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user