218 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			218 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * @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; | ||
|  | } |