2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2016-10-03 19:46:05 -04:00
|
|
|
|
2018-08-05 09:35:51 -04:00
|
|
|
import {Type, ɵglobal as global} from '@angular/core';
|
|
|
|
import {ComponentFixture} from '@angular/core/testing';
|
|
|
|
import {By, ɵgetDOM as getDOM} from '@angular/platform-browser';
|
2017-02-17 15:55:55 -05:00
|
|
|
|
2016-08-30 21:07:40 -04:00
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Jasmine matchers that check Angular specific conditions.
|
|
|
|
*/
|
2018-07-05 08:24:53 -04:00
|
|
|
export interface NgMatchers<T = any> extends jasmine.Matchers<T> {
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Expect the value to be a `Promise`.
|
|
|
|
*
|
2018-09-20 09:51:54 -04:00
|
|
|
* @usageNotes
|
|
|
|
* ### Example
|
2015-12-03 18:49:09 -05:00
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toBePromise'}
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
toBePromise(): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect the value to be an instance of a class.
|
|
|
|
*
|
2018-09-20 09:51:54 -04:00
|
|
|
* @usageNotes
|
|
|
|
* ### Example
|
2015-12-03 18:49:09 -05:00
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toBeAnInstanceOf'}
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
toBeAnInstanceOf(expected: any): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect the element to have exactly the given text.
|
|
|
|
*
|
2018-09-20 09:51:54 -04:00
|
|
|
* @usageNotes
|
|
|
|
* ### Example
|
2015-12-03 18:49:09 -05:00
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toHaveText'}
|
|
|
|
*/
|
2016-07-29 15:20:52 -04:00
|
|
|
toHaveText(expected: string): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect the element to have the given CSS class.
|
|
|
|
*
|
2018-09-20 09:51:54 -04:00
|
|
|
* @usageNotes
|
|
|
|
* ### Example
|
2015-12-03 18:49:09 -05:00
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toHaveCssClass'}
|
|
|
|
*/
|
2016-07-29 15:20:52 -04:00
|
|
|
toHaveCssClass(expected: string): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect the element to have the given CSS styles.
|
|
|
|
*
|
2018-09-20 09:51:54 -04:00
|
|
|
* @usageNotes
|
|
|
|
* ### Example
|
2015-12-03 18:49:09 -05:00
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toHaveCssStyle'}
|
|
|
|
*/
|
2016-07-29 15:20:52 -04:00
|
|
|
toHaveCssStyle(expected: {[k: string]: string}|string): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect a class to implement the interface of the given class.
|
|
|
|
*
|
2018-09-20 09:51:54 -04:00
|
|
|
* @usageNotes
|
|
|
|
* ### Example
|
2015-12-03 18:49:09 -05:00
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toImplement'}
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
toImplement(expected: any): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect an exception to contain the given error text.
|
|
|
|
*
|
2018-09-20 09:51:54 -04:00
|
|
|
* @usageNotes
|
|
|
|
* ### Example
|
2015-12-03 18:49:09 -05:00
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toContainError'}
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
toContainError(expected: any): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
2018-08-05 09:35:51 -04:00
|
|
|
/**
|
|
|
|
* Expect a component of the given type to show.
|
|
|
|
*/
|
|
|
|
toContainComponent(expectedComponentType: Type<any>, expectationFailOutput?: any): boolean;
|
|
|
|
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Invert the matchers.
|
|
|
|
*/
|
2018-07-05 08:24:53 -04:00
|
|
|
not: NgMatchers<T>;
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|
|
|
|
|
2016-07-29 15:20:52 -04:00
|
|
|
const _global = <any>(typeof window === 'undefined' ? global : window);
|
2015-10-08 18:33:17 -04:00
|
|
|
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Jasmine matching function with Angular matchers mixed in.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toHaveText'}
|
|
|
|
*/
|
2018-07-05 08:24:53 -04:00
|
|
|
export const expect: <T = any>(actual: T) => NgMatchers<T> = _global.expect;
|
2015-10-08 18:33:17 -04:00
|
|
|
|
|
|
|
|
|
|
|
// 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 :-(
|
2016-07-29 15:20:52 -04:00
|
|
|
(Map as any).prototype['jasmineToString'] = function() {
|
|
|
|
const m = this;
|
2015-10-08 18:33:17 -04:00
|
|
|
if (!m) {
|
|
|
|
return '' + m;
|
|
|
|
}
|
2016-07-29 15:20:52 -04:00
|
|
|
const res: any[] = [];
|
2017-11-03 14:11:47 -04:00
|
|
|
m.forEach((v: any, k: any) => { res.push(`${String(k)}:${String(v)}`); });
|
2015-10-08 18:33:17 -04:00
|
|
|
return `{ ${res.join(',')} }`;
|
|
|
|
};
|
|
|
|
|
|
|
|
_global.beforeEach(function() {
|
2018-03-25 15:11:49 -04:00
|
|
|
// Custom handler for Map as we use Jasmine 2.4, and support for maps is not
|
|
|
|
// added until Jasmine 2.6.
|
|
|
|
jasmine.addCustomEqualityTester(function compareMap(actual: any, expected: any): boolean {
|
|
|
|
if (actual instanceof Map) {
|
|
|
|
let pass = actual.size === expected.size;
|
|
|
|
if (pass) {
|
|
|
|
actual.forEach((v: any, k: any) => {
|
|
|
|
pass = pass && jasmine.matchersUtil.equals(v, expected.get(k));
|
|
|
|
});
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|
2018-03-25 15:11:49 -04:00
|
|
|
return pass;
|
|
|
|
} else {
|
|
|
|
// TODO(misko): we should change the return, but jasmine.d.ts is not null safe
|
|
|
|
return undefined !;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
jasmine.addMatchers({
|
2015-10-08 18:33:17 -04:00
|
|
|
toBePromise: function() {
|
|
|
|
return {
|
2016-07-29 15:20:52 -04:00
|
|
|
compare: function(actual: any) {
|
|
|
|
const pass = typeof actual === 'object' && typeof actual.then === 'function';
|
2015-10-08 18:33:17 -04:00
|
|
|
return {pass: pass, get message() { return 'Expected ' + actual + ' to be a promise'; }};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
toBeAnInstanceOf: function() {
|
|
|
|
return {
|
2016-07-29 15:20:52 -04:00
|
|
|
compare: function(actual: any, expectedClass: any) {
|
|
|
|
const pass = typeof actual === 'object' && actual instanceof expectedClass;
|
2015-10-08 18:33:17 -04:00
|
|
|
return {
|
|
|
|
pass: pass,
|
|
|
|
get message() {
|
|
|
|
return 'Expected ' + actual + ' to be an instance of ' + expectedClass;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
toHaveText: function() {
|
|
|
|
return {
|
2016-07-29 15:20:52 -04:00
|
|
|
compare: function(actual: any, expectedText: string) {
|
|
|
|
const actualText = elementText(actual);
|
2015-10-08 18:33:17 -04:00
|
|
|
return {
|
|
|
|
pass: actualText == expectedText,
|
|
|
|
get message() { return 'Expected ' + actualText + ' to be equal to ' + expectedText; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
toHaveCssClass: function() {
|
|
|
|
return {compare: buildError(false), negativeCompare: buildError(true)};
|
|
|
|
|
2016-07-29 15:20:52 -04:00
|
|
|
function buildError(isNot: boolean) {
|
|
|
|
return function(actual: any, className: string) {
|
2015-10-08 18:33:17 -04:00
|
|
|
return {
|
2016-04-28 20:50:03 -04:00
|
|
|
pass: getDOM().hasClass(actual, className) == !isNot,
|
2015-10-08 18:33:17 -04:00
|
|
|
get message() {
|
|
|
|
return `Expected ${actual.outerHTML} ${isNot ? 'not ' : ''}to contain the CSS class "${className}"`;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-11-17 15:37:39 -05:00
|
|
|
toHaveCssStyle: function() {
|
|
|
|
return {
|
2016-07-29 15:20:52 -04:00
|
|
|
compare: function(actual: any, styles: {[k: string]: string}|string) {
|
|
|
|
let allPassed: boolean;
|
2016-10-19 16:42:39 -04:00
|
|
|
if (typeof styles === 'string') {
|
2016-04-28 20:50:03 -04:00
|
|
|
allPassed = getDOM().hasStyle(actual, styles);
|
2015-11-17 15:37:39 -05:00
|
|
|
} else {
|
2016-10-03 19:46:05 -04:00
|
|
|
allPassed = Object.keys(styles).length !== 0;
|
|
|
|
Object.keys(styles).forEach(prop => {
|
|
|
|
allPassed = allPassed && getDOM().hasStyle(actual, prop, styles[prop]);
|
2016-07-29 15:20:52 -04:00
|
|
|
});
|
2015-11-17 15:37:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
pass: allPassed,
|
|
|
|
get message() {
|
2016-10-19 16:42:39 -04:00
|
|
|
const expectedValueStr = typeof styles === 'string' ? styles : JSON.stringify(styles);
|
2015-11-17 15:37:39 -05:00
|
|
|
return `Expected ${actual.outerHTML} ${!allPassed ? ' ' : 'not '}to contain the
|
2016-10-19 16:42:39 -04:00
|
|
|
CSS ${typeof styles === 'string' ? 'property' : 'styles'} "${expectedValueStr}"`;
|
2015-11-17 15:37:39 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2015-10-08 18:33:17 -04:00
|
|
|
toContainError: function() {
|
|
|
|
return {
|
2016-07-29 15:20:52 -04:00
|
|
|
compare: function(actual: any, expectedText: any) {
|
|
|
|
const errorMessage = actual.toString();
|
2015-10-08 18:33:17 -04:00
|
|
|
return {
|
|
|
|
pass: errorMessage.indexOf(expectedText) > -1,
|
|
|
|
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
toImplement: function() {
|
|
|
|
return {
|
2016-07-29 15:20:52 -04:00
|
|
|
compare: function(actualObject: any, expectedInterface: any) {
|
|
|
|
const intProps = Object.keys(expectedInterface.prototype);
|
2015-10-08 18:33:17 -04:00
|
|
|
|
2016-07-29 15:20:52 -04:00
|
|
|
const missedMethods: any[] = [];
|
2015-10-08 18:33:17 -04:00
|
|
|
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: ' +
|
2016-06-08 19:38:52 -04:00
|
|
|
missedMethods.join(', ');
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
2018-08-05 09:35:51 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
toContainComponent: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actualFixture: any, expectedComponentType: Type<any>) {
|
|
|
|
const failOutput = arguments[2];
|
|
|
|
const msgFn = (msg: string): string => [msg, failOutput].filter(Boolean).join(', ');
|
|
|
|
|
|
|
|
// verify correct actual type
|
|
|
|
if (!(actualFixture instanceof ComponentFixture)) {
|
|
|
|
return {
|
|
|
|
pass: false,
|
|
|
|
message: msgFn(
|
|
|
|
`Expected actual to be of type \'ComponentFixture\' [actual=${actualFixture.constructor.name}]`)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const found = !!actualFixture.debugElement.query(By.directive(expectedComponentType));
|
|
|
|
return found ?
|
|
|
|
{pass: true} :
|
|
|
|
{pass: false, message: msgFn(`Expected ${expectedComponentType.name} to show`)};
|
|
|
|
}
|
|
|
|
};
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2016-07-29 15:20:52 -04:00
|
|
|
function elementText(n: any): string {
|
2016-11-12 08:08:58 -05:00
|
|
|
const hasNodes = (n: any) => {
|
2016-07-29 15:20:52 -04:00
|
|
|
const children = getDOM().childNodes(n);
|
2015-10-08 18:33:17 -04:00
|
|
|
return children && children.length > 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (n instanceof Array) {
|
2016-06-08 19:38:52 -04:00
|
|
|
return n.map(elementText).join('');
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|
|
|
|
|
2016-04-28 20:50:03 -04:00
|
|
|
if (getDOM().isCommentNode(n)) {
|
2015-10-08 18:33:17 -04:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
2016-04-28 20:50:03 -04:00
|
|
|
if (getDOM().isElementNode(n) && getDOM().tagName(n) == 'CONTENT') {
|
|
|
|
return elementText(Array.prototype.slice.apply(getDOM().getDistributedNodes(n)));
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|
|
|
|
|
2016-04-28 20:50:03 -04:00
|
|
|
if (getDOM().hasShadowRoot(n)) {
|
|
|
|
return elementText(getDOM().childNodesAsList(getDOM().getShadowRoot(n)));
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (hasNodes(n)) {
|
2016-04-28 20:50:03 -04:00
|
|
|
return elementText(getDOM().childNodesAsList(n));
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|
|
|
|
|
2017-03-24 12:59:41 -04:00
|
|
|
return getDOM().getText(n) !;
|
2015-10-08 18:33:17 -04:00
|
|
|
}
|