feat(core): add renderer factory in render3 (#20855)

PR Close #20855
This commit is contained in:
Marc Laval
2017-12-11 16:30:46 +01:00
committed by Igor Minar
parent 147aec43bd
commit d1de587ce0
15 changed files with 342 additions and 76 deletions

View File

@ -6,8 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {T, b, defineComponent, markDirty, t} from '../../src/render3/index';
import {ViewEncapsulation} from '../../src/core';
import {D, E, T, b, defineComponent, e, markDirty, t} from '../../src/render3/index';
import {createRendererType2} from '../../src/view';
import {getRendererFactory2} from './imported_renderer2';
import {containerEl, renderComponent, requestAnimationFrame} from './render_util';
describe('component', () => {
@ -60,3 +63,113 @@ describe('component', () => {
});
});
// TODO: add tests with Native once tests are run in real browser (domino doesn't support shadow
// root)
describe('encapsulation', () => {
class WrapperComponent {
static ngComponentDef = defineComponent({
type: WrapperComponent,
tag: 'wrapper',
template: function(ctx: WrapperComponent, cm: boolean) {
if (cm) {
E(0, EncapsulatedComponent.ngComponentDef);
{ D(1, EncapsulatedComponent.ngComponentDef.n(), EncapsulatedComponent.ngComponentDef); }
e();
}
EncapsulatedComponent.ngComponentDef.r(1, 0);
},
factory: () => new WrapperComponent,
});
}
class EncapsulatedComponent {
static ngComponentDef = defineComponent({
type: EncapsulatedComponent,
tag: 'encapsulated',
template: function(ctx: EncapsulatedComponent, cm: boolean) {
if (cm) {
T(0, 'foo');
E(1, LeafComponent.ngComponentDef);
{ D(2, LeafComponent.ngComponentDef.n(), LeafComponent.ngComponentDef); }
e();
}
LeafComponent.ngComponentDef.r(2, 1);
},
factory: () => new EncapsulatedComponent,
rendererType:
createRendererType2({encapsulation: ViewEncapsulation.Emulated, styles: [], data: {}}),
});
}
class LeafComponent {
static ngComponentDef = defineComponent({
type: LeafComponent,
tag: 'leaf',
template: function(ctx: LeafComponent, cm: boolean) {
if (cm) {
E(0, 'span');
{ T(1, 'bar'); }
e();
}
},
factory: () => new LeafComponent,
});
}
it('should encapsulate children, but not host nor grand children', () => {
renderComponent(WrapperComponent, getRendererFactory2(document));
expect(containerEl.outerHTML)
.toEqual(
'<div host=""><encapsulated _nghost-c0="">foo<leaf _ngcontent-c0=""><span>bar</span></leaf></encapsulated></div>');
});
it('should encapsulate host', () => {
renderComponent(EncapsulatedComponent, getRendererFactory2(document));
expect(containerEl.outerHTML)
.toEqual(
'<div host="" _nghost-c0="">foo<leaf _ngcontent-c0=""><span>bar</span></leaf></div>');
});
it('should encapsulate host and children with different attributes', () => {
class WrapperComponentWith {
static ngComponentDef = defineComponent({
type: WrapperComponent,
tag: 'wrapper',
template: function(ctx: WrapperComponentWith, cm: boolean) {
if (cm) {
E(0, LeafComponentwith.ngComponentDef);
{ D(1, LeafComponentwith.ngComponentDef.n(), LeafComponentwith.ngComponentDef); }
e();
}
LeafComponentwith.ngComponentDef.r(1, 0);
},
factory: () => new WrapperComponentWith,
rendererType:
createRendererType2({encapsulation: ViewEncapsulation.Emulated, styles: [], data: {}}),
});
}
class LeafComponentwith {
static ngComponentDef = defineComponent({
type: LeafComponentwith,
tag: 'leaf',
template: function(ctx: LeafComponentwith, cm: boolean) {
if (cm) {
E(0, 'span');
{ T(1, 'bar'); }
e();
}
},
factory: () => new LeafComponentwith,
rendererType:
createRendererType2({encapsulation: ViewEncapsulation.Emulated, styles: [], data: {}}),
});
}
renderComponent(WrapperComponentWith, getRendererFactory2(document));
expect(containerEl.outerHTML)
.toEqual(
'<div host="" _nghost-c1=""><leaf _ngcontent-c1="" _nghost-c2=""><span _ngcontent-c2="">bar</span></leaf></div>');
});
});

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter, NgZone, Renderer2} from '@angular/core';
import {EventEmitter, NgZone, RendererFactory2} from '@angular/core';
import {EventManager, ɵDomEventsPlugin, ɵDomRendererFactory2, ɵDomSharedStylesHost} from '@angular/platform-browser';
// Adapted renderer: it creates a Renderer2 instance and adapts it to Renderer3
// TODO: remove once this code is in angular/angular
export class NoopNgZone implements NgZone {
@ -47,11 +46,9 @@ export class SimpleDomEventsPlugin extends ɵDomEventsPlugin {
}
}
export function getRenderer2(document: any): Renderer2 {
export function getRendererFactory2(document: any): RendererFactory2 {
const fakeNgZone: NgZone = new NoopNgZone();
const eventManager =
new EventManager([new SimpleDomEventsPlugin(document, fakeNgZone)], fakeNgZone);
const rendererFactory2 =
new ɵDomRendererFactory2(eventManager, new ɵDomSharedStylesHost(document));
return rendererFactory2.createRenderer(null, null);
return new ɵDomRendererFactory2(eventManager, new ɵDomSharedStylesHost(document));
}

View File

@ -9,19 +9,17 @@
import {ComponentTemplate, ComponentType, PublicFeature, defineComponent, renderComponent as _renderComponent} from '../../src/render3/index';
import {NG_HOST_SYMBOL, createLNode, createViewState, renderTemplate} from '../../src/render3/instructions';
import {LElement, LNodeFlags} from '../../src/render3/l_node';
import {RElement, RText, Renderer3} from '../../src/render3/renderer';
import {getRenderer2} from './imported_renderer2';
import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/renderer';
import {getRendererFactory2} from './imported_renderer2';
export const document = ((global || window) as any).document;
export let containerEl: HTMLElement = null !;
let host: LElement;
let activeRenderer: Renderer3 =
(typeof process !== 'undefined' && process.argv[3] && process.argv[3] === '--r=renderer2') ?
getRenderer2(document) :
document;
let host: LElement|null;
const isRenderer2 = process.argv[3] && process.argv[3] === '--r=renderer2';
// tslint:disable-next-line:no-console
console.log(
`Running tests with ${activeRenderer === document ? 'document' : 'Renderer2'} renderer...`);
console.log(`Running tests with ${!isRenderer2 ? 'document' : 'Renderer2'} renderer...`);
const testRendererFactory: RendererFactory3 =
isRenderer2 ? getRendererFactory2(document) : domRendererFactory3;
export const requestAnimationFrame:
{(fn: () => void): void; flush(): void; queue: (() => void)[];} = function(fn: () => void) {
@ -37,20 +35,22 @@ export function resetDOM() {
requestAnimationFrame.queue = [];
containerEl = document.createElement('div');
containerEl.setAttribute('host', '');
host = createLNode(
null, LNodeFlags.Element, containerEl, createViewState(-1, activeRenderer, null !));
host = null;
// TODO: assert that the global state is clean (e.g. ngData, previousOrParentNode, etc)
}
export function renderToHtml(template: ComponentTemplate<any>, ctx: any) {
renderTemplate(host, template, ctx);
return toHtml(host.native);
export function renderToHtml(
template: ComponentTemplate<any>, ctx: any, providedRendererFactory?: RendererFactory3) {
host = renderTemplate(
containerEl, template, ctx, providedRendererFactory || testRendererFactory, host);
return toHtml(containerEl);
}
beforeEach(resetDOM);
export function renderComponent<T>(type: ComponentType<T>): T {
return _renderComponent(type, {renderer: activeRenderer, host: containerEl});
export function renderComponent<T>(type: ComponentType<T>, rendererFactory?: RendererFactory3): T {
return _renderComponent(
type, {rendererFactory: rendererFactory || testRendererFactory, host: containerEl});
}
export function toHtml<T>(componentOrElement: T | RElement): string {

View File

@ -0,0 +1,106 @@
/**
* @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 {RendererType2} from '@angular/core';
import {D, E, e} from '../../src/render3';
import {T, defineComponent, detectChanges} from '../../src/render3/index';
import {getRendererFactory2} from './imported_renderer2';
import {document, renderComponent, renderToHtml, resetDOM} from './render_util';
describe('renderer factory lifecycle', () => {
let logs: string[] = [];
let rendererFactory = getRendererFactory2(document);
const createRender = rendererFactory.createRenderer;
rendererFactory.createRenderer = (hostElement: any, type: RendererType2 | null) => {
logs.push('create');
return createRender.apply(rendererFactory, [hostElement, type]);
};
rendererFactory.begin = () => logs.push('begin');
rendererFactory.end = () => logs.push('end');
class SomeComponent {
static ngComponentDef = defineComponent({
type: SomeComponent,
tag: 'some-component',
template: function(ctx: SomeComponent, cm: boolean) {
logs.push('component');
if (cm) {
T(0, 'foo');
}
},
factory: () => new SomeComponent
});
}
class SomeComponentWhichThrows {
static ngComponentDef = defineComponent({
type: SomeComponentWhichThrows,
tag: 'some-component-with-Error',
template: function(ctx: SomeComponentWhichThrows, cm: boolean) {
throw(new Error('SomeComponentWhichThrows threw'));
},
factory: () => new SomeComponentWhichThrows
});
}
function Template(ctx: any, cm: boolean) {
logs.push('function');
if (cm) {
T(0, 'bar');
}
}
function TemplateWithComponent(ctx: any, cm: boolean) {
logs.push('function_with_component');
if (cm) {
T(0, 'bar');
E(1, SomeComponent.ngComponentDef);
{ D(2, SomeComponent.ngComponentDef.n(), SomeComponent.ngComponentDef); }
e();
}
SomeComponent.ngComponentDef.r(2, 1);
}
beforeEach(() => { logs = []; });
it('should work with a component', () => {
const component = renderComponent(SomeComponent, rendererFactory);
expect(logs).toEqual(['create', 'create', 'begin', 'component', 'end']);
logs = [];
detectChanges(component);
expect(logs).toEqual(['begin', 'component', 'end']);
});
it('should work with a component which throws', () => {
expect(() => renderComponent(SomeComponentWhichThrows, rendererFactory)).toThrow();
expect(logs).toEqual(['create', 'create', 'begin', 'end']);
});
it('should work with a template', () => {
renderToHtml(Template, {}, rendererFactory);
expect(logs).toEqual(['create', 'begin', 'function', 'end']);
logs = [];
renderToHtml(Template, {});
expect(logs).toEqual(['begin', 'function', 'end']);
});
it('should work with a template which contains a component', () => {
renderToHtml(TemplateWithComponent, {}, rendererFactory);
expect(logs).toEqual(
['create', 'begin', 'function_with_component', 'create', 'component', 'end']);
logs = [];
renderToHtml(TemplateWithComponent, {});
expect(logs).toEqual(['begin', 'function_with_component', 'component', 'end']);
});
});