feat(platform-webworker): renderer v2 integration
This commit is contained in:
@ -42,7 +42,7 @@ export function createPairedMessageBuses(): PairedMessageBuses {
|
||||
export function expectBrokerCall(
|
||||
broker: SpyMessageBroker, methodName: string, vals?: Array<any>,
|
||||
handler?: (..._: any[]) => Promise<any>| void): void {
|
||||
broker.spy('runOnService').and.callFake((args: UiArguments, returnType: Type<any>) => {
|
||||
broker.spy('callUI').and.callFake((args: UiArguments, returnType: Type<any>) => {
|
||||
expect(args.method).toEqual(methodName);
|
||||
if (isPresent(vals)) {
|
||||
expect(args.args.length).toEqual(vals.length);
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
|
||||
import {UiArguments} from '@angular/platform-webworker/src/web_workers/shared/client_message_broker';
|
||||
import {MessageBus} from '@angular/platform-webworker/src/web_workers/shared/message_bus';
|
||||
import {LocationType} from '@angular/platform-webworker/src/web_workers/shared/serialized_types';
|
||||
@ -75,16 +74,15 @@ export function main() {
|
||||
expect(() => platformLocation.pathname = 'TEST').toThrowError();
|
||||
});
|
||||
|
||||
it('should send pathname to render thread',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const platformLocation = createWebWorkerPlatformLocation(TEST_LOCATION);
|
||||
platformLocation.init().then((_) => {
|
||||
const PATHNAME = '/test';
|
||||
expectBrokerCall(broker, 'setPathname', [PATHNAME]);
|
||||
platformLocation.pathname = PATHNAME;
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
it('should send pathname to render thread', done => {
|
||||
const platformLocation = createWebWorkerPlatformLocation(TEST_LOCATION);
|
||||
platformLocation.init().then((_) => {
|
||||
const PATHNAME = '/test';
|
||||
expectBrokerCall(broker, 'setPathname', [PATHNAME]);
|
||||
platformLocation.pathname = PATHNAME;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should send pushState to render thread', () => { testPushOrReplaceState(true); });
|
||||
|
||||
|
@ -6,23 +6,23 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ComponentRef, Injectable} from '@angular/core';
|
||||
import {Component, ComponentRef} from '@angular/core';
|
||||
import {DebugDomRootRenderer} from '@angular/core/src/debug/debug_renderer';
|
||||
import {RootRenderer} from '@angular/core/src/render/api';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DomRootRenderer, DomRootRenderer_} from '@angular/platform-browser/src/dom/dom_renderer';
|
||||
import {BrowserTestingModule} from '@angular/platform-browser/testing';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from '@angular/platform-webworker/src/web_workers/shared/client_message_broker';
|
||||
import {RenderStore} from '@angular/platform-webworker/src/web_workers/shared/render_store';
|
||||
import {Serializer} from '@angular/platform-webworker/src/web_workers/shared/serializer';
|
||||
import {ServiceMessageBrokerFactory_} from '@angular/platform-webworker/src/web_workers/shared/service_message_broker';
|
||||
import {MessageBasedRenderer} from '@angular/platform-webworker/src/web_workers/ui/renderer';
|
||||
import {WebWorkerRootRenderer} from '@angular/platform-webworker/src/web_workers/worker/renderer';
|
||||
|
||||
import {platformBrowserDynamicTesting} from '../../../../platform-browser-dynamic/testing';
|
||||
import {dispatchEvent} from '../../../../platform-browser/testing/browser_util';
|
||||
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from '../../../src/web_workers/shared/client_message_broker';
|
||||
import {RenderStore} from '../../../src/web_workers/shared/render_store';
|
||||
import {Serializer} from '../../../src/web_workers/shared/serializer';
|
||||
import {ServiceMessageBrokerFactory_} from '../../../src/web_workers/shared/service_message_broker';
|
||||
import {MessageBasedRenderer} from '../../../src/web_workers/ui/renderer';
|
||||
import {WebWorkerRootRenderer} from '../../../src/web_workers/worker/renderer';
|
||||
import {PairedMessageBuses, createPairedMessageBuses} from '../shared/web_worker_test_util';
|
||||
|
||||
export function main() {
|
||||
@ -70,19 +70,23 @@ export function main() {
|
||||
testUiInjector.ngModule = BrowserTestingModule;
|
||||
testUiInjector.configureTestingModule({
|
||||
providers: [
|
||||
Serializer, {provide: RenderStore, useValue: uiRenderStore},
|
||||
Serializer,
|
||||
{provide: RenderStore, useValue: uiRenderStore},
|
||||
{provide: DomRootRenderer, useClass: DomRootRenderer_},
|
||||
{provide: RootRenderer, useExisting: DomRootRenderer}
|
||||
{provide: RootRenderer, useExisting: DomRootRenderer},
|
||||
]
|
||||
});
|
||||
const uiSerializer = testUiInjector.get(Serializer);
|
||||
const domRootRenderer = testUiInjector.get(DomRootRenderer);
|
||||
|
||||
workerRenderStore = new RenderStore();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MyComp2],
|
||||
providers: [
|
||||
Serializer, {provide: RenderStore, useValue: workerRenderStore}, {
|
||||
Serializer,
|
||||
{provide: RenderStore, useValue: workerRenderStore},
|
||||
{
|
||||
provide: RootRenderer,
|
||||
useFactory: (workerSerializer: Serializer) => {
|
||||
return createWorkerRenderer(
|
||||
@ -90,12 +94,12 @@ export function main() {
|
||||
workerRenderStore);
|
||||
},
|
||||
deps: [Serializer]
|
||||
}
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
function getRenderElement(workerEl: any) {
|
||||
function getRenderElement(workerEl: any): any {
|
||||
const id = workerRenderStore.serialize(workerEl);
|
||||
return uiRenderStore.deserialize(id);
|
||||
}
|
||||
@ -122,26 +126,25 @@ export function main() {
|
||||
MyComp2, {set: {template: '<input [title]="y" style="position:absolute">'}});
|
||||
const fixture = TestBed.createComponent(MyComp2);
|
||||
|
||||
const checkSetters =
|
||||
(componentRef: any /** TODO #9100 */, workerEl: any /** TODO #9100 */) => {
|
||||
const renderer = getRenderer(componentRef);
|
||||
const el = getRenderElement(workerEl);
|
||||
renderer.setElementProperty(workerEl, 'tabIndex', 1);
|
||||
expect((<HTMLInputElement>el).tabIndex).toEqual(1);
|
||||
const checkSetters = (componentRef: ComponentRef<any>, workerEl: any) => {
|
||||
const renderer = getRenderer(componentRef);
|
||||
const el = getRenderElement(workerEl);
|
||||
renderer.setElementProperty(workerEl, 'tabIndex', 1);
|
||||
expect(el.tabIndex).toEqual(1);
|
||||
|
||||
renderer.setElementClass(workerEl, 'a', true);
|
||||
expect(getDOM().hasClass(el, 'a')).toBe(true);
|
||||
renderer.setElementClass(workerEl, 'a', false);
|
||||
expect(getDOM().hasClass(el, 'a')).toBe(false);
|
||||
renderer.setElementClass(workerEl, 'a', true);
|
||||
expect(getDOM().hasClass(el, 'a')).toBe(true);
|
||||
renderer.setElementClass(workerEl, 'a', false);
|
||||
expect(getDOM().hasClass(el, 'a')).toBe(false);
|
||||
|
||||
renderer.setElementStyle(workerEl, 'width', '10px');
|
||||
expect(getDOM().getStyle(el, 'width')).toEqual('10px');
|
||||
renderer.setElementStyle(workerEl, 'width', null);
|
||||
expect(getDOM().getStyle(el, 'width')).toEqual('');
|
||||
renderer.setElementStyle(workerEl, 'width', '10px');
|
||||
expect(getDOM().getStyle(el, 'width')).toEqual('10px');
|
||||
renderer.setElementStyle(workerEl, 'width', null);
|
||||
expect(getDOM().getStyle(el, 'width')).toEqual('');
|
||||
|
||||
renderer.setElementAttribute(workerEl, 'someattr', 'someValue');
|
||||
expect(getDOM().getAttribute(el, 'someattr')).toEqual('someValue');
|
||||
};
|
||||
renderer.setElementAttribute(workerEl, 'someattr', 'someValue');
|
||||
expect(getDOM().getAttribute(el, 'someattr')).toEqual('someValue');
|
||||
};
|
||||
|
||||
// root element
|
||||
checkSetters(fixture.componentRef, fixture.nativeElement);
|
||||
@ -150,12 +153,11 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should update any template comment property/attributes', () => {
|
||||
|
||||
TestBed.overrideComponent(
|
||||
MyComp2, {set: {template: '<ng-container *ngIf="ctxBoolProp"></ng-container>'}});
|
||||
const fixture = TestBed.createComponent(MyComp2);
|
||||
|
||||
(<MyComp2>fixture.componentInstance).ctxBoolProp = true;
|
||||
fixture.componentInstance.ctxBoolProp = true;
|
||||
fixture.detectChanges();
|
||||
const el = getRenderElement(fixture.nativeElement);
|
||||
expect(getDOM().getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
|
||||
@ -183,6 +185,7 @@ export function main() {
|
||||
TestBed.overrideComponent(MyComp2, {set: {template: '<input [title]="y">'}});
|
||||
const fixture = TestBed.createComponent(MyComp2);
|
||||
const el = fixture.debugElement.children[0];
|
||||
|
||||
getRenderer(fixture.componentRef).invokeElementMethod(el.nativeElement, 'setAttribute', [
|
||||
'a', 'b'
|
||||
]);
|
||||
@ -207,16 +210,8 @@ export function main() {
|
||||
|
||||
|
||||
@Component({selector: 'my-comp'})
|
||||
@Injectable()
|
||||
class MyComp2 {
|
||||
ctxProp: string;
|
||||
ctxNumProp: any /** TODO #9100 */;
|
||||
ctxBoolProp: boolean;
|
||||
constructor() {
|
||||
this.ctxProp = 'initial value';
|
||||
this.ctxNumProp = 0;
|
||||
this.ctxBoolProp = false;
|
||||
}
|
||||
|
||||
throwError() { throw 'boom'; }
|
||||
ctxProp = 'initial value';
|
||||
ctxNumProp = 0;
|
||||
ctxBoolProp = false;
|
||||
}
|
||||
|
@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @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 {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||
import {Component, ComponentRef, RendererFactoryV2, RendererTypeV2, RendererV2, RootRenderer} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DomRendererFactoryV2, DomRootRenderer, DomRootRenderer_} from '@angular/platform-browser/src/dom/dom_renderer';
|
||||
import {BrowserTestingModule} from '@angular/platform-browser/testing';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from '../../../src/web_workers/shared/client_message_broker';
|
||||
import {RenderStore} from '../../../src/web_workers/shared/render_store';
|
||||
import {Serializer} from '../../../src/web_workers/shared/serializer';
|
||||
import {ServiceMessageBrokerFactory_} from '../../../src/web_workers/shared/service_message_broker';
|
||||
import {MessageBasedRendererV2} from '../../../src/web_workers/ui/renderer';
|
||||
import {WebWorkerRendererFactoryV2} from '../../../src/web_workers/worker/renderer';
|
||||
import {PairedMessageBuses, createPairedMessageBuses} from '../shared/web_worker_test_util';
|
||||
|
||||
let lastCreatedRenderer: RendererV2;
|
||||
|
||||
export function main() {
|
||||
describe('Web Worker Renderer v2', () => {
|
||||
// Don't run on server...
|
||||
if (!getDOM().supportsDOMEvents()) return;
|
||||
|
||||
let uiRenderStore: RenderStore;
|
||||
let wwRenderStore: RenderStore;
|
||||
|
||||
beforeEach(() => {
|
||||
// UI side
|
||||
uiRenderStore = new RenderStore();
|
||||
const uiInjector = new TestBed();
|
||||
uiInjector.platform = platformBrowserDynamicTesting();
|
||||
uiInjector.ngModule = BrowserTestingModule;
|
||||
uiInjector.configureTestingModule({
|
||||
providers: [
|
||||
{provide: USE_VIEW_ENGINE, useValue: true}, Serializer,
|
||||
{provide: RenderStore, useValue: uiRenderStore}, DomRendererFactoryV2,
|
||||
{provide: RendererFactoryV2, useExisting: DomRendererFactoryV2},
|
||||
{provide: DomRootRenderer, useClass: DomRootRenderer_},
|
||||
{provide: RootRenderer, useExisting: DomRootRenderer}
|
||||
]
|
||||
});
|
||||
const uiSerializer = uiInjector.get(Serializer);
|
||||
const domRendererFactory = uiInjector.get(RendererFactoryV2);
|
||||
|
||||
// Worker side
|
||||
lastCreatedRenderer = null;
|
||||
|
||||
wwRenderStore = new RenderStore();
|
||||
|
||||
TestBed
|
||||
.configureCompiler({
|
||||
useJit: true,
|
||||
providers: [
|
||||
{provide: USE_VIEW_ENGINE, useValue: true},
|
||||
],
|
||||
})
|
||||
.configureTestingModule({
|
||||
declarations: [MyComp2],
|
||||
providers: [
|
||||
Serializer,
|
||||
{provide: RenderStore, useValue: wwRenderStore},
|
||||
{
|
||||
provide: RendererFactoryV2,
|
||||
useFactory: (wwSerializer: Serializer) => createWebWorkerRendererFactoryV2(
|
||||
wwSerializer, uiSerializer, domRendererFactory, uiRenderStore,
|
||||
wwRenderStore),
|
||||
deps: [Serializer],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
function getRenderElement(workerEl: any): any {
|
||||
const id = wwRenderStore.serialize(workerEl);
|
||||
return uiRenderStore.deserialize(id);
|
||||
}
|
||||
|
||||
it('should update text nodes', () => {
|
||||
const fixture =
|
||||
TestBed.overrideTemplate(MyComp2, '<div>{{ctxProp}}</div>').createComponent(MyComp2);
|
||||
const renderEl = getRenderElement(fixture.nativeElement);
|
||||
expect(renderEl).toHaveText('');
|
||||
|
||||
fixture.componentInstance.ctxProp = 'Hello World!';
|
||||
fixture.detectChanges();
|
||||
expect(renderEl).toHaveText('Hello World!');
|
||||
});
|
||||
|
||||
it('should update any element property/attributes/class/style(s) independent of the compilation on the root element and other elements',
|
||||
() => {
|
||||
const fixture =
|
||||
TestBed.overrideTemplate(MyComp2, '<input [title]="y" style="position:absolute">')
|
||||
.createComponent(MyComp2);
|
||||
|
||||
const checkSetters = (componentRef: ComponentRef<any>, workerEl: any) => {
|
||||
expect(lastCreatedRenderer).not.toEqual(null);
|
||||
|
||||
const el = getRenderElement(workerEl);
|
||||
lastCreatedRenderer.setProperty(workerEl, 'tabIndex', 1);
|
||||
expect(el.tabIndex).toEqual(1);
|
||||
|
||||
lastCreatedRenderer.addClass(workerEl, 'a');
|
||||
expect(getDOM().hasClass(el, 'a')).toBe(true);
|
||||
|
||||
lastCreatedRenderer.removeClass(workerEl, 'a');
|
||||
expect(getDOM().hasClass(el, 'a')).toBe(false);
|
||||
|
||||
lastCreatedRenderer.setStyle(workerEl, 'width', '10px', false, false);
|
||||
expect(getDOM().getStyle(el, 'width')).toEqual('10px');
|
||||
|
||||
lastCreatedRenderer.removeStyle(workerEl, 'width', false);
|
||||
expect(getDOM().getStyle(el, 'width')).toEqual('');
|
||||
|
||||
lastCreatedRenderer.setAttribute(workerEl, 'someattr', 'someValue');
|
||||
expect(getDOM().getAttribute(el, 'someattr')).toEqual('someValue');
|
||||
};
|
||||
|
||||
// root element
|
||||
checkSetters(fixture.componentRef, fixture.nativeElement);
|
||||
// nested elements
|
||||
checkSetters(fixture.componentRef, fixture.debugElement.children[0].nativeElement);
|
||||
});
|
||||
|
||||
it('should update any template comment property/attributes', () => {
|
||||
const fixture =
|
||||
TestBed.overrideTemplate(MyComp2, '<ng-container *ngIf="ctxBoolProp"></ng-container>')
|
||||
.createComponent(MyComp2);
|
||||
fixture.componentInstance.ctxBoolProp = true;
|
||||
fixture.detectChanges();
|
||||
const el = getRenderElement(fixture.nativeElement);
|
||||
expect(getDOM().getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
|
||||
});
|
||||
|
||||
it('should add and remove fragments', () => {
|
||||
const fixture =
|
||||
TestBed
|
||||
.overrideTemplate(MyComp2, '<ng-container *ngIf="ctxBoolProp">hello</ng-container>')
|
||||
.createComponent(MyComp2);
|
||||
|
||||
const rootEl = getRenderElement(fixture.nativeElement);
|
||||
expect(rootEl).toHaveText('');
|
||||
|
||||
fixture.componentInstance.ctxBoolProp = true;
|
||||
fixture.detectChanges();
|
||||
expect(rootEl).toHaveText('hello');
|
||||
|
||||
fixture.componentInstance.ctxBoolProp = false;
|
||||
fixture.detectChanges();
|
||||
expect(rootEl).toHaveText('');
|
||||
});
|
||||
|
||||
if (getDOM().supportsDOMEvents()) {
|
||||
it('should listen to events', () => {
|
||||
const fixture = TestBed.overrideTemplate(MyComp2, '<input (change)="ctxNumProp = 1">')
|
||||
.createComponent(MyComp2);
|
||||
|
||||
const el = fixture.debugElement.children[0];
|
||||
dispatchEvent(getRenderElement(el.nativeElement), 'change');
|
||||
expect(fixture.componentInstance.ctxNumProp).toBe(1);
|
||||
|
||||
fixture.destroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Component({selector: 'my-comp'})
|
||||
class MyComp2 {
|
||||
ctxProp = 'initial value';
|
||||
ctxNumProp = 0;
|
||||
ctxBoolProp = false;
|
||||
}
|
||||
|
||||
function createWebWorkerBrokerFactory(
|
||||
messageBuses: PairedMessageBuses, wwSerializer: Serializer, uiSerializer: Serializer,
|
||||
domRendererFactory: DomRendererFactoryV2,
|
||||
uiRenderStore: RenderStore): ClientMessageBrokerFactory {
|
||||
const uiMessageBus = messageBuses.ui;
|
||||
const wwMessageBus = messageBuses.worker;
|
||||
|
||||
// set up the worker side
|
||||
const wwBrokerFactory = new ClientMessageBrokerFactory_(wwMessageBus, wwSerializer);
|
||||
|
||||
// set up the ui side
|
||||
const uiBrokerFactory = new ServiceMessageBrokerFactory_(uiMessageBus, uiSerializer);
|
||||
const renderer = new MessageBasedRendererV2(
|
||||
uiBrokerFactory, uiMessageBus, uiSerializer, uiRenderStore, domRendererFactory);
|
||||
renderer.start();
|
||||
|
||||
return wwBrokerFactory;
|
||||
}
|
||||
|
||||
function createWebWorkerRendererFactoryV2(
|
||||
workerSerializer: Serializer, uiSerializer: Serializer,
|
||||
domRendererFactory: DomRendererFactoryV2, uiRenderStore: RenderStore,
|
||||
workerRenderStore: RenderStore): RendererFactoryV2 {
|
||||
const messageBuses = createPairedMessageBuses();
|
||||
const brokerFactory = createWebWorkerBrokerFactory(
|
||||
messageBuses, workerSerializer, uiSerializer, domRendererFactory, uiRenderStore);
|
||||
|
||||
const rendererFactory =
|
||||
new RenderFactory(brokerFactory, messageBuses.worker, workerSerializer, workerRenderStore);
|
||||
|
||||
return rendererFactory;
|
||||
}
|
||||
|
||||
class RenderFactory extends WebWorkerRendererFactoryV2 {
|
||||
createRenderer(element: any, type: RendererTypeV2): RendererV2 {
|
||||
lastCreatedRenderer = super.createRenderer(element, type);
|
||||
return lastCreatedRenderer;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user