From a57f3e7bbf18f8a8a32de0916dd74051786e3169 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 13 May 2019 19:01:55 +0200 Subject: [PATCH] test(ivy): move render3 renderer_factory tests to acceptance (#30435) Moves all manual render3 tests which are located within the `renderer_factory_spec.ts` file to acceptance tests. A few tests that use Ivy-specific logic which is not replicable with `TestBed` remain in the render3 folder (e.g. using `renderTemplate`) Additionally migrated tests that assert the lifecycles of the renderer_factory are set to *ivy only* as the lifecycle seems to be different in Ivy. Tracked with: FW-1320 PR Close #30435 --- packages/core/test/acceptance/BUILD.bazel | 14 +- .../test/acceptance/renderer_factory_spec.ts | 200 ++++++++++++++++++ .../test/render3/renderer_factory_spec.ts | 126 +---------- 3 files changed, 216 insertions(+), 124 deletions(-) create mode 100644 packages/core/test/acceptance/renderer_factory_spec.ts diff --git a/packages/core/test/acceptance/BUILD.bazel b/packages/core/test/acceptance/BUILD.bazel index 8f74f3f99a..c037d23381 100644 --- a/packages/core/test/acceptance/BUILD.bazel +++ b/packages/core/test/acceptance/BUILD.bazel @@ -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", @@ -9,6 +9,9 @@ ts_library( ["**/*.ts"], ), deps = [ + "//packages/animations", + "//packages/animations/browser", + "//packages/animations/browser/testing", "//packages/common", "//packages/compiler", "//packages/compiler/testing", @@ -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", ], @@ -34,3 +39,10 @@ jasmine_node_test( "@npm//zone.js", ], ) + +ts_web_test_suite( + name = "acceptance_web", + deps = [ + ":acceptance_lib", + ], +) diff --git a/packages/core/test/acceptance/renderer_factory_spec.ts b/packages/core/test/acceptance/renderer_factory_spec.ts new file mode 100644 index 0000000000..be26ca3543 --- /dev/null +++ b/packages/core/test/acceptance/renderer_factory_spec.ts @@ -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: ``}) + 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: ` +
+ foo +
+ `, + 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(/
\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); +} diff --git a/packages/core/test/render3/renderer_factory_spec.ts b/packages/core/test/render3/renderer_factory_spec.ts index 4d3952b83c..1e7473bdbd 100644 --- a/packages/core/test/render3/renderer_factory_spec.ts +++ b/packages/core/test/render3/renderer_factory_spec.ts @@ -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[] = []; @@ -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']); @@ -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(/
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);