Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/core/test/acceptance/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package(default_visibility = ["//visibility:private"])

load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library", "ts_web_test_suite")

ts_library(
name = "acceptance_lib",
Expand All @@ -9,6 +9,9 @@ ts_library(
["**/*.ts"],
),
deps = [
"//packages/animations",
"//packages/animations/browser",
"//packages/animations/browser/testing",
"//packages/common",
"//packages/compiler",
"//packages/compiler/testing",
Expand All @@ -17,7 +20,9 @@ ts_library(
"//packages/core/testing",
"//packages/platform-browser",
"//packages/platform-browser-dynamic",
"//packages/platform-browser/animations",
"//packages/platform-browser/testing",
"//packages/platform-server",
"//packages/private/testing",
"@npm//zone.js",
],
Expand All @@ -34,3 +39,10 @@ jasmine_node_test(
"@npm//zone.js",
],
)

ts_web_test_suite(
name = "acceptance_web",
deps = [
":acceptance_lib",
],
)
200 changes: 200 additions & 0 deletions packages/core/test/acceptance/renderer_factory_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/**
* @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 {AnimationEvent} from '@angular/animations';
import {ɵAnimationEngine, ɵNoopAnimationStyleNormalizer} from '@angular/animations/browser';
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
import {DOCUMENT} from '@angular/common';
import {Component, DoCheck, NgZone, RendererFactory2, RendererType2} from '@angular/core';
import {NoopNgZone} from '@angular/core/src/zone/ng_zone';
import {TestBed} from '@angular/core/testing';
import {EventManager, ɵDomSharedStylesHost} from '@angular/platform-browser';
import {ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ServerRendererFactory2} from '@angular/platform-server/src/server_renderer';
import {onlyInIvy} from '@angular/private/testing';

describe('renderer factory lifecycle', () => {
let logs: string[] = [];

@Component({selector: 'some-component', template: `foo`})
class SomeComponent implements DoCheck {
ngOnInit() { logs.push('some_component create'); }
ngDoCheck() { logs.push('some_component update'); }
}

@Component({selector: 'some-component-with-error', template: `With error`})
class SomeComponentWhichThrows {
ngOnInit() { throw new Error('SomeComponentWhichThrows threw'); }
}

@Component({selector: 'lol', template: `<some-component></some-component>`})
class TestComponent implements DoCheck {
ngOnInit() { logs.push('test_component create'); }
ngDoCheck() { logs.push('test_component update'); }
}

/** Creates a patched renderer factory that pushes entries to the test log */
function createPatchedRendererFactory(document: any) {
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');

return rendererFactory;
}

beforeEach(() => {
logs = [];

TestBed.configureTestingModule({
declarations: [SomeComponent, SomeComponentWhichThrows, TestComponent],
providers: [{
provide: RendererFactory2,
useFactory: (document: any) => createPatchedRendererFactory(document),
deps: [DOCUMENT]
}]
});
});

onlyInIvy('FW-1320: Ivy creates renderer twice.').it('should work with a component', () => {
const fixture = TestBed.createComponent(SomeComponent);
fixture.detectChanges();
expect(logs).toEqual(
['create', 'create', 'begin', 'some_component create', 'some_component update', 'end']);

logs = [];
fixture.detectChanges();
expect(logs).toEqual(['begin', 'some_component update', 'end']);
});

onlyInIvy('FW-1320: Ivy creates renderer twice.')
.it('should work with a component which throws', () => {
expect(() => {
const fixture = TestBed.createComponent(SomeComponentWhichThrows);
fixture.detectChanges();
}).toThrow();
expect(logs).toEqual(['create', 'create', 'begin', 'end']);
});
});

describe('animation renderer factory', () => {
let eventLogs: string[] = [];
let rendererFactory: RendererFactory2|null = null;

function getAnimationLog(): MockAnimationPlayer[] {
return MockAnimationDriver.log as MockAnimationPlayer[];
}

beforeEach(() => {
eventLogs = [];
rendererFactory = null;
MockAnimationDriver.log = [];

TestBed.configureTestingModule({
declarations: [SomeComponentWithAnimation, SomeComponent],
providers: [{
provide: RendererFactory2,
useFactory: (d: any) => rendererFactory = getAnimationRendererFactory2(d),
deps: [DOCUMENT]
}]
});
});

@Component({
selector: 'some-component',
template: `
<div [@myAnimation]="exp"
(@myAnimation.start)="callback($event)"
(@myAnimation.done)="callback($event)">
foo
</div>
`,
animations: [{
type: 7,
name: 'myAnimation',
definitions: [{
type: 1,
expr: '* => on',
animation: [{type: 4, styles: {type: 6, styles: {opacity: 1}, offset: null}, timings: 10}],
options: null
}],
options: {}
}]
})
class SomeComponentWithAnimation {
exp: string|undefined;

callback(event: AnimationEvent) {
eventLogs.push(`${event.fromState ? event.fromState : event.toState} - ${event.phaseName}`);
}
}

@Component({selector: 'some-component', template: 'foo'})
class SomeComponent {
}

it('should work with components without animations', () => {
const fixture = TestBed.createComponent(SomeComponent);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toEqual('foo');
});

isBrowser && it('should work with animated components', (done) => {
const fixture = TestBed.createComponent(SomeComponentWithAnimation);
fixture.detectChanges();

expect(rendererFactory).toBeTruthy();
expect(fixture.nativeElement.innerHTML)
.toMatch(/<div class="ng-tns-c\d+-0 ng-trigger ng-trigger-myAnimation">\s+foo\s+<\/div>/);

fixture.componentInstance.exp = 'on';
fixture.detectChanges();

const [player] = getAnimationLog();
expect(player.keyframes).toEqual([
{opacity: '*', offset: 0},
{opacity: 1, offset: 1},
]);
player.finish();

rendererFactory !.whenRenderingDone !().then(() => {
expect(eventLogs).toEqual(['void - start', 'void - done', 'on - start', 'on - done']);
done();
});
});
});

function getRendererFactory2(document: any): RendererFactory2 {
const fakeNgZone: NgZone = new NoopNgZone();
const eventManager = new EventManager([], fakeNgZone);
const rendererFactory = new ServerRendererFactory2(
eventManager, fakeNgZone, document, new ɵDomSharedStylesHost(document));
const origCreateRenderer = rendererFactory.createRenderer;
rendererFactory.createRenderer = function() {
const renderer = origCreateRenderer.apply(this, arguments);
renderer.destroyNode = () => {};
return renderer;
};
return rendererFactory;
}

function getAnimationRendererFactory2(document: any): RendererFactory2 {
const fakeNgZone: NgZone = new NoopNgZone();
return new ɵAnimationRendererFactory(
getRendererFactory2(document),
new ɵAnimationEngine(
document.body, new MockAnimationDriver(), new ɵNoopAnimationStyleNormalizer()),
fakeNgZone);
}
126 changes: 3 additions & 123 deletions packages/core/test/render3/renderer_factory_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/

import {AnimationEvent} from '@angular/animations';
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';

import {RendererType2, ViewEncapsulation} from '../../src/core';
import {ɵɵdefineComponent} from '../../src/render3/index';
import {tick, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵlistener, ɵɵtext} from '../../src/render3/instructions/all';
import {ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵtext} from '../../src/render3/instructions/all';
import {RenderFlags} from '../../src/render3/interfaces/definition';

import {getAnimationRendererFactory2, getRendererFactory2} from './imported_renderer2';
import {TemplateFixture, containerEl, document, renderComponent, renderToHtml, toHtml} from './render_util';
import {getRendererFactory2} from './imported_renderer2';
import {TemplateFixture, document, renderToHtml} from './render_util';

describe('renderer factory lifecycle', () => {
let logs: string[] = [];
Expand Down Expand Up @@ -87,21 +84,6 @@ describe('renderer factory lifecycle', () => {

beforeEach(() => { logs = []; });

it('should work with a component', () => {
const component = renderComponent(SomeComponent, {rendererFactory});
expect(logs).toEqual(
['create', 'create', 'begin', 'component create', 'component update', 'end']);

logs = [];
tick(component);
expect(logs).toEqual(['begin', 'component update', '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, {}, 1, 0, null, null, rendererFactory);
expect(logs).toEqual(['create', 'function create', 'function update']);
Expand All @@ -125,108 +107,6 @@ describe('renderer factory lifecycle', () => {

});

describe('animation renderer factory', () => {
let eventLogs: string[] = [];
function getLog(): MockAnimationPlayer[] {
return MockAnimationDriver.log as MockAnimationPlayer[];
}

function resetLog() { MockAnimationDriver.log = []; }

beforeEach(() => {
eventLogs = [];
resetLog();
});

class SomeComponent {
static ngComponentDef = ɵɵdefineComponent({
type: SomeComponent,
encapsulation: ViewEncapsulation.None,
selectors: [['some-component']],
consts: 1,
vars: 0,
template: function(rf: RenderFlags, ctx: SomeComponent) {
if (rf & RenderFlags.Create) {
ɵɵtext(0, 'foo');
}
},
factory: () => new SomeComponent
});
}

class SomeComponentWithAnimation {
// TODO(issue/24571): remove '!'.
exp !: string;
callback(event: AnimationEvent) {
eventLogs.push(`${event.fromState ? event.fromState : event.toState} - ${event.phaseName}`);
}
static ngComponentDef = ɵɵdefineComponent({
type: SomeComponentWithAnimation,
selectors: [['some-component']],
consts: 2,
vars: 1,
template: function(rf: RenderFlags, ctx: SomeComponentWithAnimation) {
if (rf & RenderFlags.Create) {
ɵɵelementStart(0, 'div');
{
ɵɵlistener('@myAnimation.start', ctx.callback.bind(ctx));
ɵɵlistener('@myAnimation.done', ctx.callback.bind(ctx));
ɵɵtext(1, 'foo');
}
ɵɵelementEnd();
}
if (rf & RenderFlags.Update) {
ɵɵelementProperty(0, '@myAnimation', ɵɵbind(ctx.exp));
}
},
factory: () => new SomeComponentWithAnimation,
encapsulation: ViewEncapsulation.None,
styles: [],
data: {
animation: [{
type: 7,
name: 'myAnimation',
definitions: [{
type: 1,
expr: '* => on',
animation:
[{type: 4, styles: {type: 6, styles: {opacity: 1}, offset: null}, timings: 10}],
options: null
}],
options: {}
}]
},
});
}

it('should work with components without animations', () => {
renderComponent(SomeComponent, {rendererFactory: getAnimationRendererFactory2(document)});
expect(toHtml(containerEl)).toEqual('foo');
});

isBrowser && it('should work with animated components', (done) => {
const rendererFactory = getAnimationRendererFactory2(document);
const component = renderComponent(SomeComponentWithAnimation, {rendererFactory});
expect(toHtml(containerEl))
.toMatch(/<div class="ng-tns-c\d+-0 ng-trigger ng-trigger-myAnimation">foo<\/div>/);

component.exp = 'on';
tick(component);

const [player] = getLog();
expect(player.keyframes).toEqual([
{opacity: '*', offset: 0},
{opacity: 1, offset: 1},
]);
player.finish();

rendererFactory.whenRenderingDone !().then(() => {
expect(eventLogs).toEqual(['void - start', 'void - done', 'on - start', 'on - done']);
done();
});
});
});

describe('Renderer2 destruction hooks', () => {
const rendererFactory = getRendererFactory2(document);

Expand Down