refactor: move angular source to /packages rather than modules/@angular
This commit is contained in:
42
packages/platform-browser/testing/src/browser.ts
Normal file
42
packages/platform-browser/testing/src/browser.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @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 {APP_ID, NgModule, NgZone, PLATFORM_INITIALIZER, PlatformRef, Provider, createPlatformFactory, platformCore} from '@angular/core';
|
||||
import {BrowserModule, ɵBrowserDomAdapter as BrowserDomAdapter, ɵELEMENT_PROBE_PROVIDERS as ELEMENT_PROBE_PROVIDERS} from '@angular/platform-browser';
|
||||
import {BrowserDetection, createNgZone} from './browser_util';
|
||||
|
||||
function initBrowserTests() {
|
||||
BrowserDomAdapter.makeCurrent();
|
||||
BrowserDetection.setup();
|
||||
}
|
||||
|
||||
const _TEST_BROWSER_PLATFORM_PROVIDERS: Provider[] =
|
||||
[{provide: PLATFORM_INITIALIZER, useValue: initBrowserTests, multi: true}];
|
||||
|
||||
/**
|
||||
* Platform for testing
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export const platformBrowserTesting =
|
||||
createPlatformFactory(platformCore, 'browserTesting', _TEST_BROWSER_PLATFORM_PROVIDERS);
|
||||
|
||||
/**
|
||||
* NgModule for testing.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@NgModule({
|
||||
exports: [BrowserModule],
|
||||
providers: [
|
||||
{provide: APP_ID, useValue: 'a'},
|
||||
ELEMENT_PROBE_PROVIDERS,
|
||||
{provide: NgZone, useFactory: createNgZone},
|
||||
]
|
||||
})
|
||||
export class BrowserTestingModule {
|
||||
}
|
137
packages/platform-browser/testing/src/browser_util.ts
Normal file
137
packages/platform-browser/testing/src/browser_util.ts
Normal file
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* @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 {NgZone, ɵglobal as global} from '@angular/core';
|
||||
import {ɵgetDOM as getDOM} from '@angular/platform-browser';
|
||||
|
||||
export let browserDetection: BrowserDetection;
|
||||
|
||||
export class BrowserDetection {
|
||||
private _overrideUa: string;
|
||||
private get _ua(): string {
|
||||
if (typeof this._overrideUa === 'string') {
|
||||
return this._overrideUa;
|
||||
}
|
||||
|
||||
return getDOM() ? getDOM().getUserAgent() : '';
|
||||
}
|
||||
|
||||
static setup() { browserDetection = new BrowserDetection(null); }
|
||||
|
||||
constructor(ua: string) { this._overrideUa = ua; }
|
||||
|
||||
get isFirefox(): boolean { return this._ua.indexOf('Firefox') > -1; }
|
||||
|
||||
get isAndroid(): boolean {
|
||||
return this._ua.indexOf('Mozilla/5.0') > -1 && this._ua.indexOf('Android') > -1 &&
|
||||
this._ua.indexOf('AppleWebKit') > -1 && this._ua.indexOf('Chrome') == -1 &&
|
||||
this._ua.indexOf('IEMobile') == -1;
|
||||
}
|
||||
|
||||
get isEdge(): boolean { return this._ua.indexOf('Edge') > -1; }
|
||||
|
||||
get isIE(): boolean { return this._ua.indexOf('Trident') > -1; }
|
||||
|
||||
get isWebkit(): boolean {
|
||||
return this._ua.indexOf('AppleWebKit') > -1 && this._ua.indexOf('Edge') == -1 &&
|
||||
this._ua.indexOf('IEMobile') == -1;
|
||||
}
|
||||
|
||||
get isIOS7(): boolean {
|
||||
return (this._ua.indexOf('iPhone OS 7') > -1 || this._ua.indexOf('iPad OS 7') > -1) &&
|
||||
this._ua.indexOf('IEMobile') == -1;
|
||||
}
|
||||
|
||||
get isSlow(): boolean { return this.isAndroid || this.isIE || this.isIOS7; }
|
||||
|
||||
// The Intl API is only natively supported in Chrome, Firefox, IE11 and Edge.
|
||||
// This detector is needed in tests to make the difference between:
|
||||
// 1) IE11/Edge: they have a native Intl API, but with some discrepancies
|
||||
// 2) IE9/IE10: they use the polyfill, and so no discrepancies
|
||||
get supportsNativeIntlApi(): boolean {
|
||||
return !!(<any>global).Intl && (<any>global).Intl !== (<any>global).IntlPolyfill;
|
||||
}
|
||||
|
||||
get isChromeDesktop(): boolean {
|
||||
return this._ua.indexOf('Chrome') > -1 && this._ua.indexOf('Mobile Safari') == -1 &&
|
||||
this._ua.indexOf('Edge') == -1;
|
||||
}
|
||||
|
||||
// "Old Chrome" means Chrome 3X, where there are some discrepancies in the Intl API.
|
||||
// Android 4.4 and 5.X have such browsers by default (respectively 30 and 39).
|
||||
get isOldChrome(): boolean {
|
||||
return this._ua.indexOf('Chrome') > -1 && this._ua.indexOf('Chrome/3') > -1 &&
|
||||
this._ua.indexOf('Edge') == -1;
|
||||
}
|
||||
}
|
||||
|
||||
BrowserDetection.setup();
|
||||
|
||||
export function dispatchEvent(element: any, eventType: any): void {
|
||||
getDOM().dispatchEvent(element, getDOM().createEvent(eventType));
|
||||
}
|
||||
|
||||
export function el(html: string): HTMLElement {
|
||||
return <HTMLElement>getDOM().firstChild(getDOM().content(getDOM().createTemplate(html)));
|
||||
}
|
||||
|
||||
export function normalizeCSS(css: string): string {
|
||||
return css.replace(/\s+/g, ' ')
|
||||
.replace(/:\s/g, ':')
|
||||
.replace(/'/g, '"')
|
||||
.replace(/ }/g, '}')
|
||||
.replace(/url\((\"|\s)(.+)(\"|\s)\)(\s*)/g, (...match: string[]) => `url("${match[2]}")`)
|
||||
.replace(/\[(.+)=([^"\]]+)\]/g, (...match: string[]) => `[${match[1]}="${match[2]}"]`);
|
||||
}
|
||||
|
||||
const _singleTagWhitelist = ['br', 'hr', 'input'];
|
||||
export function stringifyElement(el: any /** TODO #9100 */): string {
|
||||
let result = '';
|
||||
if (getDOM().isElementNode(el)) {
|
||||
const tagName = getDOM().tagName(el).toLowerCase();
|
||||
|
||||
// Opening tag
|
||||
result += `<${tagName}`;
|
||||
|
||||
// Attributes in an ordered way
|
||||
const attributeMap = getDOM().attributeMap(el);
|
||||
const keys: string[] = Array.from(attributeMap.keys()).sort();
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const attValue = attributeMap.get(key);
|
||||
if (typeof attValue !== 'string') {
|
||||
result += ` ${key}`;
|
||||
} else {
|
||||
result += ` ${key}="${attValue}"`;
|
||||
}
|
||||
}
|
||||
result += '>';
|
||||
|
||||
// Children
|
||||
const childrenRoot = getDOM().templateAwareRoot(el);
|
||||
const children = childrenRoot ? getDOM().childNodes(childrenRoot) : [];
|
||||
for (let j = 0; j < children.length; j++) {
|
||||
result += stringifyElement(children[j]);
|
||||
}
|
||||
|
||||
// Closing tag
|
||||
if (_singleTagWhitelist.indexOf(tagName) == -1) {
|
||||
result += `</${tagName}>`;
|
||||
}
|
||||
} else if (getDOM().isCommentNode(el)) {
|
||||
result += `<!--${getDOM().nodeValue(el)}-->`;
|
||||
} else {
|
||||
result += getDOM().getText(el);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createNgZone(): NgZone {
|
||||
return new NgZone({enableLongStackTrace: true});
|
||||
}
|
14
packages/platform-browser/testing/src/index.ts
Normal file
14
packages/platform-browser/testing/src/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* Entry point for all public APIs of the platform-browser/testing package.
|
||||
*/
|
||||
export * from './browser';
|
274
packages/platform-browser/testing/src/matchers.ts
Normal file
274
packages/platform-browser/testing/src/matchers.ts
Normal file
@ -0,0 +1,274 @@
|
||||
/**
|
||||
* @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 {ɵglobal as global} from '@angular/core';
|
||||
import {ɵgetDOM as getDOM} from '@angular/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 (typeof styles === 'string') {
|
||||
allPassed = getDOM().hasStyle(actual, styles);
|
||||
} else {
|
||||
allPassed = Object.keys(styles).length !== 0;
|
||||
Object.keys(styles).forEach(prop => {
|
||||
allPassed = allPassed && getDOM().hasStyle(actual, prop, styles[prop]);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
pass: allPassed,
|
||||
get message() {
|
||||
const expectedValueStr = typeof styles === 'string' ? styles : JSON.stringify(styles);
|
||||
return `Expected ${actual.outerHTML} ${!allPassed ? ' ' : 'not '}to contain the
|
||||
CSS ${typeof styles === 'string' ? '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 {
|
||||
const 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);
|
||||
}
|
21
packages/platform-browser/testing/tsconfig-build.json
Normal file
21
packages/platform-browser/testing/tsconfig-build.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "./tsconfig-build",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@angular/core": ["../../../dist/packages-dist/core"],
|
||||
"@angular/core/testing": ["../../../dist/packages-dist/core/testing"],
|
||||
"@angular/common": ["../../../dist/packages-dist/common"],
|
||||
"@angular/common/testing": ["../../../dist/packages-dist/common/testing"],
|
||||
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"testing/index.ts",
|
||||
"../../../node_modules/@types/hammerjs/index.d.ts",
|
||||
"../../../node_modules/@types/jasmine/index.d.ts",
|
||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"strictMetadataEmit": true
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user