2015-11-19 18:09:34 -05:00
|
|
|
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
2015-11-17 15:37:39 -05:00
|
|
|
import {global, isString} from 'angular2/src/facade/lang';
|
|
|
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
2015-10-08 18:33:17 -04:00
|
|
|
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Jasmine matchers that check Angular specific conditions.
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
export interface NgMatchers extends jasmine.Matchers {
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Expect the value to be a `Promise`.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@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.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@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.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toHaveText'}
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
toHaveText(expected: any): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect the element to have the given CSS class.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toHaveCssClass'}
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
toHaveCssClass(expected: any): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect the element to have the given CSS styles.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toHaveCssStyle'}
|
|
|
|
*/
|
2015-11-17 15:37:39 -05:00
|
|
|
toHaveCssStyle(expected: any): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect a class to implement the interface of the given class.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@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.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@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
|
|
|
|
|
|
|
/**
|
|
|
|
* Expect a function to throw an error with the given error text when executed.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example testing/ts/matchers.ts region='toThrowErrorWith'}
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
toThrowErrorWith(expectedMessage: any): boolean;
|
2015-12-03 18:49:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Invert the matchers.
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
not: NgMatchers;
|
|
|
|
}
|
|
|
|
|
2016-02-01 13:28:57 -05:00
|
|
|
var _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'}
|
|
|
|
*/
|
2015-10-08 18:33:17 -04:00
|
|
|
export var 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.prototype['jasmineToString'] = function() {
|
|
|
|
var m = this;
|
|
|
|
if (!m) {
|
|
|
|
return '' + m;
|
|
|
|
}
|
|
|
|
var res = [];
|
|
|
|
m.forEach((v, k) => { 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, customEqualityTesters) {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expected) {
|
|
|
|
return {pass: util.equals(actual, expected, [compareMap])};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function compareMap(actual, expected) {
|
|
|
|
if (actual instanceof Map) {
|
|
|
|
var pass = actual.size === expected.size;
|
|
|
|
if (pass) {
|
|
|
|
actual.forEach((v, k) => { pass = pass && util.equals(v, expected.get(k)); });
|
|
|
|
}
|
|
|
|
return pass;
|
|
|
|
} else {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
toBePromise: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expectedClass) {
|
|
|
|
var 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, expectedClass) {
|
|
|
|
var 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, expectedText) {
|
|
|
|
var 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) {
|
|
|
|
return function(actual, className) {
|
|
|
|
return {
|
|
|
|
pass: DOM.hasClass(actual, className) == !isNot,
|
|
|
|
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 {
|
|
|
|
compare: function(actual, styles) {
|
|
|
|
var allPassed;
|
|
|
|
if (isString(styles)) {
|
|
|
|
allPassed = DOM.hasStyle(actual, styles);
|
|
|
|
} else {
|
|
|
|
allPassed = !StringMapWrapper.isEmpty(styles);
|
|
|
|
StringMapWrapper.forEach(styles, (style, prop) => {
|
|
|
|
allPassed = allPassed && DOM.hasStyle(actual, prop, style);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
pass: allPassed,
|
|
|
|
get message() {
|
|
|
|
var expectedValueStr = isString(styles) ? styles : JSON.stringify(styles);
|
|
|
|
return `Expected ${actual.outerHTML} ${!allPassed ? ' ' : 'not '}to contain the
|
|
|
|
CSS ${isString(styles) ? 'property' : 'styles'} "${expectedValueStr}"`;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2015-10-08 18:33:17 -04:00
|
|
|
toContainError: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expectedText) {
|
|
|
|
var errorMessage = actual.toString();
|
|
|
|
return {
|
|
|
|
pass: errorMessage.indexOf(expectedText) > -1,
|
|
|
|
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
toThrowErrorWith: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expectedText) {
|
|
|
|
try {
|
|
|
|
actual();
|
|
|
|
return {
|
|
|
|
pass: false,
|
|
|
|
get message() { return "Was expected to throw, but did not throw"; }
|
|
|
|
};
|
|
|
|
} catch (e) {
|
|
|
|
var errorMessage = e.toString();
|
|
|
|
return {
|
|
|
|
pass: errorMessage.indexOf(expectedText) > -1,
|
|
|
|
get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
toImplement: function() {
|
|
|
|
return {
|
|
|
|
compare: function(actualObject, expectedInterface) {
|
|
|
|
var objProps = Object.keys(actualObject.constructor.prototype);
|
|
|
|
var intProps = Object.keys(expectedInterface.prototype);
|
|
|
|
|
|
|
|
var missedMethods = [];
|
|
|
|
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) {
|
|
|
|
var hasNodes = (n) => {
|
|
|
|
var children = DOM.childNodes(n);
|
|
|
|
return children && children.length > 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (n instanceof Array) {
|
|
|
|
return n.map(elementText).join("");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DOM.isCommentNode(n)) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') {
|
|
|
|
return elementText(Array.prototype.slice.apply(DOM.getDistributedNodes(n)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DOM.hasShadowRoot(n)) {
|
|
|
|
return elementText(DOM.childNodesAsList(DOM.getShadowRoot(n)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasNodes(n)) {
|
|
|
|
return elementText(DOM.childNodesAsList(n));
|
|
|
|
}
|
|
|
|
|
|
|
|
return DOM.getText(n);
|
|
|
|
}
|