- Made `*OpCodes` array branded for safer type checking. - Simplify `I18NRemoveOpCodes` encoding. - Broke out `IcuCreateOpCodes` from `I18nMutableOpCodes`. PR Close #39233
		
			
				
	
	
		
			255 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			7.3 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 {I18nDebug, IcuCreateOpCodes, TI18n, TIcu} 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, isTIcu, 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() {
 | 
						|
    let errors: string[] = [];
 | 
						|
    if (!_actual || typeof _actual !== 'object') {
 | 
						|
      return `Expecting ${jasmine.pp(expect)} got ${jasmine.pp(_actual)}`;
 | 
						|
    }
 | 
						|
    for (const key in expected) {
 | 
						|
      if (expected.hasOwnProperty(key) && !jasmine.matchersUtil.equals(_actual[key], expected[key]))
 | 
						|
        errors.push(`\n  property obj.${key} to equal ${expected[key]} but got ${_actual[key]}`);
 | 
						|
    }
 | 
						|
    return errors.join('\n');
 | 
						|
  };
 | 
						|
 | 
						|
  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 `T1cu` of a given shape.
 | 
						|
 *
 | 
						|
 * Expected usage:
 | 
						|
 * ```
 | 
						|
 * expect(tNode).toEqual(matchTIcu({type: TIcuType.select}));
 | 
						|
 * expect({
 | 
						|
 *   type: TIcuType.select
 | 
						|
 * }).toEqual({
 | 
						|
 *   node: matchT18n({type: TIcuType.select})
 | 
						|
 * });
 | 
						|
 * ```
 | 
						|
 *
 | 
						|
 * @param expected optional properties which the `TIcu` must contain.
 | 
						|
 */
 | 
						|
export function matchTIcu(expected?: Partial<TIcu>): jasmine.AsymmetricMatcher<TIcu> {
 | 
						|
  return matchObjectShape('TIcu', isTIcu, 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;
 | 
						|
}
 | 
						|
 | 
						|
export function matchI18nMutableOpCodes(expectedMutableOpCodes: string[]):
 | 
						|
    jasmine.AsymmetricMatcher<IcuCreateOpCodes> {
 | 
						|
  const matcher = function() {};
 | 
						|
  let _actual: any = null;
 | 
						|
 | 
						|
  matcher.asymmetricMatch = function(actual: any) {
 | 
						|
    _actual = actual;
 | 
						|
    if (!Array.isArray(actual)) return false;
 | 
						|
    const debug = (actual as I18nDebug).debug as undefined | string[];
 | 
						|
    if (expectedMutableOpCodes && (!jasmine.matchersUtil.equals(debug, expectedMutableOpCodes))) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  };
 | 
						|
  matcher.jasmineToString = function() {
 | 
						|
    const debug = (_actual as I18nDebug).debug as undefined | string[];
 | 
						|
    return `[${JSON.stringify(debug)} != ${expectedMutableOpCodes}]`;
 | 
						|
  };
 | 
						|
 | 
						|
  return matcher;
 | 
						|
} |