2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2018-06-22 01:55:47 -04:00
|
|
|
import {AnimationBuilder, animate, state, style, transition, trigger} from '@angular/animations';
|
2019-08-22 22:16:25 -04:00
|
|
|
import {DOCUMENT, PlatformLocation, isPlatformServer, ɵgetDOM as getDOM} from '@angular/common';
|
2018-05-31 13:35:51 -04:00
|
|
|
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
|
2017-07-18 15:45:47 -04:00
|
|
|
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
2019-03-11 20:20:40 -04:00
|
|
|
import {ApplicationRef, CompilerFactory, Component, HostListener, Inject, Injectable, Input, NgModule, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation, destroyPlatform, getPlatform} from '@angular/core';
|
2018-11-16 10:14:36 -05:00
|
|
|
import {async, inject} from '@angular/core/testing';
|
2019-03-11 20:20:40 -04:00
|
|
|
import {BrowserModule, Title, TransferState, makeStateKey} from '@angular/platform-browser';
|
2017-09-11 03:18:55 -04:00
|
|
|
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformState, ServerModule, ServerTransferStateModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server';
|
2019-03-11 20:20:40 -04:00
|
|
|
import {ivyEnabled, modifiedInIvy} from '@angular/private/testing';
|
2018-05-31 13:35:51 -04:00
|
|
|
import {Observable} from 'rxjs';
|
2018-02-27 17:06:06 -05:00
|
|
|
import {first} from 'rxjs/operators';
|
2016-08-15 16:44:01 -04:00
|
|
|
|
|
|
|
@Component({selector: 'app', template: `Works!`})
|
|
|
|
class MyServerApp {
|
|
|
|
}
|
|
|
|
|
2017-02-10 20:00:27 -05:00
|
|
|
@NgModule({
|
|
|
|
bootstrap: [MyServerApp],
|
|
|
|
declarations: [MyServerApp],
|
|
|
|
imports: [ServerModule],
|
|
|
|
})
|
2016-08-15 16:44:01 -04:00
|
|
|
class ExampleModule {
|
|
|
|
}
|
|
|
|
|
2017-09-04 03:38:42 -04:00
|
|
|
function getTitleRenderHook(doc: any) {
|
|
|
|
return () => {
|
|
|
|
// Set the title as part of the render hook.
|
|
|
|
doc.title = 'RenderHook';
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function exceptionRenderHook() {
|
|
|
|
throw new Error('error');
|
|
|
|
}
|
|
|
|
|
|
|
|
function getMetaRenderHook(doc: any) {
|
|
|
|
return () => {
|
|
|
|
// Add a meta tag before rendering the document.
|
|
|
|
const metaElement = doc.createElement('meta');
|
|
|
|
metaElement.setAttribute('name', 'description');
|
|
|
|
doc.head.appendChild(metaElement);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-03-05 17:30:45 -05:00
|
|
|
function getAsyncTitleRenderHook(doc: any) {
|
|
|
|
return () => {
|
|
|
|
// Async set the title as part of the render hook.
|
|
|
|
return new Promise(resolve => {
|
|
|
|
setTimeout(() => {
|
|
|
|
doc.title = 'AsyncRenderHook';
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function asyncRejectRenderHook() {
|
|
|
|
return () => {
|
|
|
|
return new Promise((_resolve, reject) => { setTimeout(() => { reject('reject'); }); });
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-09-04 03:38:42 -04:00
|
|
|
@NgModule({
|
|
|
|
bootstrap: [MyServerApp],
|
|
|
|
declarations: [MyServerApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
|
|
|
|
providers: [
|
|
|
|
{provide: BEFORE_APP_SERIALIZED, useFactory: getTitleRenderHook, multi: true, deps: [DOCUMENT]},
|
|
|
|
]
|
|
|
|
})
|
|
|
|
class RenderHookModule {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
bootstrap: [MyServerApp],
|
|
|
|
declarations: [MyServerApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
|
|
|
|
providers: [
|
|
|
|
{provide: BEFORE_APP_SERIALIZED, useFactory: getTitleRenderHook, multi: true, deps: [DOCUMENT]},
|
|
|
|
{provide: BEFORE_APP_SERIALIZED, useValue: exceptionRenderHook, multi: true},
|
|
|
|
{provide: BEFORE_APP_SERIALIZED, useFactory: getMetaRenderHook, multi: true, deps: [DOCUMENT]},
|
|
|
|
]
|
|
|
|
})
|
|
|
|
class MultiRenderHookModule {
|
|
|
|
}
|
|
|
|
|
2019-03-05 17:30:45 -05:00
|
|
|
@NgModule({
|
|
|
|
bootstrap: [MyServerApp],
|
|
|
|
declarations: [MyServerApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
|
|
|
|
providers: [
|
|
|
|
{
|
|
|
|
provide: BEFORE_APP_SERIALIZED,
|
|
|
|
useFactory: getAsyncTitleRenderHook,
|
|
|
|
multi: true,
|
|
|
|
deps: [DOCUMENT]
|
|
|
|
},
|
|
|
|
]
|
|
|
|
})
|
|
|
|
class AsyncRenderHookModule {
|
|
|
|
}
|
|
|
|
@NgModule({
|
|
|
|
bootstrap: [MyServerApp],
|
|
|
|
declarations: [MyServerApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
|
|
|
|
providers: [
|
|
|
|
{provide: BEFORE_APP_SERIALIZED, useFactory: getMetaRenderHook, multi: true, deps: [DOCUMENT]},
|
|
|
|
{
|
|
|
|
provide: BEFORE_APP_SERIALIZED,
|
|
|
|
useFactory: getAsyncTitleRenderHook,
|
|
|
|
multi: true,
|
|
|
|
deps: [DOCUMENT]
|
|
|
|
},
|
|
|
|
{provide: BEFORE_APP_SERIALIZED, useFactory: asyncRejectRenderHook, multi: true},
|
|
|
|
]
|
|
|
|
})
|
|
|
|
class AsyncMultiRenderHookModule {
|
|
|
|
}
|
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
@Component({selector: 'app', template: `Works too!`})
|
|
|
|
class MyServerApp2 {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({declarations: [MyServerApp2], imports: [ServerModule], bootstrap: [MyServerApp2]})
|
|
|
|
class ExampleModule2 {
|
|
|
|
}
|
|
|
|
|
2017-03-13 16:22:03 -04:00
|
|
|
@Component({selector: 'app', template: ``})
|
|
|
|
class TitleApp {
|
|
|
|
constructor(private title: Title) {}
|
|
|
|
ngOnInit() { this.title.setTitle('Test App Title'); }
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({declarations: [TitleApp], imports: [ServerModule], bootstrap: [TitleApp]})
|
|
|
|
class TitleAppModule {
|
|
|
|
}
|
|
|
|
|
2019-02-05 00:42:55 -05:00
|
|
|
@Component({selector: 'app', template: '{{text}}<h1 [textContent]="h1"></h1>'})
|
2017-02-12 12:16:23 -05:00
|
|
|
class MyAsyncServerApp {
|
|
|
|
text = '';
|
2017-04-13 14:54:57 -04:00
|
|
|
h1 = '';
|
2017-02-12 12:16:23 -05:00
|
|
|
|
2017-03-14 23:48:01 -04:00
|
|
|
@HostListener('window:scroll')
|
|
|
|
track() { console.error('scroll'); }
|
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
ngOnInit() {
|
2017-04-13 14:54:57 -04:00
|
|
|
Promise.resolve(null).then(() => setTimeout(() => {
|
|
|
|
this.text = 'Works!';
|
|
|
|
this.h1 = 'fine';
|
|
|
|
}, 10));
|
2017-02-12 12:16:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-22 19:06:21 -05:00
|
|
|
@NgModule({
|
|
|
|
declarations: [MyAsyncServerApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'async-server'}), ServerModule],
|
|
|
|
bootstrap: [MyAsyncServerApp]
|
|
|
|
})
|
2017-02-12 12:16:23 -05:00
|
|
|
class AsyncServerModule {
|
|
|
|
}
|
|
|
|
|
2017-03-14 18:40:55 -04:00
|
|
|
@Component({selector: 'app', template: '<svg><use xlink:href="#clear"></use></svg>'})
|
|
|
|
class SVGComponent {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [SVGComponent],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'svg-server'}), ServerModule],
|
|
|
|
bootstrap: [SVGComponent]
|
|
|
|
})
|
|
|
|
class SVGServerModule {
|
|
|
|
}
|
|
|
|
|
2017-03-13 20:31:03 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'app',
|
2018-06-22 01:55:47 -04:00
|
|
|
template: `<div [@myAnimation]="state">{{text}}</div>`,
|
2017-03-13 20:31:03 -04:00
|
|
|
animations: [trigger(
|
|
|
|
'myAnimation',
|
2018-06-22 01:55:47 -04:00
|
|
|
[
|
|
|
|
state('void', style({'opacity': '0'})),
|
|
|
|
state('active', style({
|
|
|
|
'opacity': '1', // simple supported property
|
|
|
|
'font-weight': 'bold', // property with dashed name
|
|
|
|
'transform': 'translate3d(0, 0, 0)', // not natively supported by Domino
|
|
|
|
})),
|
|
|
|
transition('void => *', [animate('0ms')]),
|
|
|
|
], )]
|
2017-03-13 20:31:03 -04:00
|
|
|
})
|
|
|
|
class MyAnimationApp {
|
2018-06-22 01:55:47 -04:00
|
|
|
state = 'active';
|
2017-08-10 21:08:49 -04:00
|
|
|
constructor(private builder: AnimationBuilder) {}
|
|
|
|
|
2017-03-13 20:31:03 -04:00
|
|
|
text = 'Works!';
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [MyAnimationApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'anim-server'}), ServerModule],
|
|
|
|
bootstrap: [MyAnimationApp]
|
|
|
|
})
|
|
|
|
class AnimationServerModule {
|
|
|
|
}
|
|
|
|
|
2018-05-28 03:20:53 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'app',
|
|
|
|
template: `<div>Works!</div>`,
|
|
|
|
styles: ['div {color: blue; } :host { color: red; }']
|
|
|
|
})
|
2017-02-14 14:34:05 -05:00
|
|
|
class MyStylesApp {
|
|
|
|
}
|
|
|
|
|
2017-02-22 19:06:21 -05:00
|
|
|
@NgModule({
|
|
|
|
declarations: [MyStylesApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'example-styles'}), ServerModule],
|
|
|
|
bootstrap: [MyStylesApp]
|
|
|
|
})
|
2017-02-14 14:34:05 -05:00
|
|
|
class ExampleStylesModule {
|
|
|
|
}
|
|
|
|
|
2017-07-18 15:45:47 -04:00
|
|
|
@NgModule({
|
|
|
|
bootstrap: [MyServerApp],
|
|
|
|
declarations: [MyServerApp],
|
|
|
|
imports: [ServerModule, HttpClientModule, HttpClientTestingModule],
|
|
|
|
})
|
2018-03-10 12:14:58 -05:00
|
|
|
export class HttpClientExampleModule {
|
2017-07-18 15:45:47 -04:00
|
|
|
}
|
|
|
|
|
2018-05-31 13:35:51 -04:00
|
|
|
@Injectable()
|
|
|
|
export class MyHttpInterceptor implements HttpInterceptor {
|
|
|
|
constructor(private http: HttpClient) {}
|
|
|
|
|
|
|
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
|
|
|
return next.handle(req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
bootstrap: [MyServerApp],
|
|
|
|
declarations: [MyServerApp],
|
|
|
|
imports: [ServerModule, HttpClientModule, HttpClientTestingModule],
|
|
|
|
providers: [
|
|
|
|
{provide: HTTP_INTERCEPTORS, multi: true, useClass: MyHttpInterceptor},
|
|
|
|
],
|
|
|
|
})
|
|
|
|
export class HttpInterceptorExampleModule {
|
|
|
|
}
|
|
|
|
|
2017-02-13 18:17:40 -05:00
|
|
|
@Component({selector: 'app', template: `<img [src]="'link'">`})
|
|
|
|
class ImageApp {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({declarations: [ImageApp], imports: [ServerModule], bootstrap: [ImageApp]})
|
|
|
|
class ImageExampleModule {
|
|
|
|
}
|
|
|
|
|
2017-03-14 17:09:24 -04:00
|
|
|
@Component({
|
|
|
|
selector: 'app',
|
|
|
|
template: 'Native works',
|
|
|
|
encapsulation: ViewEncapsulation.Native,
|
|
|
|
styles: [':host { color: red; }']
|
|
|
|
})
|
|
|
|
class NativeEncapsulationApp {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [NativeEncapsulationApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'test'}), ServerModule],
|
|
|
|
bootstrap: [NativeEncapsulationApp]
|
|
|
|
})
|
|
|
|
class NativeExampleModule {
|
|
|
|
}
|
|
|
|
|
2017-03-28 16:33:07 -04:00
|
|
|
@Component({selector: 'my-child', template: 'Works!'})
|
|
|
|
class MyChildComponent {
|
2018-06-18 19:38:33 -04:00
|
|
|
// TODO(issue/24571): remove '!'.
|
|
|
|
@Input() public attr !: boolean;
|
2017-03-28 16:33:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'app', template: '<my-child [attr]="false"></my-child>'})
|
|
|
|
class MyHostComponent {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [MyHostComponent, MyChildComponent],
|
|
|
|
bootstrap: [MyHostComponent],
|
|
|
|
imports: [ServerModule, BrowserModule.withServerTransition({appId: 'false-attributes'})]
|
|
|
|
})
|
|
|
|
class FalseAttributesModule {
|
|
|
|
}
|
|
|
|
|
2019-02-05 00:42:55 -05:00
|
|
|
@Component({selector: 'app', template: '<div [innerText]="foo"></div>'})
|
|
|
|
class InnerTextComponent {
|
|
|
|
foo = 'Some text';
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [InnerTextComponent],
|
|
|
|
bootstrap: [InnerTextComponent],
|
|
|
|
imports: [ServerModule, BrowserModule.withServerTransition({appId: 'inner-text'})]
|
|
|
|
})
|
|
|
|
class InnerTextModule {
|
|
|
|
}
|
|
|
|
|
2017-07-19 16:58:23 -04:00
|
|
|
@Component({selector: 'app', template: '<input [name]="name">'})
|
|
|
|
class MyInputComponent {
|
|
|
|
@Input()
|
|
|
|
name = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [MyInputComponent],
|
|
|
|
bootstrap: [MyInputComponent],
|
|
|
|
imports: [ServerModule, BrowserModule.withServerTransition({appId: 'name-attributes'})]
|
|
|
|
})
|
|
|
|
class NameModule {
|
|
|
|
}
|
|
|
|
|
2018-05-24 19:04:04 -04:00
|
|
|
@Component({selector: 'app', template: '<div [innerHTML]="html"></div>'})
|
|
|
|
class HTMLTypesApp {
|
|
|
|
html = '<b>foo</b> bar';
|
|
|
|
constructor(@Inject(DOCUMENT) doc: Document) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [HTMLTypesApp],
|
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'inner-html'}), ServerModule],
|
|
|
|
bootstrap: [HTMLTypesApp]
|
|
|
|
})
|
|
|
|
class HTMLTypesModule {
|
|
|
|
}
|
|
|
|
|
2017-09-11 03:18:55 -04:00
|
|
|
const TEST_KEY = makeStateKey<number>('test');
|
|
|
|
const STRING_KEY = makeStateKey<string>('testString');
|
|
|
|
|
|
|
|
@Component({selector: 'app', template: 'Works!'})
|
|
|
|
class TransferComponent {
|
|
|
|
constructor(private transferStore: TransferState) {}
|
|
|
|
ngOnInit() { this.transferStore.set(TEST_KEY, 10); }
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({selector: 'esc-app', template: 'Works!'})
|
|
|
|
class EscapedComponent {
|
|
|
|
constructor(private transferStore: TransferState) {}
|
|
|
|
ngOnInit() {
|
|
|
|
this.transferStore.set(STRING_KEY, '</script><script>alert(\'Hello&\' + "World");');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
bootstrap: [TransferComponent],
|
|
|
|
declarations: [TransferComponent],
|
|
|
|
imports: [
|
|
|
|
BrowserModule.withServerTransition({appId: 'transfer'}),
|
|
|
|
ServerModule,
|
|
|
|
ServerTransferStateModule,
|
|
|
|
]
|
|
|
|
})
|
|
|
|
class TransferStoreModule {
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
bootstrap: [EscapedComponent],
|
|
|
|
declarations: [EscapedComponent],
|
|
|
|
imports: [
|
|
|
|
BrowserModule.withServerTransition({appId: 'transfer'}),
|
|
|
|
ServerModule,
|
|
|
|
ServerTransferStateModule,
|
|
|
|
]
|
|
|
|
})
|
|
|
|
class EscapedTransferStoreModule {
|
|
|
|
}
|
|
|
|
|
2018-05-31 22:29:02 -04:00
|
|
|
@Component({selector: 'app', template: '<input [hidden]="true"><input [hidden]="false">'})
|
|
|
|
class MyHiddenComponent {
|
|
|
|
@Input()
|
|
|
|
name = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [MyHiddenComponent],
|
|
|
|
bootstrap: [MyHiddenComponent],
|
|
|
|
imports: [ServerModule, BrowserModule.withServerTransition({appId: 'hidden-attributes'})]
|
|
|
|
})
|
|
|
|
class HiddenModule {
|
|
|
|
}
|
|
|
|
|
2017-12-17 18:10:54 -05:00
|
|
|
(function() {
|
2016-06-14 22:49:25 -04:00
|
|
|
if (getDOM().supportsDOMEvents()) return; // NODE only
|
|
|
|
|
2017-02-10 19:48:04 -05:00
|
|
|
describe('platform-server integration', () => {
|
2017-02-12 12:16:23 -05:00
|
|
|
beforeEach(() => {
|
|
|
|
if (getPlatform()) destroyPlatform();
|
|
|
|
});
|
2016-06-14 22:49:25 -04:00
|
|
|
|
|
|
|
it('should bootstrap', async(() => {
|
2017-02-12 12:16:23 -05:00
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
|
|
|
|
platform.bootstrapModule(ExampleModule).then((moduleRef) => {
|
2017-02-22 19:49:46 -05:00
|
|
|
expect(isPlatformServer(moduleRef.injector.get(PLATFORM_ID))).toBe(true);
|
2017-02-12 12:16:23 -05:00
|
|
|
const doc = moduleRef.injector.get(DOCUMENT);
|
2017-03-14 23:48:01 -04:00
|
|
|
|
2019-08-30 00:24:33 -04:00
|
|
|
expect(doc.head).toBe(doc.querySelector('head') !);
|
|
|
|
expect(doc.body).toBe(doc.querySelector('body') !);
|
2017-03-14 23:48:01 -04:00
|
|
|
|
2019-08-23 16:28:33 -04:00
|
|
|
expect(doc.documentElement.textContent).toEqual('Works!');
|
2017-03-14 23:48:01 -04:00
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
platform.destroy();
|
|
|
|
});
|
2016-06-14 22:49:25 -04:00
|
|
|
}));
|
2017-02-10 19:29:30 -05:00
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
it('should allow multiple platform instances', async(() => {
|
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
2017-02-14 19:14:40 -05:00
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
const platform2 = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
2017-02-14 19:14:40 -05:00
|
|
|
|
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
platform.bootstrapModule(ExampleModule).then((moduleRef) => {
|
|
|
|
const doc = moduleRef.injector.get(DOCUMENT);
|
2019-08-23 16:28:33 -04:00
|
|
|
expect(doc.documentElement.textContent).toEqual('Works!');
|
2017-02-12 12:16:23 -05:00
|
|
|
platform.destroy();
|
|
|
|
});
|
2017-02-14 19:14:40 -05:00
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
platform2.bootstrapModule(ExampleModule2).then((moduleRef) => {
|
|
|
|
const doc = moduleRef.injector.get(DOCUMENT);
|
2019-08-23 16:28:33 -04:00
|
|
|
expect(doc.documentElement.textContent).toEqual('Works too!');
|
2017-02-12 12:16:23 -05:00
|
|
|
platform2.destroy();
|
|
|
|
});
|
|
|
|
}));
|
2017-02-14 19:14:40 -05:00
|
|
|
|
2017-03-13 16:22:03 -04:00
|
|
|
it('adds title to the document using Title service', async(() => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue:
|
|
|
|
{document: '<html><head><title></title></head><body><app></app></body></html>'}
|
|
|
|
}]);
|
|
|
|
platform.bootstrapModule(TitleAppModule).then(ref => {
|
|
|
|
const state = ref.injector.get(PlatformState);
|
|
|
|
const doc = ref.injector.get(DOCUMENT);
|
2019-08-30 00:24:33 -04:00
|
|
|
const title = doc.querySelector('title') !;
|
2019-08-23 16:28:33 -04:00
|
|
|
expect(title.textContent).toBe('Test App Title');
|
2017-03-13 16:22:03 -04:00
|
|
|
expect(state.renderToString()).toContain('<title>Test App Title</title>');
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2017-03-14 18:38:24 -04:00
|
|
|
it('should get base href from document', async(() => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue:
|
|
|
|
{document: '<html><head><base href="/"></head><body><app></app></body></html>'}
|
|
|
|
}]);
|
|
|
|
platform.bootstrapModule(ExampleModule).then((moduleRef) => {
|
|
|
|
const location = moduleRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.getBaseHrefFromDOM()).toEqual('/');
|
|
|
|
platform.destroy();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2018-11-16 10:26:21 -05:00
|
|
|
it('adds styles with ng-transition attribute', async(() => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {document: '<html><head></head><body><app></app></body></html>'}
|
|
|
|
}]);
|
|
|
|
platform.bootstrapModule(ExampleStylesModule).then(ref => {
|
|
|
|
const doc = ref.injector.get(DOCUMENT);
|
2019-08-30 15:52:48 -04:00
|
|
|
const head = doc.getElementsByTagName('head')[0];
|
2018-11-16 10:26:21 -05:00
|
|
|
const styles: any[] = head.children as any;
|
|
|
|
expect(styles.length).toBe(1);
|
2019-08-23 16:28:33 -04:00
|
|
|
expect(styles[0].textContent).toContain('color: red');
|
2019-08-30 15:52:48 -04:00
|
|
|
expect(styles[0].getAttribute('ng-transition')).toBe('example-styles');
|
2018-11-16 10:26:21 -05:00
|
|
|
});
|
|
|
|
}));
|
2017-02-14 14:34:05 -05:00
|
|
|
|
2017-02-13 18:17:40 -05:00
|
|
|
it('copies known properties to attributes', async(() => {
|
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
platform.bootstrapModule(ImageExampleModule).then(ref => {
|
|
|
|
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
|
|
|
|
const app = appRef.components[0].location.nativeElement;
|
2019-08-30 15:52:48 -04:00
|
|
|
const img = app.getElementsByTagName('img')[0] as any;
|
2017-08-08 05:17:40 -04:00
|
|
|
expect(img.attributes['src'].value).toEqual('link');
|
2017-02-13 18:17:40 -05:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
describe('PlatformLocation', () => {
|
|
|
|
it('is injectable', async(() => {
|
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
platform.bootstrapModule(ExampleModule).then(appRef => {
|
|
|
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.pathname).toBe('/');
|
|
|
|
platform.destroy();
|
|
|
|
});
|
|
|
|
}));
|
2017-02-14 22:48:48 -05:00
|
|
|
it('is configurable via INITIAL_CONFIG', () => {
|
|
|
|
platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {document: '<app></app>', url: 'http://test.com/deep/path?query#hash'}
|
|
|
|
}])
|
|
|
|
.bootstrapModule(ExampleModule)
|
|
|
|
.then(appRef => {
|
|
|
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.pathname).toBe('/deep/path');
|
|
|
|
expect(location.search).toBe('?query');
|
|
|
|
expect(location.hash).toBe('#hash');
|
|
|
|
});
|
|
|
|
});
|
2019-02-11 13:56:50 -05:00
|
|
|
it('parses component pieces of a URL', () => {
|
|
|
|
platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {document: '<app></app>', url: 'http://test.com:80/deep/path?query#hash'}
|
|
|
|
}])
|
|
|
|
.bootstrapModule(ExampleModule)
|
|
|
|
.then(appRef => {
|
|
|
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.hostname).toBe('test.com');
|
|
|
|
expect(location.protocol).toBe('http:');
|
|
|
|
expect(location.port).toBe('80');
|
|
|
|
expect(location.pathname).toBe('/deep/path');
|
|
|
|
expect(location.search).toBe('?query');
|
|
|
|
expect(location.hash).toBe('#hash');
|
|
|
|
});
|
|
|
|
});
|
2017-02-16 13:18:55 -05:00
|
|
|
it('handles empty search and hash portions of the url', () => {
|
|
|
|
platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {document: '<app></app>', url: 'http://test.com/deep/path'}
|
|
|
|
}])
|
|
|
|
.bootstrapModule(ExampleModule)
|
|
|
|
.then(appRef => {
|
|
|
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.pathname).toBe('/deep/path');
|
|
|
|
expect(location.search).toBe('');
|
|
|
|
expect(location.hash).toBe('');
|
|
|
|
});
|
|
|
|
});
|
2017-02-12 12:16:23 -05:00
|
|
|
it('pushState causes the URL to update', async(() => {
|
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
platform.bootstrapModule(ExampleModule).then(appRef => {
|
|
|
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
|
|
|
location.pushState(null, 'Test', '/foo#bar');
|
|
|
|
expect(location.pathname).toBe('/foo');
|
|
|
|
expect(location.hash).toBe('#bar');
|
|
|
|
platform.destroy();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
it('allows subscription to the hash state', done => {
|
|
|
|
const platform =
|
|
|
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
platform.bootstrapModule(ExampleModule).then(appRef => {
|
|
|
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.pathname).toBe('/');
|
|
|
|
location.onHashChange((e: any) => {
|
|
|
|
expect(e.type).toBe('hashchange');
|
|
|
|
expect(e.oldUrl).toBe('/');
|
|
|
|
expect(e.newUrl).toBe('/foo#bar');
|
|
|
|
platform.destroy();
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
location.pushState(null, 'Test', '/foo#bar');
|
|
|
|
});
|
|
|
|
});
|
2017-02-14 19:14:40 -05:00
|
|
|
});
|
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
describe('render', () => {
|
|
|
|
let doc: string;
|
|
|
|
let called: boolean;
|
|
|
|
let expectedOutput =
|
2019-02-05 00:42:55 -05:00
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 textcontent="fine">fine</h1></app></body></html>';
|
2017-02-14 19:14:40 -05:00
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
beforeEach(() => {
|
|
|
|
// PlatformConfig takes in a parsed document so that it can be cached across requests.
|
|
|
|
doc = '<html><head></head><body><app></app></body></html>';
|
|
|
|
called = false;
|
2018-11-21 10:50:35 -05:00
|
|
|
// We use `window` and `document` directly in some parts of render3 for ivy
|
|
|
|
// Only set it to undefined for legacy
|
|
|
|
if (!ivyEnabled) {
|
|
|
|
(global as any)['window'] = undefined;
|
|
|
|
(global as any)['document'] = undefined;
|
|
|
|
}
|
2017-02-12 12:16:23 -05:00
|
|
|
});
|
|
|
|
afterEach(() => { expect(called).toBe(true); });
|
|
|
|
|
2018-11-19 15:06:41 -05:00
|
|
|
it('using long form should work', async(() => {
|
|
|
|
const platform =
|
|
|
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]);
|
|
|
|
|
|
|
|
platform.bootstrapModule(AsyncServerModule)
|
|
|
|
.then((moduleRef) => {
|
|
|
|
const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
|
|
|
|
return applicationRef.isStable.pipe(first((isStable: boolean) => isStable))
|
|
|
|
.toPromise();
|
|
|
|
})
|
|
|
|
.then((b) => {
|
|
|
|
expect(platform.injector.get(PlatformState).renderToString()).toBe(expectedOutput);
|
|
|
|
platform.destroy();
|
2017-02-12 12:16:23 -05:00
|
|
|
called = true;
|
|
|
|
});
|
2018-11-19 15:06:41 -05:00
|
|
|
}));
|
|
|
|
|
|
|
|
it('using renderModule should work', async(() => {
|
|
|
|
renderModule(AsyncServerModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(expectedOutput);
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2017-02-12 12:16:23 -05:00
|
|
|
|
2019-02-05 00:42:55 -05:00
|
|
|
modifiedInIvy('Will not support binding to innerText in Ivy since domino does not')
|
|
|
|
.it('should support binding to innerText', async(() => {
|
|
|
|
renderModule(InnerTextModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER"><div innertext="Some text">Some text</div></app></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2017-02-12 12:16:23 -05:00
|
|
|
it('using renderModuleFactory should work',
|
|
|
|
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
|
|
|
|
const compilerFactory: CompilerFactory =
|
2019-10-01 19:44:50 -04:00
|
|
|
defaultPlatform.injector.get(CompilerFactory, null) !;
|
2017-02-12 12:16:23 -05:00
|
|
|
const moduleFactory =
|
|
|
|
compilerFactory.createCompiler().compileModuleSync(AsyncServerModule);
|
|
|
|
renderModuleFactory(moduleFactory, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(expectedOutput);
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
})));
|
2017-03-14 18:40:55 -04:00
|
|
|
|
2019-01-22 17:21:53 -05:00
|
|
|
it('works with SVG elements', async(() => {
|
|
|
|
renderModule(SVGServerModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
|
|
|
|
'<svg><use xlink:href="#clear"></use></svg></app></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2018-12-13 00:21:52 -05:00
|
|
|
it('works with animation', async(() => {
|
|
|
|
renderModule(AnimationServerModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toContain('Works!');
|
|
|
|
expect(output).toContain('ng-trigger-myAnimation');
|
|
|
|
expect(output).toContain('opacity:1;');
|
|
|
|
expect(output).toContain('transform:translate3d(0 , 0 , 0);');
|
|
|
|
expect(output).toContain('font-weight:bold;');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2017-03-14 17:09:24 -04:00
|
|
|
|
|
|
|
it('should handle ViewEncapsulation.Native', async(() => {
|
|
|
|
renderModule(NativeExampleModule, {document: doc}).then(output => {
|
|
|
|
expect(output).not.toBe('');
|
|
|
|
expect(output).toContain('color: red');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2017-03-28 16:33:07 -04:00
|
|
|
|
2018-05-28 03:20:53 -04:00
|
|
|
|
2018-11-19 15:06:41 -05:00
|
|
|
it('sets a prefix for the _nghost and _ngcontent attributes', async(() => {
|
|
|
|
renderModule(ExampleStylesModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toMatch(
|
|
|
|
/<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;
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
fix(ivy): TestBed should not clobber compilation of global-scope modules (#28033)
When an @NgModule decorator executes, the module is added to a queue in
render3/jit/module.ts. Reading an ngComponentDef property causes this queue
to be flushed, ensuring that the component gets the correct module scope
applied.
In before_each.ts, a global beforeEach is added to all Angular tests which
calls TestBed.resetTestingModule() prior to running each test. This in turn
clears the module compilation queue (which is correct behavior, as modules
declared within the test should not leak outside of it via the queue).
So far this is okay. But before the first test runs, the module compilation
queue is full of modules declared in global scope. No definitions have been
read, so no flushes of the queue have been triggered. The global beforeEach
triggers a reset of the queue, aborting all of the in-progress global
compilation, breaking those classes when they're later used in tests.
This commit adds logic to TestBedRender3 to respect the state of the module
queue before the TestBed is first initialized or reset. The queue is flushed
prior to such an operation to ensure global compilation is allowed to finish
properly.
With this fix, a platform-server test now passes (previously the <my-child>
element was not detected as a component, because the encompassing module
never finished compilation.
FW-887 #resolve
PR Close #28033
2019-01-09 19:24:36 -05:00
|
|
|
it('should handle false values on attributes', async(() => {
|
|
|
|
renderModule(FalseAttributesModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
|
|
|
|
'<my-child ng-reflect-attr="false">Works!</my-child></app></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2018-11-21 07:40:27 -05:00
|
|
|
it('should handle element property "name"', async(() => {
|
|
|
|
renderModule(NameModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
|
|
|
|
'<input name=""></app></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2018-11-21 10:50:35 -05:00
|
|
|
it('should work with sanitizer to handle "innerHTML"', async(() => {
|
|
|
|
// Clear out any global states. These should be set when platform-server
|
|
|
|
// is initialized.
|
|
|
|
(global as any).Node = undefined;
|
|
|
|
(global as any).Document = undefined;
|
|
|
|
renderModule(HTMLTypesModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
|
|
|
|
'<div><b>foo</b> bar</div></app></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2018-11-21 07:40:27 -05:00
|
|
|
it('should handle element property "hidden"', async(() => {
|
|
|
|
renderModule(HiddenModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
|
|
|
|
'<input hidden=""><input></app></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2018-11-19 15:06:41 -05:00
|
|
|
it('should call render hook', async(() => {
|
|
|
|
renderModule(RenderHookModule, {document: doc}).then(output => {
|
|
|
|
// title should be added by the render hook.
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head><title>RenderHook</title></head><body>' +
|
|
|
|
'<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2018-11-19 15:06:41 -05:00
|
|
|
it('should call multiple render hooks', async(() => {
|
|
|
|
const consoleSpy = spyOn(console, 'warn');
|
|
|
|
renderModule(MultiRenderHookModule, {document: doc}).then(output => {
|
|
|
|
// title should be added by the render hook.
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head><title>RenderHook</title><meta name="description"></head>' +
|
|
|
|
'<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
|
|
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2019-03-05 17:30:45 -05:00
|
|
|
|
|
|
|
it('should call async render hooks', async(() => {
|
|
|
|
renderModule(AsyncRenderHookModule, {document: doc}).then(output => {
|
|
|
|
// title should be added by the render hook.
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head><title>AsyncRenderHook</title></head><body>' +
|
|
|
|
'<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('should call multiple async and sync render hooks', async(() => {
|
|
|
|
const consoleSpy = spyOn(console, 'warn');
|
|
|
|
renderModule(AsyncMultiRenderHookModule, {document: doc}).then(output => {
|
|
|
|
// title should be added by the render hook.
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head><meta name="description"><title>AsyncRenderHook</title></head>' +
|
|
|
|
'<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
|
|
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2017-02-12 12:16:23 -05:00
|
|
|
});
|
2017-02-10 20:00:27 -05:00
|
|
|
|
2017-07-18 15:45:47 -04:00
|
|
|
describe('HttpClient', () => {
|
|
|
|
it('can inject HttpClient', async(() => {
|
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
2018-03-10 12:14:58 -05:00
|
|
|
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
|
2017-07-18 15:45:47 -04:00
|
|
|
expect(ref.injector.get(HttpClient) instanceof HttpClient).toBeTruthy();
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2017-07-18 15:45:47 -04:00
|
|
|
it('can make HttpClient requests', async(() => {
|
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
2018-03-10 12:14:58 -05:00
|
|
|
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
|
2017-07-18 15:45:47 -04:00
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
2017-11-15 11:43:35 -05:00
|
|
|
ref.injector.get<NgZone>(NgZone).run(() => {
|
2019-08-19 18:05:29 -04:00
|
|
|
http.get<string>('http://localhost/testing').subscribe((body: string) => {
|
2017-07-18 15:45:47 -04:00
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2017-07-18 15:45:47 -04:00
|
|
|
it('requests are macrotasks', async(() => {
|
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
2018-03-10 12:14:58 -05:00
|
|
|
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
|
2017-07-18 15:45:47 -04:00
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
2019-08-19 18:05:29 -04:00
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('http://localhost/testing').subscribe((body: string) => {
|
2017-07-18 15:45:47 -04:00
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
2017-11-15 11:43:35 -05:00
|
|
|
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeTruthy();
|
2017-07-18 15:45:47 -04:00
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
2017-11-15 11:43:35 -05:00
|
|
|
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeFalsy();
|
2017-07-18 15:45:47 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}));
|
2018-11-16 10:14:36 -05:00
|
|
|
|
2018-05-31 13:35:51 -04:00
|
|
|
it('can use HttpInterceptor that injects HttpClient', () => {
|
|
|
|
const platform =
|
|
|
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
platform.bootstrapModule(HttpInterceptorExampleModule).then(ref => {
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
2019-08-19 18:05:29 -04:00
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('http://localhost/testing').subscribe((body: string) => {
|
2018-05-31 13:35:51 -04:00
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2017-07-18 15:45:47 -04:00
|
|
|
});
|
2017-09-11 03:18:55 -04:00
|
|
|
|
|
|
|
describe('ServerTransferStoreModule', () => {
|
|
|
|
let called = false;
|
|
|
|
const defaultExpectedOutput =
|
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!</app><script id="transfer-state" type="application/json">{&q;test&q;:10}</script></body></html>';
|
|
|
|
|
|
|
|
beforeEach(() => { called = false; });
|
|
|
|
afterEach(() => { expect(called).toBe(true); });
|
|
|
|
|
2018-11-19 15:06:41 -05:00
|
|
|
it('adds transfer script tag when using renderModule', async(() => {
|
|
|
|
renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => {
|
|
|
|
expect(output).toBe(defaultExpectedOutput);
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2017-09-11 03:18:55 -04:00
|
|
|
|
|
|
|
it('adds transfer script tag when using renderModuleFactory',
|
|
|
|
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
|
|
|
|
const compilerFactory: CompilerFactory =
|
2019-10-01 19:44:50 -04:00
|
|
|
defaultPlatform.injector.get(CompilerFactory, null) !;
|
2017-09-11 03:18:55 -04:00
|
|
|
const moduleFactory =
|
|
|
|
compilerFactory.createCompiler().compileModuleSync(TransferStoreModule);
|
|
|
|
renderModuleFactory(moduleFactory, {document: '<app></app>'}).then(output => {
|
|
|
|
expect(output).toBe(defaultExpectedOutput);
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
})));
|
|
|
|
|
2018-11-19 15:06:41 -05:00
|
|
|
it('cannot break out of <script> tag in serialized output', async(() => {
|
|
|
|
renderModule(EscapedTransferStoreModule, {
|
|
|
|
document: '<esc-app></esc-app>'
|
|
|
|
}).then(output => {
|
|
|
|
expect(output).toBe(
|
|
|
|
'<html><head></head><body><esc-app ng-version="0.0.0-PLACEHOLDER">Works!</esc-app>' +
|
|
|
|
'<script id="transfer-state" type="application/json">' +
|
|
|
|
'{&q;testString&q;:&q;&l;/script&g;&l;script&g;' +
|
|
|
|
'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}</script></body></html>');
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2017-09-11 03:18:55 -04:00
|
|
|
});
|
2017-02-14 19:14:40 -05:00
|
|
|
});
|
2017-12-16 17:42:55 -05:00
|
|
|
})();
|