feat(platform-server): provide a DOM implementation on the server

Fixes #14638

Uses Domino - https://github.com/fgnass/domino and removes dependency on
Parse5.

The DOCUMENT and nativeElement were never typed earlier and were
different on the browser(DOM nodes) and the server(Parse5 nodes). With
this change, platform-server also exposes a DOCUMENT and nativeElement
that is closer to the client. If you were relying on nativeElement on
the server, you would have to change your code to use the DOM API now
instead of Parse5 AST API.

Removes the need to add services for each and every Document
manipulation like Title/Meta etc.

This does *not* provide a global variable 'document' or 'window' on the
server. You still have to inject DOCUMENT to get the document backing
the current platform server instance.
This commit is contained in:
Vikram Subramanian
2017-08-30 17:46:16 -07:00
committed by Jason Aden
parent 30d53a8942
commit 2f2d5f35bd
21 changed files with 280 additions and 914 deletions
+3 -8
View File
@@ -2352,6 +2352,9 @@
"domhandler": {
"version": "2.3.0"
},
"domino": {
"version": "1.0.29"
},
"domutils": {
"version": "1.5.1"
},
@@ -6162,14 +6165,6 @@
"parse-json": {
"version": "2.2.0"
},
"parse5": {
"version": "3.0.1",
"dependencies": {
"@types/node": {
"version": "6.0.63"
}
}
},
"parsejson": {
"version": "0.0.1"
},
+5 -12
View File
@@ -3720,6 +3720,11 @@
"from": "domhandler@>=2.3.0 <3.0.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
},
"domino": {
"version": "1.0.29",
"from": "domino@latest",
"resolved": "https://registry.npmjs.org/domino/-/domino-1.0.29.tgz"
},
"domutils": {
"version": "1.5.1",
"from": "domutils@>=1.5.1 <2.0.0",
@@ -9850,18 +9855,6 @@
"from": "parse-json@>=2.2.0 <3.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz"
},
"parse5": {
"version": "3.0.1",
"from": "[email protected]",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.1.tgz",
"dependencies": {
"@types/node": {
"version": "6.0.63",
"from": "@types/node@>=6.0.46 <7.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.63.tgz"
}
}
},
"parsejson": {
"version": "0.0.1",
"from": "[email protected]",
+1 -1
View File
@@ -56,6 +56,7 @@
"cors": "^2.7.1",
"dgeni": "^0.4.2",
"dgeni-packages": "^0.16.5",
"domino": "^1.0.29",
"entities": "^1.1.1",
"firebase-tools": "^3.9.2",
"firefox-profile": "^0.3.4",
@@ -81,7 +82,6 @@
"minimist": "^1.2.0",
"nan": "^2.4.0",
"node-uuid": "1.4.x",
"parse5": "^3.0.1",
"protractor": "^4.0.14",
"react": "^0.14.0",
"rewire": "^2.3.3",
@@ -84,8 +84,8 @@ describe('template codegen output', () => {
it('should support i18n for content tags', () => {
const containerElement = createComponent(BasicComp).nativeElement;
const pElement = containerElement.children.find((c: any) => c.name == 'p');
const pText = pElement.children.map((c: any) => c.data).join('').trim();
const pElement = containerElement.querySelector('p');
const pText = pElement.textContent;
expect(pText).toBe('tervetuloa');
});
@@ -49,7 +49,7 @@ describe('NgModule', () => {
// https://github.com/angular/angular/issues/15221
const fixture = createComponent(ComponentUsingFlatModule);
const bundleComp = fixture.nativeElement.children;
expect(bundleComp[0].children[0].children[0].data).toEqual('flat module component');
expect(bundleComp[0].children[0].textContent).toEqual('flat module component');
});
});
@@ -58,8 +58,8 @@ describe('NgModule', () => {
it('should support third party entryComponents components', () => {
const fixture = createComponent(ComponentUsingThirdParty);
const thirdPComps = fixture.nativeElement.children;
expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component');
expect(thirdPComps[1].children[0].children[0].data).toEqual(`other-3rdP-component
expect(thirdPComps[0].children[0].textContent).toEqual('3rdP-component');
expect(thirdPComps[1].children[0].textContent).toEqual(`other-3rdP-component
multi-lines`);
});
@@ -129,12 +129,12 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
}
dispatchEvent(el: Node, evt: any) { el.dispatchEvent(evt); }
createMouseEvent(eventType: string): MouseEvent {
const evt: MouseEvent = document.createEvent('MouseEvent');
const evt: MouseEvent = this.getDefaultDocument().createEvent('MouseEvent');
evt.initEvent(eventType, true, true);
return evt;
}
createEvent(eventType: any): Event {
const evt: Event = document.createEvent('Event');
const evt: Event = this.getDefaultDocument().createEvent('Event');
evt.initEvent(eventType, true, true);
return evt;
}
@@ -147,7 +147,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
}
getInnerHTML(el: HTMLElement): string { return el.innerHTML; }
getTemplateContent(el: Node): Node|null {
return 'content' in el && el instanceof HTMLTemplateElement ? el.content : null;
return 'content' in el && this.isTemplateElement(el) ? (<any>el).content : null;
}
getOuterHTML(el: HTMLElement): string { return el.outerHTML; }
nodeName(node: Node): string { return node.nodeName; }
@@ -198,25 +198,34 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
setValue(el: any, value: string) { el.value = value; }
getChecked(el: any): boolean { return el.checked; }
setChecked(el: any, value: boolean) { el.checked = value; }
createComment(text: string): Comment { return document.createComment(text); }
createComment(text: string): Comment { return this.getDefaultDocument().createComment(text); }
createTemplate(html: any): HTMLElement {
const t = document.createElement('template');
const t = this.getDefaultDocument().createElement('template');
t.innerHTML = html;
return t;
}
createElement(tagName: string, doc = document): HTMLElement { return doc.createElement(tagName); }
createElementNS(ns: string, tagName: string, doc = document): Element {
createElement(tagName: string, doc?: Document): HTMLElement {
doc = doc || this.getDefaultDocument();
return doc.createElement(tagName);
}
createElementNS(ns: string, tagName: string, doc?: Document): Element {
doc = doc || this.getDefaultDocument();
return doc.createElementNS(ns, tagName);
}
createTextNode(text: string, doc = document): Text { return doc.createTextNode(text); }
createScriptTag(attrName: string, attrValue: string, doc = document): HTMLScriptElement {
createTextNode(text: string, doc?: Document): Text {
doc = doc || this.getDefaultDocument();
return doc.createTextNode(text);
}
createScriptTag(attrName: string, attrValue: string, doc?: Document): HTMLScriptElement {
doc = doc || this.getDefaultDocument();
const el = <HTMLScriptElement>doc.createElement('SCRIPT');
el.setAttribute(attrName, attrValue);
return el;
}
createStyleElement(css: string, doc = document): HTMLStyleElement {
createStyleElement(css: string, doc?: Document): HTMLStyleElement {
doc = doc || this.getDefaultDocument();
const style = <HTMLStyleElement>doc.createElement('style');
this.appendChild(style, this.createTextNode(css));
this.appendChild(style, this.createTextNode(css, doc));
return style;
}
createShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).createShadowRoot(); }
@@ -253,7 +262,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
const res = new Map<string, string>();
const elAttrs = element.attributes;
for (let i = 0; i < elAttrs.length; i++) {
const attrib = elAttrs[i];
const attrib = elAttrs.item(i);
res.set(attrib.name, attrib.value);
}
return res;
@@ -282,6 +291,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
createHtmlDocument(): HTMLDocument {
return document.implementation.createHTMLDocument('fakeTitle');
}
getDefaultDocument(): Document { return document; }
getBoundingClientRect(el: Element): any {
try {
return el.getBoundingClientRect();
@@ -289,10 +299,10 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0};
}
}
getTitle(doc: Document): string { return document.title; }
setTitle(doc: Document, newTitle: string) { document.title = newTitle || ''; }
getTitle(doc: Document): string { return doc.title; }
setTitle(doc: Document, newTitle: string) { doc.title = newTitle || ''; }
elementMatches(n: any, selector: string): boolean {
if (n instanceof HTMLElement) {
if (this.isElementNode(n)) {
return n.matches && n.matches(selector) ||
n.msMatchesSelector && n.msMatchesSelector(selector) ||
n.webkitMatchesSelector && n.webkitMatchesSelector(selector);
@@ -301,7 +311,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
return false;
}
isTemplateElement(el: Node): boolean {
return el instanceof HTMLElement && el.nodeName == 'TEMPLATE';
return this.isElementNode(el) && el.nodeName === 'TEMPLATE';
}
isTextNode(node: Node): boolean { return node.nodeType === Node.TEXT_NODE; }
isCommentNode(node: Node): boolean { return node.nodeType === Node.COMMENT_NODE; }
@@ -312,7 +322,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
isShadowRoot(node: any): boolean { return node instanceof DocumentFragment; }
importIntoDoc(node: Node): any { return document.importNode(this.templateAwareRoot(node), true); }
adoptNode(node: Node): any { return document.adoptNode(node); }
getHref(el: Element): string { return (<any>el).href; }
getHref(el: Element): string { return el.getAttribute('href') !; }
getEventKey(event: any): string {
let key = event.key;
@@ -342,10 +352,10 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
return window;
}
if (target === 'document') {
return document;
return doc;
}
if (target === 'body') {
return document.body;
return doc.body;
}
return null;
}
@@ -56,7 +56,7 @@ export class Meta {
getTag(attrSelector: string): HTMLMetaElement|null {
if (!attrSelector) return null;
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`);
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`) || null;
}
getTags(attrSelector: string): HTMLMetaElement[] {
@@ -125,6 +125,7 @@ export abstract class DomAdapter {
abstract removeAttributeNS(element: any, ns: string, attribute: string): any;
abstract templateAwareRoot(el: any): any;
abstract createHtmlDocument(): HTMLDocument;
abstract getDefaultDocument(): Document;
abstract getBoundingClientRect(el: any): any;
abstract getTitle(doc: Document): string;
abstract setTitle(doc: Document, newTitle: string): any;
@@ -14,11 +14,13 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
export function main() {
describe('Meta service', () => {
const doc = getDOM().createHtmlDocument();
const metaService = new Meta(doc);
let doc: Document;
let metaService: Meta;
let defaultMeta: HTMLMetaElement;
beforeEach(() => {
doc = getDOM().createHtmlDocument();
metaService = new Meta(doc);
defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement;
getDOM().setAttribute(defaultMeta, 'property', 'fb:app_id');
getDOM().setAttribute(defaultMeta, 'content', '123456789');
@@ -14,9 +14,15 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
export function main() {
describe('title service', () => {
const doc = getDOM().createHtmlDocument();
const initialTitle = getDOM().getTitle(doc);
const titleService = new Title(doc);
let doc: Document;
let initialTitle: string;
let titleService: Title;
beforeEach(() => {
doc = getDOM().createHtmlDocument();
initialTitle = getDOM().getTitle(doc);
titleService = new Title(doc);
});
afterEach(() => { getDOM().setTitle(doc, initialTitle); });
+1 -1
View File
@@ -18,7 +18,7 @@
},
"dependencies": {
"tslib": "^1.7.1",
"parse5": "^3.0.1",
"domino": "^1.0.29",
"xhr2": "^0.1.4"
},
"repository": {
@@ -0,0 +1,201 @@
/**
* @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
*/
const domino = require('domino');
import {ɵBrowserDomAdapter as BrowserDomAdapter, ɵsetRootDomAdapter as setRootDomAdapter} from '@angular/platform-browser';
function _notImplemented(methodName: string) {
return new Error('This method is not implemented in DominoAdapter: ' + methodName);
}
/**
* Parses a document string to a Document object.
*/
export function parseDocument(html: string, url = '/') {
let window = domino.createWindow(html, url);
let doc = window.document;
return doc;
}
/**
* Serializes a document to string.
*/
export function serializeDocument(doc: Document): string {
return (doc as any).serialize();
}
/**
* DOM Adapter for the server platform based on https://github.com/fgnass/domino.
*/
export class DominoAdapter extends BrowserDomAdapter {
static makeCurrent() { setRootDomAdapter(new DominoAdapter()); }
private static defaultDoc: Document;
logError(error: string) { console.error(error); }
// tslint:disable-next-line:no-console
log(error: string) { console.log(error); }
logGroup(error: string) { console.error(error); }
logGroupEnd() {}
supportsDOMEvents(): boolean { return false; }
supportsNativeShadowDOM(): boolean { return false; }
contains(nodeA: any, nodeB: any): boolean {
let inner = nodeB;
while (inner) {
if (inner === nodeA) return true;
inner = inner.parent;
}
return false;
}
createHtmlDocument(): HTMLDocument {
return parseDocument('<html><head><title>fakeTitle</title></head><body></body></html>');
}
getDefaultDocument(): Document {
if (!DominoAdapter.defaultDoc) {
DominoAdapter.defaultDoc = domino.createDocument();
}
return DominoAdapter.defaultDoc;
}
createShadowRoot(el: any, doc: Document = document): DocumentFragment {
el.shadowRoot = doc.createDocumentFragment();
el.shadowRoot.parent = el;
return el.shadowRoot;
}
getShadowRoot(el: any): DocumentFragment { return el.shadowRoot; }
isTextNode(node: any): boolean { return node.nodeType === DominoAdapter.defaultDoc.TEXT_NODE; }
isCommentNode(node: any): boolean {
return node.nodeType === DominoAdapter.defaultDoc.COMMENT_NODE;
}
isElementNode(node: any): boolean {
return node ? node.nodeType === DominoAdapter.defaultDoc.ELEMENT_NODE : false;
}
hasShadowRoot(node: any): boolean { return node.shadowRoot != null; }
isShadowRoot(node: any): boolean { return this.getShadowRoot(node) == node; }
getProperty(el: Element, name: string): any {
// Domino tries tp resolve href-s which we do not want. Just return the
// atribute value.
if (name === 'href') {
return this.getAttribute(el, 'href');
}
return (<any>el)[name];
}
setProperty(el: Element, name: string, value: any) {
// Eventhough the server renderer reflects any properties to attributes
// map 'href' to atribute just to handle when setProperty is directly called.
if (name === 'href') {
this.setAttribute(el, 'href', value);
}
(<any>el)[name] = value;
}
getGlobalEventTarget(doc: Document, target: string): EventTarget|null {
if (target === 'window') {
return doc.defaultView;
}
if (target === 'document') {
return doc;
}
if (target === 'body') {
return doc.body;
}
return null;
}
getBaseHref(doc: Document): string {
const base = this.querySelector(doc.documentElement, 'base');
let href = '';
if (base) {
href = this.getHref(base);
}
// TODO(alxhub): Need relative path logic from BrowserDomAdapter here?
return href;
}
/** @internal */
_readStyleAttribute(element: any) {
const styleMap = {};
const styleAttribute = element.getAttribute('style');
if (styleAttribute) {
const styleList = styleAttribute.split(/;+/g);
for (let i = 0; i < styleList.length; i++) {
if (styleList[i].length > 0) {
const style = styleList[i] as string;
const colon = style.indexOf(':');
if (colon === -1) {
throw new Error(`Invalid CSS style: ${style}`);
}
(styleMap as any)[style.substr(0, colon).trim()] = style.substr(colon + 1).trim();
}
}
}
return styleMap;
}
/** @internal */
_writeStyleAttribute(element: any, styleMap: any) {
let styleAttrValue = '';
for (const key in styleMap) {
const newValue = styleMap[key];
if (newValue) {
styleAttrValue += key + ':' + styleMap[key] + ';';
}
}
element.setAttribute('style', styleAttrValue);
}
setStyle(element: any, styleName: string, styleValue?: string|null) {
const styleMap = this._readStyleAttribute(element);
(styleMap as any)[styleName] = styleValue;
this._writeStyleAttribute(element, styleMap);
}
removeStyle(element: any, styleName: string) { this.setStyle(element, styleName, null); }
getStyle(element: any, styleName: string): string {
const styleMap = this._readStyleAttribute(element);
return styleMap.hasOwnProperty(styleName) ? (styleMap as any)[styleName] : '';
}
hasStyle(element: any, styleName: string, styleValue?: string): boolean {
const value = this.getStyle(element, styleName) || '';
return styleValue ? value == styleValue : value.length > 0;
}
dispatchEvent(el: Node, evt: any) {
el.dispatchEvent(evt);
// Dispatch the event to the window also.
const doc = el.ownerDocument || el;
const win = (doc as any).defaultView;
if (win) {
win.dispatchEvent(evt);
}
}
getHistory(): History { throw _notImplemented('getHistory'); }
getLocation(): Location { throw _notImplemented('getLocation'); }
getUserAgent(): string { return 'Fake user agent'; }
supportsWebAnimation(): boolean { return false; }
performanceNow(): number { return Date.now(); }
getAnimationPrefix(): string { return ''; }
getTransitionEnd(): string { return 'transitionend'; }
supportsAnimation(): boolean { return true; }
getDistributedNodes(el: any): Node[] { throw _notImplemented('getDistributedNodes'); }
supportsCookies(): boolean { return false; }
getCookie(name: string): string { throw _notImplemented('getCookie'); }
setCookie(name: string, value: string) { throw _notImplemented('setCookie'); }
}
File diff suppressed because it is too large Load Diff
@@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
const parse5 = require('parse5');
import {Injectable, Inject} from '@angular/core';
import {Inject, Injectable} from '@angular/core';
import {DOCUMENT, ɵgetDOM as getDOM} from '@angular/platform-browser';
import {serializeDocument} from './domino_adapter';
/**
* Representation of the current platform state.
*
@@ -23,7 +23,7 @@ export class PlatformState {
/**
* Renders the current state of the platform to string.
*/
renderToString(): string { return getDOM().getInnerHTML(this._doc); }
renderToString(): string { return serializeDocument(this._doc); }
/**
* Returns the current DOM state.
+3 -3
View File
@@ -15,9 +15,9 @@ import {BrowserModule, DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANS
import {ɵplatformCoreDynamic as platformCoreDynamic} from '@angular/platform-browser-dynamic';
import {NoopAnimationsModule, ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
import {DominoAdapter, parseDocument} from './domino_adapter';
import {SERVER_HTTP_PROVIDERS} from './http';
import {ServerPlatformLocation} from './location';
import {Parse5DomAdapter, parseDocument} from './parse5_adapter';
import {PlatformState} from './platform_state';
import {ServerRendererFactory2} from './server_renderer';
import {ServerStylesHost} from './styles_host';
@@ -41,7 +41,7 @@ export const INTERNAL_SERVER_PLATFORM_PROVIDERS: StaticProvider[] = [
];
function initParse5Adapter(injector: Injector) {
return () => { Parse5DomAdapter.makeCurrent(); };
return () => { DominoAdapter.makeCurrent(); };
}
export function instantiateServerRendererFactory(
@@ -80,7 +80,7 @@ export class ServerModule {
function _document(injector: Injector) {
let config: PlatformConfig|null = injector.get(INITIAL_CONFIG, null);
if (config && config.document) {
return parseDocument(config.document);
return parseDocument(config.document, config.url);
} else {
return getDOM().createHtmlDocument();
}
+2 -4
View File
@@ -7,9 +7,7 @@
*/
import {ApplicationRef, Inject, Injectable, Optional} from '@angular/core';
import {DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser';
import {Parse5DomAdapter} from './parse5_adapter';
import {DOCUMENT, ɵDomAdapter as DomAdapter, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser';
@Injectable()
export class ServerStylesHost extends SharedStylesHost {
@@ -23,7 +21,7 @@ export class ServerStylesHost extends SharedStylesHost {
}
private _addStyle(style: string): void {
let adapter: Parse5DomAdapter = getDOM() as Parse5DomAdapter;
let adapter = getDOM();
const el = adapter.createElement('style');
adapter.setText(el, style);
if (!!this.transitionId) {
-2
View File
@@ -16,8 +16,6 @@ import {PlatformState} from './platform_state';
import {platformDynamicServer, platformServer} from './server';
import {INITIAL_CONFIG} from './tokens';
const parse5 = require('parse5');
interface PlatformOptions {
document?: string;
url?: string;
@@ -231,9 +231,8 @@ export function main() {
expect(doc.head).toBe(getDOM().querySelector(doc, 'head'));
expect(doc.body).toBe(getDOM().querySelector(doc, 'body'));
expect((<any>doc)._window).toEqual({});
expect(getDOM().getText(doc)).toEqual('Works!');
expect(getDOM().getText(doc.documentElement)).toEqual('Works!');
platform.destroy();
});
@@ -249,13 +248,13 @@ export function main() {
platform.bootstrapModule(ExampleModule).then((moduleRef) => {
const doc = moduleRef.injector.get(DOCUMENT);
expect(getDOM().getText(doc)).toEqual('Works!');
expect(getDOM().getText(doc.documentElement)).toEqual('Works!');
platform.destroy();
});
platform2.bootstrapModule(ExampleModule2).then((moduleRef) => {
const doc = moduleRef.injector.get(DOCUMENT);
expect(getDOM().getText(doc)).toEqual('Works too!');
expect(getDOM().getText(doc.documentElement)).toEqual('Works too!');
platform2.destroy();
});
}));
@@ -310,7 +309,7 @@ export function main() {
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
const app = appRef.components[0].location.nativeElement;
const img = getDOM().getElementsByTagName(app, 'img')[0] as any;
expect(img.attribs['src']).toEqual('link');
expect(img.attributes['src'].value).toEqual('link');
});
}));
@@ -383,7 +382,7 @@ export function main() {
let doc: string;
let called: boolean;
let expectedOutput =
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 innerText="fine">fine</h1></app></body></html>';
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 innertext="fine"></h1></app></body></html>';
beforeEach(() => {
// PlatformConfig takes in a parsed document so that it can be cached across requests.
@@ -439,9 +438,8 @@ export function main() {
it('works with animation', async(() => {
renderModule(AnimationServerModule, {document: doc}).then(output => {
expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
'<div>Works!</div></app></body></html>');
expect(output).toContain('Works!');
expect(output).toContain('ng-trigger-myAnimation');
called = true;
});
}));
@@ -128,6 +128,7 @@ export class WorkerDomAdapter extends DomAdapter {
removeAttributeNS(element: any, ns: string, attribute: string) { throw 'not implemented'; }
templateAwareRoot(el: any) { throw 'not implemented'; }
createHtmlDocument(): HTMLDocument { throw 'not implemented'; }
getDefaultDocument(): Document { throw 'not implemented'; }
getBoundingClientRect(el: any) { throw 'not implemented'; }
getTitle(doc: Document): string { throw 'not implemented'; }
setTitle(doc: Document, newTitle: string) { throw 'not implemented'; }
+2 -2
View File
@@ -22,10 +22,10 @@ System.config({
'benchpress/*': 'dist/js/dev/es5/benchpress/*.js',
'@angular': 'dist/all/@angular',
'rxjs': 'node_modules/rxjs',
'parse5': 'dist/all/@angular/empty.js',
'domino': 'dist/all/@angular/empty.js',
'url': 'dist/all/@angular/empty.js',
'xhr2': 'dist/all/@angular/empty.js',
'@angular/platform-server/src/parse5_adapter': 'dist/all/empty.js',
'@angular/platform-server/src/domino_adapter': 'dist/all/empty.js',
'angular2/*': 'dist/all/angular2/*.js',
'angular2/src/alt_router/router_testing_providers':
'dist/all/angular2/src/alt_router/router_testing_providers.js'
+1 -1
View File
@@ -83,7 +83,7 @@ jrunner.onComplete(function(passed: boolean) { process.exit(passed ? 0 : 1); });
jrunner.projectBaseDir = path.resolve(__dirname, '../../');
jrunner.specDir = '';
require('./test-cjs-main.js');
distAllRequire('@angular/platform-server/src/parse5_adapter.js').Parse5DomAdapter.makeCurrent();
distAllRequire('@angular/platform-server/src/domino_adapter.js').DominoAdapter.makeCurrent();
specFiles.forEach((file: string) => {
const r = distAllRequire(file);
if (r.main) {