test(ivy): enable platform-server tests for ivy (#27102)

PR Close #27102
This commit is contained in:
Olivier Combe 2018-11-16 16:14:36 +01:00 committed by Miško Hevery
parent 893c1735dd
commit cf1ebdc079
2 changed files with 202 additions and 173 deletions

View File

@ -17,6 +17,7 @@ ts_library(
"//packages/http/testing", "//packages/http/testing",
"//packages/platform-browser", "//packages/platform-browser",
"//packages/platform-server", "//packages/platform-server",
"//packages/private/testing",
"@rxjs", "@rxjs",
"@rxjs//operators", "@rxjs//operators",
], ],
@ -26,7 +27,6 @@ jasmine_node_test(
name = "test", name = "test",
bootstrap = ["angular/tools/testing/init_node_spec.js"], bootstrap = ["angular/tools/testing/init_node_spec.js"],
tags = [ tags = [
"fixme-ivy-aot",
], ],
deps = [ deps = [
":test_lib", ":test_lib",

View File

@ -7,16 +7,17 @@
*/ */
import {AnimationBuilder, animate, state, style, transition, trigger} from '@angular/animations'; import {AnimationBuilder, animate, state, style, transition, trigger} from '@angular/animations';
import {APP_BASE_HREF, PlatformLocation, isPlatformServer} from '@angular/common'; import {PlatformLocation, isPlatformServer} from '@angular/common';
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {ApplicationRef, CompilerFactory, Component, HostListener, Inject, Injectable, Input, NgModule, NgModuleRef, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation, destroyPlatform, getPlatform} from '@angular/core'; import {ApplicationRef, CompilerFactory, Component, HostListener, Inject, Injectable, Input, NgModule, NgModuleRef, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation, destroyPlatform, getPlatform} from '@angular/core';
import {TestBed, async, inject} from '@angular/core/testing'; import {async, inject} from '@angular/core/testing';
import {Http, HttpModule, Response, ResponseOptions, XHRBackend} from '@angular/http'; import {Http, HttpModule, Response, ResponseOptions, XHRBackend} from '@angular/http';
import {MockBackend, MockConnection} from '@angular/http/testing'; import {MockBackend, MockConnection} from '@angular/http/testing';
import {BrowserModule, DOCUMENT, StateKey, Title, TransferState, makeStateKey} from '@angular/platform-browser'; import {BrowserModule, DOCUMENT, Title, TransferState, makeStateKey} from '@angular/platform-browser';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformState, ServerModule, ServerTransferStateModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server'; import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformState, ServerModule, ServerTransferStateModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server';
import {fixmeIvy} from '@angular/private/testing';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {first} from 'rxjs/operators'; import {first} from 'rxjs/operators';
@ -432,20 +433,21 @@ class HiddenModule {
}); });
})); }));
it('adds styles with ng-transition attribute', async(() => { fixmeIvy('no deduplication of imported modules') &&
const platform = platformDynamicServer([{ it('adds styles with ng-transition attribute', async(() => {
provide: INITIAL_CONFIG, const platform = platformDynamicServer([{
useValue: {document: '<html><head></head><body><app></app></body></html>'} provide: INITIAL_CONFIG,
}]); useValue: {document: '<html><head></head><body><app></app></body></html>'}
platform.bootstrapModule(ExampleStylesModule).then(ref => { }]);
const doc = ref.injector.get(DOCUMENT); platform.bootstrapModule(ExampleStylesModule).then(ref => {
const head = getDOM().getElementsByTagName(doc, 'head')[0]; const doc = ref.injector.get(DOCUMENT);
const styles: any[] = head.children as any; const head = getDOM().getElementsByTagName(doc, 'head')[0];
expect(styles.length).toBe(1); const styles: any[] = head.children as any;
expect(getDOM().getText(styles[0])).toContain('color: red'); expect(styles.length).toBe(1);
expect(getDOM().getAttribute(styles[0], 'ng-transition')).toBe('example-styles'); expect(getDOM().getText(styles[0])).toContain('color: red');
}); expect(getDOM().getAttribute(styles[0], 'ng-transition')).toBe('example-styles');
})); });
}));
it('copies known properties to attributes', async(() => { it('copies known properties to attributes', async(() => {
const platform = platformDynamicServer( const platform = platformDynamicServer(
@ -538,29 +540,32 @@ class HiddenModule {
}); });
afterEach(() => { expect(called).toBe(true); }); afterEach(() => { expect(called).toBe(true); });
it('using long form should work', async(() => { fixmeIvy('to investigate') &&
const platform = it('using long form should work', async(() => {
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]); const platform =
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]);
platform.bootstrapModule(AsyncServerModule) platform.bootstrapModule(AsyncServerModule)
.then((moduleRef) => { .then((moduleRef) => {
const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
return applicationRef.isStable.pipe(first((isStable: boolean) => isStable)) return applicationRef.isStable.pipe(first((isStable: boolean) => isStable))
.toPromise(); .toPromise();
}) })
.then((b) => { .then((b) => {
expect(platform.injector.get(PlatformState).renderToString()).toBe(expectedOutput); expect(platform.injector.get(PlatformState).renderToString())
platform.destroy(); .toBe(expectedOutput);
platform.destroy();
called = true;
});
}));
fixmeIvy('to investigate') &&
it('using renderModule should work', async(() => {
renderModule(AsyncServerModule, {document: doc}).then(output => {
expect(output).toBe(expectedOutput);
called = true; called = true;
}); });
})); }));
it('using renderModule should work', async(() => {
renderModule(AsyncServerModule, {document: doc}).then(output => {
expect(output).toBe(expectedOutput);
called = true;
});
}));
it('using renderModuleFactory should work', it('using renderModuleFactory should work',
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => { async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
@ -574,25 +579,27 @@ class HiddenModule {
}); });
}))); })));
it('works with SVG elements', async(() => { fixmeIvy('to investigate') &&
renderModule(SVGServerModule, {document: doc}).then(output => { it('works with SVG elements', async(() => {
expect(output).toBe( renderModule(SVGServerModule, {document: doc}).then(output => {
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' + expect(output).toBe(
'<svg><use xlink:href="#clear"></use></svg></app></body></html>'); '<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
called = true; '<svg><use xlink:href="#clear"></use></svg></app></body></html>');
}); called = true;
})); });
}));
it('works with animation', async(() => { fixmeIvy('to investigate') &&
renderModule(AnimationServerModule, {document: doc}).then(output => { it('works with animation', async(() => {
expect(output).toContain('Works!'); renderModule(AnimationServerModule, {document: doc}).then(output => {
expect(output).toContain('ng-trigger-myAnimation'); expect(output).toContain('Works!');
expect(output).toContain('opacity:1;'); expect(output).toContain('ng-trigger-myAnimation');
expect(output).toContain('transform:translate3d(0 , 0 , 0);'); expect(output).toContain('opacity:1;');
expect(output).toContain('font-weight:bold;'); expect(output).toContain('transform:translate3d(0 , 0 , 0);');
called = true; expect(output).toContain('font-weight:bold;');
}); called = true;
})); });
}));
it('should handle ViewEncapsulation.Native', async(() => { it('should handle ViewEncapsulation.Native', async(() => {
renderModule(NativeExampleModule, {document: doc}).then(output => { renderModule(NativeExampleModule, {document: doc}).then(output => {
@ -603,75 +610,82 @@ class HiddenModule {
})); }));
it('sets a prefix for the _nghost and _ngcontent attributes', async(() => { fixmeIvy('to investigate') &&
renderModule(ExampleStylesModule, {document: doc}).then(output => { it('sets a prefix for the _nghost and _ngcontent attributes', async(() => {
expect(output).toMatch( renderModule(ExampleStylesModule, {document: doc}).then(output => {
/<html><head><style ng-transition="example-styles">div\[_ngcontent-sc\d+\] {color: blue; } \[_nghost-sc\d+\] { color: red; }<\/style><\/head><body><app _nghost-sc\d+="" ng-version="0.0.0-PLACEHOLDER"><div _ngcontent-sc\d+="">Works!<\/div><\/app><\/body><\/html>/); expect(output).toMatch(
called = true; /<html><head><style ng-transition="example-styles">div\[_ngcontent-sc\d+\] {color: blue; } \[_nghost-sc\d+\] { color: red; }<\/style><\/head><body><app _nghost-sc\d+="" ng-version="0.0.0-PLACEHOLDER"><div _ngcontent-sc\d+="">Works!<\/div><\/app><\/body><\/html>/);
}); called = true;
})); });
}));
it('should handle false values on attributes', async(() => { fixmeIvy('to investigate') &&
renderModule(FalseAttributesModule, {document: doc}).then(output => { it('should handle false values on attributes', async(() => {
expect(output).toBe( renderModule(FalseAttributesModule, {document: doc}).then(output => {
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' + expect(output).toBe(
'<my-child ng-reflect-attr="false">Works!</my-child></app></body></html>'); '<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
called = true; '<my-child ng-reflect-attr="false">Works!</my-child></app></body></html>');
}); called = true;
})); });
}));
it('should handle element property "name"', async(() => { fixmeIvy('to investigate') &&
renderModule(NameModule, {document: doc}).then(output => { it('should handle element property "name"', async(() => {
expect(output).toBe( renderModule(NameModule, {document: doc}).then(output => {
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' + expect(output).toBe(
'<input name=""></app></body></html>'); '<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
called = true; '<input name=""></app></body></html>');
}); called = true;
})); });
}));
it('should work with sanitizer to handle "innerHTML"', async(() => { fixmeIvy('to investigate') &&
// Clear out any global states. These should be set when platform-server it('should work with sanitizer to handle "innerHTML"', async(() => {
// is initialized. // Clear out any global states. These should be set when platform-server
(global as any).Node = undefined; // is initialized.
(global as any).Document = undefined; (global as any).Node = undefined;
renderModule(HTMLTypesModule, {document: doc}).then(output => { (global as any).Document = undefined;
expect(output).toBe( renderModule(HTMLTypesModule, {document: doc}).then(output => {
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' + expect(output).toBe(
'<div><b>foo</b> bar</div></app></body></html>'); '<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
called = true; '<div><b>foo</b> bar</div></app></body></html>');
}); called = true;
})); });
}));
it('should handle element property "hidden"', async(() => { fixmeIvy('to investigate') &&
renderModule(HiddenModule, {document: doc}).then(output => { it('should handle element property "hidden"', async(() => {
expect(output).toBe( renderModule(HiddenModule, {document: doc}).then(output => {
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' + expect(output).toBe(
'<input hidden=""><input></app></body></html>'); '<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
called = true; '<input hidden=""><input></app></body></html>');
}); called = true;
})); });
}));
it('should call render hook', async(() => { fixmeIvy('to investigate') &&
renderModule(RenderHookModule, {document: doc}).then(output => { it('should call render hook', async(() => {
// title should be added by the render hook. renderModule(RenderHookModule, {document: doc}).then(output => {
expect(output).toBe( // title should be added by the render hook.
'<html><head><title>RenderHook</title></head><body>' + expect(output).toBe(
'<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>'); '<html><head><title>RenderHook</title></head><body>' +
called = true; '<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
}); called = true;
})); });
}));
it('should call multiple render hooks', async(() => { fixmeIvy('to investigate') &&
const consoleSpy = spyOn(console, 'warn'); it('should call multiple render hooks', async(() => {
renderModule(MultiRenderHookModule, {document: doc}).then(output => { const consoleSpy = spyOn(console, 'warn');
// title should be added by the render hook. renderModule(MultiRenderHookModule, {document: doc}).then(output => {
expect(output).toBe( // title should be added by the render hook.
'<html><head><title>RenderHook</title><meta name="description"></head>' + expect(output).toBe(
'<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>'); '<html><head><title>RenderHook</title><meta name="description"></head>' +
expect(consoleSpy).toHaveBeenCalled(); '<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
called = true; expect(consoleSpy).toHaveBeenCalled();
}); called = true;
})); });
}));
}); });
describe('http', () => { describe('http', () => {
@ -682,6 +696,7 @@ class HiddenModule {
expect(ref.injector.get(Http) instanceof Http).toBeTruthy(); expect(ref.injector.get(Http) instanceof Http).toBeTruthy();
}); });
})); }));
it('can make Http requests', async(() => { it('can make Http requests', async(() => {
const platform = platformDynamicServer( const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]); [{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
@ -702,25 +717,29 @@ class HiddenModule {
}); });
}); });
})); }));
it('requests are macrotasks', async(() => {
const platform = platformDynamicServer( fixmeIvy('no deduplication of imported modules') &&
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]); it('requests are macrotasks', async(() => {
platform.bootstrapModule(ExampleModule).then(ref => { const platform = platformDynamicServer(
const mock = ref.injector.get(MockBackend); [{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
const http = ref.injector.get(Http); platform.bootstrapModule(ExampleModule).then(ref => {
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeFalsy(); const mock = ref.injector.get(MockBackend);
ref.injector.get<NgZone>(NgZone).run(() => { const http = ref.injector.get(Http);
NgZone.assertInAngularZone(); expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeFalsy();
mock.connections.subscribe((mc: MockConnection) => { ref.injector.get<NgZone>(NgZone).run(() => {
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeTruthy(); NgZone.assertInAngularZone();
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200}))); mock.connections.subscribe((mc: MockConnection) => {
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeTruthy();
mc.mockRespond(
new Response(new ResponseOptions({body: 'success!', status: 200})));
});
http.get('http://localhost/testing').subscribe(resp => {
expect(resp.text()).toBe('success!');
});
});
}); });
http.get('http://localhost/testing').subscribe(resp => { }));
expect(resp.text()).toBe('success!');
});
});
});
}));
it('works when HttpModule is included before ServerModule', async(() => { it('works when HttpModule is included before ServerModule', async(() => {
const platform = platformDynamicServer( const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]); [{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
@ -740,25 +759,29 @@ class HiddenModule {
}); });
}); });
})); }));
it('works when HttpModule is included after ServerModule', async(() => {
const platform = platformDynamicServer( fixmeIvy('no deduplication of imported modules') &&
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]); it('works when HttpModule is included after ServerModule', async(() => {
platform.bootstrapModule(HttpAfterExampleModule).then(ref => { const platform = platformDynamicServer(
const mock = ref.injector.get(MockBackend); [{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
const http = ref.injector.get(Http); platform.bootstrapModule(HttpAfterExampleModule).then(ref => {
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeFalsy(); const mock = ref.injector.get(MockBackend);
ref.injector.get<NgZone>(NgZone).run(() => { const http = ref.injector.get(Http);
NgZone.assertInAngularZone(); expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeFalsy();
mock.connections.subscribe((mc: MockConnection) => { ref.injector.get<NgZone>(NgZone).run(() => {
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeTruthy(); NgZone.assertInAngularZone();
mc.mockRespond(new Response(new ResponseOptions({body: 'success!', status: 200}))); mock.connections.subscribe((mc: MockConnection) => {
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeTruthy();
mc.mockRespond(
new Response(new ResponseOptions({body: 'success!', status: 200})));
});
http.get('http://localhost/testing').subscribe(resp => {
expect(resp.text()).toBe('success!');
});
});
}); });
http.get('http://localhost/testing').subscribe(resp => { }));
expect(resp.text()).toBe('success!');
});
});
});
}));
it('throws when given a relative URL', async(() => { it('throws when given a relative URL', async(() => {
const platform = platformDynamicServer( const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]); [{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
@ -770,6 +793,7 @@ class HiddenModule {
}); });
})); }));
}); });
describe('HttpClient', () => { describe('HttpClient', () => {
it('can inject HttpClient', async(() => { it('can inject HttpClient', async(() => {
const platform = platformDynamicServer( const platform = platformDynamicServer(
@ -778,6 +802,7 @@ class HiddenModule {
expect(ref.injector.get(HttpClient) instanceof HttpClient).toBeTruthy(); expect(ref.injector.get(HttpClient) instanceof HttpClient).toBeTruthy();
}); });
})); }));
it('can make HttpClient requests', async(() => { it('can make HttpClient requests', async(() => {
const platform = platformDynamicServer( const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]); [{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
@ -793,6 +818,7 @@ class HiddenModule {
}); });
}); });
})); }));
it('requests are macrotasks', async(() => { it('requests are macrotasks', async(() => {
const platform = platformDynamicServer( const platform = platformDynamicServer(
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]); [{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
@ -809,6 +835,7 @@ class HiddenModule {
}); });
}); });
})); }));
it('can use HttpInterceptor that injects HttpClient', () => { it('can use HttpInterceptor that injects HttpClient', () => {
const platform = const platform =
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]); platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
@ -834,12 +861,13 @@ class HiddenModule {
beforeEach(() => { called = false; }); beforeEach(() => { called = false; });
afterEach(() => { expect(called).toBe(true); }); afterEach(() => { expect(called).toBe(true); });
it('adds transfer script tag when using renderModule', async(() => { fixmeIvy('to investigate') &&
renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => { it('adds transfer script tag when using renderModule', async(() => {
expect(output).toBe(defaultExpectedOutput); renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => {
called = true; expect(output).toBe(defaultExpectedOutput);
}); called = true;
})); });
}));
it('adds transfer script tag when using renderModuleFactory', it('adds transfer script tag when using renderModuleFactory',
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => { async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
@ -853,18 +881,19 @@ class HiddenModule {
}); });
}))); })));
it('cannot break out of <script> tag in serialized output', async(() => { fixmeIvy('to investigate') &&
renderModule(EscapedTransferStoreModule, { it('cannot break out of <script> tag in serialized output', async(() => {
document: '<esc-app></esc-app>' renderModule(EscapedTransferStoreModule, {
}).then(output => { document: '<esc-app></esc-app>'
expect(output).toBe( }).then(output => {
'<html><head></head><body><esc-app ng-version="0.0.0-PLACEHOLDER">Works!</esc-app>' + expect(output).toBe(
'<script id="transfer-state" type="application/json">' + '<html><head></head><body><esc-app ng-version="0.0.0-PLACEHOLDER">Works!</esc-app>' +
'{&q;testString&q;:&q;&l;/script&g;&l;script&g;' + '<script id="transfer-state" type="application/json">' +
'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}</script></body></html>'); '{&q;testString&q;:&q;&l;/script&g;&l;script&g;' +
called = true; 'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}</script></body></html>');
}); called = true;
})); });
}));
}); });
}); });
})(); })();