274 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * @license
 | 
						|
 * Copyright Google Inc. 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 {StringMapWrapper} from './facade/collection';
 | 
						|
import {global, isString} from './facade/lang';
 | 
						|
import {getDOM} from './private_import_platform-browser';
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Jasmine matchers that check Angular specific conditions.
 | 
						|
 */
 | 
						|
export interface NgMatchers extends jasmine.Matchers {
 | 
						|
  /**
 | 
						|
   * Expect the value to be a `Promise`.
 | 
						|
   *
 | 
						|
   * ## Example
 | 
						|
   *
 | 
						|
   * {@example testing/ts/matchers.ts region='toBePromise'}
 | 
						|
   */
 | 
						|
  toBePromise(): boolean;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Expect the value to be an instance of a class.
 | 
						|
   *
 | 
						|
   * ## Example
 | 
						|
   *
 | 
						|
   * {@example testing/ts/matchers.ts region='toBeAnInstanceOf'}
 | 
						|
   */
 | 
						|
  toBeAnInstanceOf(expected: any): boolean;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Expect the element to have exactly the given text.
 | 
						|
   *
 | 
						|
   * ## Example
 | 
						|
   *
 | 
						|
   * {@example testing/ts/matchers.ts region='toHaveText'}
 | 
						|
   */
 | 
						|
  toHaveText(expected: string): boolean;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Expect the element to have the given CSS class.
 | 
						|
   *
 | 
						|
   * ## Example
 | 
						|
   *
 | 
						|
   * {@example testing/ts/matchers.ts region='toHaveCssClass'}
 | 
						|
   */
 | 
						|
  toHaveCssClass(expected: string): boolean;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Expect the element to have the given CSS styles.
 | 
						|
   *
 | 
						|
   * ## Example
 | 
						|
   *
 | 
						|
   * {@example testing/ts/matchers.ts region='toHaveCssStyle'}
 | 
						|
   */
 | 
						|
  toHaveCssStyle(expected: {[k: string]: string}|string): boolean;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Expect a class to implement the interface of the given class.
 | 
						|
   *
 | 
						|
   * ## Example
 | 
						|
   *
 | 
						|
   * {@example testing/ts/matchers.ts region='toImplement'}
 | 
						|
   */
 | 
						|
  toImplement(expected: any): boolean;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Expect an exception to contain the given error text.
 | 
						|
   *
 | 
						|
   * ## Example
 | 
						|
   *
 | 
						|
   * {@example testing/ts/matchers.ts region='toContainError'}
 | 
						|
   */
 | 
						|
  toContainError(expected: any): boolean;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Invert the matchers.
 | 
						|
   */
 | 
						|
  not: NgMatchers;
 | 
						|
}
 | 
						|
 | 
						|
const _global = <any>(typeof window === 'undefined' ? global : window);
 | 
						|
 | 
						|
/**
 | 
						|
 * Jasmine matching function with Angular matchers mixed in.
 | 
						|
 *
 | 
						|
 * ## Example
 | 
						|
 *
 | 
						|
 * {@example testing/ts/matchers.ts region='toHaveText'}
 | 
						|
 */
 | 
						|
export const expect: (actual: any) => NgMatchers = <any>_global.expect;
 | 
						|
 | 
						|
 | 
						|
// Some Map polyfills don't polyfill Map.toString correctly, which
 | 
						|
// gives us bad error messages in tests.
 | 
						|
// The only way to do this in Jasmine is to monkey patch a method
 | 
						|
// to the object :-(
 | 
						|
(Map as any).prototype['jasmineToString'] = function() {
 | 
						|
  const m = this;
 | 
						|
  if (!m) {
 | 
						|
    return '' + m;
 | 
						|
  }
 | 
						|
  const res: any[] = [];
 | 
						|
  m.forEach((v: any, k: any) => { res.push(`${k}:${v}`); });
 | 
						|
  return `{ ${res.join(',')} }`;
 | 
						|
};
 | 
						|
 | 
						|
_global.beforeEach(function() {
 | 
						|
  jasmine.addMatchers({
 | 
						|
    // Custom handler for Map as Jasmine does not support it yet
 | 
						|
    toEqual: function(util) {
 | 
						|
      return {
 | 
						|
        compare: function(actual: any, expected: any) {
 | 
						|
          return {pass: util.equals(actual, expected, [compareMap])};
 | 
						|
        }
 | 
						|
      };
 | 
						|
 | 
						|
      function compareMap(actual: any, expected: any) {
 | 
						|
        if (actual instanceof Map) {
 | 
						|
          let pass = actual.size === expected.size;
 | 
						|
          if (pass) {
 | 
						|
            actual.forEach((v: any, k: any) => { pass = pass && util.equals(v, expected.get(k)); });
 | 
						|
          }
 | 
						|
          return pass;
 | 
						|
        } else {
 | 
						|
          return undefined;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    toBePromise: function() {
 | 
						|
      return {
 | 
						|
        compare: function(actual: any) {
 | 
						|
          const pass = typeof actual === 'object' && typeof actual.then === 'function';
 | 
						|
          return {pass: pass, get message() { return 'Expected ' + actual + ' to be a promise'; }};
 | 
						|
        }
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    toBeAnInstanceOf: function() {
 | 
						|
      return {
 | 
						|
        compare: function(actual: any, expectedClass: any) {
 | 
						|
          const pass = typeof actual === 'object' && actual instanceof expectedClass;
 | 
						|
          return {
 | 
						|
            pass: pass,
 | 
						|
            get message() {
 | 
						|
              return 'Expected ' + actual + ' to be an instance of ' + expectedClass;
 | 
						|
            }
 | 
						|
          };
 | 
						|
        }
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    toHaveText: function() {
 | 
						|
      return {
 | 
						|
        compare: function(actual: any, expectedText: string) {
 | 
						|
          const actualText = elementText(actual);
 | 
						|
          return {
 | 
						|
            pass: actualText == expectedText,
 | 
						|
            get message() { return 'Expected ' + actualText + ' to be equal to ' + expectedText; }
 | 
						|
          };
 | 
						|
        }
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    toHaveCssClass: function() {
 | 
						|
      return {compare: buildError(false), negativeCompare: buildError(true)};
 | 
						|
 | 
						|
      function buildError(isNot: boolean) {
 | 
						|
        return function(actual: any, className: string) {
 | 
						|
          return {
 | 
						|
            pass: getDOM().hasClass(actual, className) == !isNot,
 | 
						|
            get message() {
 | 
						|
              return `Expected ${actual.outerHTML} ${isNot ? 'not ' : ''}to contain the CSS class "${className}"`;
 | 
						|
            }
 | 
						|
          };
 | 
						|
        };
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    toHaveCssStyle: function() {
 | 
						|
      return {
 | 
						|
        compare: function(actual: any, styles: {[k: string]: string}|string) {
 | 
						|
          let allPassed: boolean;
 | 
						|
          if (isString(styles)) {
 | 
						|
            allPassed = getDOM().hasStyle(actual, styles);
 | 
						|
          } else {
 | 
						|
            allPassed = !StringMapWrapper.isEmpty(styles);
 | 
						|
            StringMapWrapper.forEach(styles, (style: string, prop: string) => {
 | 
						|
              allPassed = allPassed && getDOM().hasStyle(actual, prop, style);
 | 
						|
            });
 | 
						|
          }
 | 
						|
 | 
						|
          return {
 | 
						|
            pass: allPassed,
 | 
						|
            get message() {
 | 
						|
              const expectedValueStr = isString(styles) ? styles : JSON.stringify(styles);
 | 
						|
              return `Expected ${actual.outerHTML} ${!allPassed ? ' ' : 'not '}to contain the
 | 
						|
                      CSS ${isString(styles) ? 'property' : 'styles'} "${expectedValueStr}"`;
 | 
						|
            }
 | 
						|
          };
 | 
						|
        }
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    toContainError: function() {
 | 
						|
      return {
 | 
						|
        compare: function(actual: any, expectedText: any) {
 | 
						|
          const errorMessage = actual.toString();
 | 
						|
          return {
 | 
						|
            pass: errorMessage.indexOf(expectedText) > -1,
 | 
						|
            get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
 | 
						|
          };
 | 
						|
        }
 | 
						|
      };
 | 
						|
    },
 | 
						|
 | 
						|
    toImplement: function() {
 | 
						|
      return {
 | 
						|
        compare: function(actualObject: any, expectedInterface: any) {
 | 
						|
          const intProps = Object.keys(expectedInterface.prototype);
 | 
						|
 | 
						|
          const missedMethods: any[] = [];
 | 
						|
          intProps.forEach((k) => {
 | 
						|
            if (!actualObject.constructor.prototype[k]) missedMethods.push(k);
 | 
						|
          });
 | 
						|
 | 
						|
          return {
 | 
						|
            pass: missedMethods.length == 0,
 | 
						|
            get message() {
 | 
						|
              return 'Expected ' + actualObject + ' to have the following methods: ' +
 | 
						|
                  missedMethods.join(', ');
 | 
						|
            }
 | 
						|
          };
 | 
						|
        }
 | 
						|
      };
 | 
						|
    }
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
function elementText(n: any): string {
 | 
						|
  var hasNodes = (n: any) => {
 | 
						|
    const children = getDOM().childNodes(n);
 | 
						|
    return children && children.length > 0;
 | 
						|
  };
 | 
						|
 | 
						|
  if (n instanceof Array) {
 | 
						|
    return n.map(elementText).join('');
 | 
						|
  }
 | 
						|
 | 
						|
  if (getDOM().isCommentNode(n)) {
 | 
						|
    return '';
 | 
						|
  }
 | 
						|
 | 
						|
  if (getDOM().isElementNode(n) && getDOM().tagName(n) == 'CONTENT') {
 | 
						|
    return elementText(Array.prototype.slice.apply(getDOM().getDistributedNodes(n)));
 | 
						|
  }
 | 
						|
 | 
						|
  if (getDOM().hasShadowRoot(n)) {
 | 
						|
    return elementText(getDOM().childNodesAsList(getDOM().getShadowRoot(n)));
 | 
						|
  }
 | 
						|
 | 
						|
  if (hasNodes(n)) {
 | 
						|
    return elementText(getDOM().childNodesAsList(n));
 | 
						|
  }
 | 
						|
 | 
						|
  return getDOM().getText(n);
 | 
						|
}
 |