2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2016-06-23 12:47:54 -04:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
import {animate, AnimationBuilder, state, style, transition, trigger} from '@angular/animations';
|
|
|
|
import {DOCUMENT, isPlatformServer, PlatformLocation, ɵ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';
|
2020-04-13 19:40:21 -04:00
|
|
|
import {ApplicationRef, CompilerFactory, Component, destroyPlatform, getPlatform, HostListener, Inject, Injectable, Input, NgModule, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation} from '@angular/core';
|
2020-07-31 15:43:18 -04:00
|
|
|
import {inject, waitForAsync} from '@angular/core/testing';
|
2020-04-13 19:40:21 -04:00
|
|
|
import {BrowserModule, makeStateKey, Title, TransferState} from '@angular/platform-browser';
|
|
|
|
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, platformDynamicServer, PlatformState, renderModule, renderModuleFactory, ServerModule, ServerTransferStateModule} 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.
|
2020-11-04 19:58:29 -05:00
|
|
|
return new Promise<void>(resolve => {
|
2019-03-05 17:30:45 -05:00
|
|
|
setTimeout(() => {
|
|
|
|
doc.title = 'AsyncRenderHook';
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function asyncRejectRenderHook() {
|
|
|
|
return () => {
|
2020-11-04 19:58:29 -05:00
|
|
|
return new Promise<void>((_resolve, reject) => {
|
2020-04-13 19:40:21 -04:00
|
|
|
setTimeout(() => {
|
|
|
|
reject('reject');
|
|
|
|
});
|
|
|
|
});
|
2019-03-05 17:30:45 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
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) {}
|
2020-04-13 19:40:21 -04:00
|
|
|
ngOnInit() {
|
|
|
|
this.title.setTitle('Test App Title');
|
|
|
|
}
|
2017-03-13 16:22:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@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')
|
2020-04-13 19:40:21 -04:00
|
|
|
track() {
|
|
|
|
console.error('scroll');
|
|
|
|
}
|
2017-03-14 23:48:01 -04:00
|
|
|
|
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')]),
|
2020-04-13 19:40:21 -04:00
|
|
|
],
|
|
|
|
)]
|
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',
|
2020-10-08 10:59:29 -04:00
|
|
|
template: 'Shadow DOM works',
|
|
|
|
encapsulation: ViewEncapsulation.ShadowDom,
|
2017-03-14 17:09:24 -04:00
|
|
|
styles: [':host { color: red; }']
|
|
|
|
})
|
2020-10-08 10:59:29 -04:00
|
|
|
class ShadowDomEncapsulationApp {
|
2017-03-14 17:09:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
2020-10-08 10:59:29 -04:00
|
|
|
declarations: [ShadowDomEncapsulationApp],
|
2017-03-14 17:09:24 -04:00
|
|
|
imports: [BrowserModule.withServerTransition({appId: 'test'}), ServerModule],
|
2020-10-08 10:59:29 -04:00
|
|
|
bootstrap: [ShadowDomEncapsulationApp]
|
2017-03-14 17:09:24 -04:00
|
|
|
})
|
2020-10-08 10:59:29 -04:00
|
|
|
class ShadowDomExampleModule {
|
2017-03-14 17:09:24 -04:00
|
|
|
}
|
|
|
|
|
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 '!'.
|
2020-04-13 19:40:21 -04:00
|
|
|
@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 {
|
2020-04-13 19:40:21 -04:00
|
|
|
@Input() name = '';
|
2017-07-19 16:58:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@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) {}
|
2020-04-13 19:40:21 -04:00
|
|
|
ngOnInit() {
|
|
|
|
this.transferStore.set(TEST_KEY, 10);
|
|
|
|
}
|
2017-09-11 03:18:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@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 {
|
2020-04-13 19:40:21 -04:00
|
|
|
@Input() name = '';
|
2018-05-31 22:29:02 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@NgModule({
|
|
|
|
declarations: [MyHiddenComponent],
|
|
|
|
bootstrap: [MyHiddenComponent],
|
|
|
|
imports: [ServerModule, BrowserModule.withServerTransition({appId: 'hidden-attributes'})]
|
|
|
|
})
|
|
|
|
class HiddenModule {
|
|
|
|
}
|
|
|
|
|
2017-12-17 18:10:54 -05:00
|
|
|
(function() {
|
2020-04-13 19:40:21 -04:00
|
|
|
if (getDOM().supportsDOMEvents()) return; // NODE only
|
2016-06-14 22:49:25 -04:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
describe('platform-server integration', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
if (getPlatform()) destroyPlatform();
|
|
|
|
});
|
2016-06-14 22:49:25 -04:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should bootstrap', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
const platform =
|
|
|
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
|
|
|
|
platform.bootstrapModule(ExampleModule).then((moduleRef) => {
|
|
|
|
expect(isPlatformServer(moduleRef.injector.get(PLATFORM_ID))).toBe(true);
|
|
|
|
const doc = moduleRef.injector.get(DOCUMENT);
|
|
|
|
|
|
|
|
expect(doc.head).toBe(doc.querySelector('head')!);
|
|
|
|
expect(doc.body).toBe(doc.querySelector('body')!);
|
|
|
|
|
|
|
|
expect(doc.documentElement.textContent).toEqual('Works!');
|
|
|
|
|
|
|
|
platform.destroy();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should allow multiple platform instances', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
const platform =
|
|
|
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
|
|
|
|
const platform2 =
|
|
|
|
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
|
|
|
|
|
|
|
|
platform.bootstrapModule(ExampleModule).then((moduleRef) => {
|
|
|
|
const doc = moduleRef.injector.get(DOCUMENT);
|
|
|
|
expect(doc.documentElement.textContent).toEqual('Works!');
|
|
|
|
platform.destroy();
|
|
|
|
});
|
|
|
|
|
|
|
|
platform2.bootstrapModule(ExampleModule2).then((moduleRef) => {
|
|
|
|
const doc = moduleRef.injector.get(DOCUMENT);
|
|
|
|
expect(doc.documentElement.textContent).toEqual('Works too!');
|
|
|
|
platform2.destroy();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('adds title to the document using Title service', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
|
|
|
const title = doc.querySelector('title')!;
|
|
|
|
expect(title.textContent).toBe('Test App Title');
|
|
|
|
expect(state.renderToString()).toContain('<title>Test App Title</title>');
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should get base href from document', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('adds styles with ng-transition attribute', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
|
|
|
const head = doc.getElementsByTagName('head')[0];
|
|
|
|
const styles: any[] = head.children as any;
|
|
|
|
expect(styles.length).toBe(1);
|
|
|
|
expect(styles[0].textContent).toContain('color: red');
|
|
|
|
expect(styles[0].getAttribute('ng-transition')).toBe('example-styles');
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('copies known properties to attributes', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
|
|
|
const img = app.getElementsByTagName('img')[0] as any;
|
|
|
|
expect(img.attributes['src'].value).toEqual('link');
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
describe('PlatformLocation', () => {
|
2020-07-31 15:43:18 -04:00
|
|
|
it('is injectable', waitForAsync(() => {
|
2017-02-12 12:16:23 -05:00
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
2020-04-13 19:40:21 -04:00
|
|
|
platform.bootstrapModule(ExampleModule).then(appRef => {
|
|
|
|
const location: PlatformLocation = appRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.pathname).toBe('/');
|
|
|
|
platform.destroy();
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
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');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
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');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
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('');
|
|
|
|
});
|
|
|
|
});
|
2020-07-31 15:43:18 -04:00
|
|
|
it('pushState causes the URL to update', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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-12 12:16:23 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
describe('render', () => {
|
|
|
|
let doc: string;
|
|
|
|
let called: boolean;
|
|
|
|
let expectedOutput =
|
|
|
|
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 textcontent="fine">fine</h1></app></body></html>';
|
2017-03-14 23:48:01 -04:00
|
|
|
|
2020-04-13 19:40:21 -04: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;
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
afterEach(() => {
|
|
|
|
expect(called).toBe(true);
|
|
|
|
});
|
2017-03-14 23:48:01 -04:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('using long form should work', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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();
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
2017-03-14 23:48:01 -04:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('using renderModule should work', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
renderModule(AsyncServerModule, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(expectedOutput);
|
|
|
|
called = true;
|
2017-02-12 12:16:23 -05:00
|
|
|
});
|
2016-06-14 22:49:25 -04:00
|
|
|
}));
|
2017-02-10 19:29:30 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
modifiedInIvy('Will not support binding to innerText in Ivy since domino does not')
|
2020-07-31 15:43:18 -04:00
|
|
|
.it('should support binding to innerText', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('using renderModuleFactory should work',
|
2020-07-31 15:43:18 -04:00
|
|
|
waitForAsync(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
|
2020-04-13 19:40:21 -04:00
|
|
|
const compilerFactory: CompilerFactory =
|
|
|
|
defaultPlatform.injector.get(CompilerFactory, null)!;
|
|
|
|
const moduleFactory =
|
|
|
|
compilerFactory.createCompiler().compileModuleSync(AsyncServerModule);
|
|
|
|
renderModuleFactory(moduleFactory, {document: doc}).then(output => {
|
|
|
|
expect(output).toBe(expectedOutput);
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
})));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('works with SVG elements', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
}));
|
2017-02-14 19:14:40 -05:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('works with animation', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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-02-14 19:14:40 -05:00
|
|
|
|
2020-10-08 10:59:29 -04:00
|
|
|
it('should handle ViewEncapsulation.ShadowDom', waitForAsync(() => {
|
|
|
|
renderModule(ShadowDomExampleModule, {document: doc}).then(output => {
|
2020-04-13 19:40:21 -04:00
|
|
|
expect(output).not.toBe('');
|
|
|
|
expect(output).toContain('color: red');
|
|
|
|
called = true;
|
2017-02-12 12:16:23 -05:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
}));
|
|
|
|
|
2017-02-14 19:14:40 -05:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('sets a prefix for the _nghost and _ngcontent attributes', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
2017-02-12 12:16:23 -05:00
|
|
|
});
|
|
|
|
}));
|
2017-02-14 19:14:40 -05:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should handle false values on attributes', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
2017-03-13 16:22:03 -04:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should handle element property "name"', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
2017-03-14 18:38:24 -04:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should work with sanitizer to handle "innerHTML"', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
// 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:26:21 -05:00
|
|
|
});
|
|
|
|
}));
|
2017-02-14 14:34:05 -05:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should handle element property "hidden"', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
2017-02-13 18:17:40 -05:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should call render hook', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
}));
|
2017-02-14 19:14:40 -05:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should call multiple render hooks', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
}));
|
2017-03-14 17:09:24 -04:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should call async render hooks', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
}));
|
2017-03-28 16:33:07 -04:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('should call multiple async and sync render hooks', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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;
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
});
|
2018-05-28 03:20:53 -04:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
describe('HttpClient', () => {
|
2020-07-31 15:43:18 -04:00
|
|
|
it('can inject HttpClient', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
|
|
|
|
expect(ref.injector.get(HttpClient) instanceof HttpClient).toBeTruthy();
|
|
|
|
});
|
|
|
|
}));
|
2017-02-10 20:00:27 -05:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('can make HttpClient requests', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get<NgZone>(NgZone).run(() => {
|
|
|
|
http.get<string>('http://localhost/testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
2017-07-18 15:45:47 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
2017-07-18 15:45:47 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2020-06-11 12:42:31 -04:00
|
|
|
describe('relative requests', () => {
|
2020-10-19 18:23:37 -04:00
|
|
|
it('will throw if "useAbsoluteUrl" is true but "baseUrl" is not provided', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
|
|
|
},
|
|
|
|
}]);
|
|
|
|
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
expect(() => appRef.injector.get(PlatformLocation))
|
|
|
|
.toThrowError(/"PlatformConfig\.baseUrl" must be set if "useAbsoluteUrl" is true/);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('will resolve absolute url using "baseUrl"', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
|
|
|
baseUrl: 'https://angular.io:8080',
|
|
|
|
},
|
|
|
|
}]);
|
|
|
|
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const location = appRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.protocol).toBe('https:');
|
|
|
|
expect(location.hostname).toBe('angular.io');
|
|
|
|
expect(location.port).toBe('8080');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('"baseUrl" has no effect if "useAbsoluteUrl" is not enabled', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost',
|
|
|
|
baseUrl: 'https://angular.io:8080',
|
|
|
|
},
|
|
|
|
}]);
|
|
|
|
const appRef = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const location = appRef.injector.get(PlatformLocation);
|
|
|
|
expect(location.protocol).toBe('http:');
|
|
|
|
expect(location.hostname).toBe('localhost');
|
|
|
|
expect(location.port).toBe('');
|
|
|
|
});
|
|
|
|
|
2020-06-11 12:42:31 -04:00
|
|
|
it('correctly maps to absolute URL request with base config', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
2020-10-19 18:23:37 -04:00
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost',
|
|
|
|
baseUrl: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
|
|
|
}
|
2020-06-11 12:42:31 -04:00
|
|
|
}]);
|
|
|
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('/testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
2020-05-12 11:05:18 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-06-11 12:42:31 -04:00
|
|
|
it('uses default URL behavior when not enabled', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {document: '<app></app>', url: 'http://localhost', useAbsoluteUrl: false}
|
|
|
|
}]);
|
|
|
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('/testing').subscribe(() => {}, (body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('error');
|
|
|
|
});
|
|
|
|
mock.expectOne('/testing').flush('error');
|
2020-05-12 11:05:18 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-06-11 12:42:31 -04:00
|
|
|
it('correctly maps to absolute URL request with port', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
2020-10-19 18:23:37 -04:00
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost:5000',
|
|
|
|
baseUrl: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
|
|
|
}
|
2020-06-11 12:42:31 -04:00
|
|
|
}]);
|
|
|
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('/testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://localhost:5000/testing').flush('success!');
|
2020-05-12 11:05:18 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-06-11 12:42:31 -04:00
|
|
|
it('correctly maps to absolute URL request with two slashes', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
2020-10-19 18:23:37 -04:00
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost/',
|
|
|
|
baseUrl: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
|
|
|
}
|
2020-06-11 12:42:31 -04:00
|
|
|
}]);
|
|
|
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('/testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
2020-05-28 22:33:15 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-06-11 12:42:31 -04:00
|
|
|
it('correctly maps to absolute URL request with no slashes', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
2020-10-19 18:23:37 -04:00
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost',
|
|
|
|
baseUrl: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
|
|
|
}
|
2020-06-11 12:42:31 -04:00
|
|
|
}]);
|
|
|
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
2020-05-28 22:33:15 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-06-11 12:42:31 -04:00
|
|
|
it('correctly maps to absolute URL request with longer url and no slashes', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
2020-10-19 18:23:37 -04:00
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost/path/page',
|
|
|
|
baseUrl: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
|
|
|
}
|
2020-06-11 12:42:31 -04:00
|
|
|
}]);
|
|
|
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://localhost/path/testing').flush('success!');
|
2020-05-28 22:33:15 -04:00
|
|
|
});
|
|
|
|
});
|
2020-06-11 12:42:31 -04:00
|
|
|
|
|
|
|
it('correctly maps to absolute URL request with longer url and slashes', async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
2020-10-19 18:23:37 -04:00
|
|
|
useValue: {
|
|
|
|
document: '<app></app>',
|
|
|
|
url: 'http://localhost/path/page',
|
|
|
|
baseUrl: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
|
|
|
}
|
2020-06-11 12:42:31 -04:00
|
|
|
}]);
|
|
|
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('/testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('correctly maps to absolute URL request with longer url, slashes, and base href',
|
|
|
|
async () => {
|
|
|
|
const platform = platformDynamicServer([{
|
|
|
|
provide: INITIAL_CONFIG,
|
|
|
|
useValue: {
|
|
|
|
document: '<base href="http://other"><app></app>',
|
|
|
|
url: 'http://localhost/path/page',
|
2020-10-19 18:23:37 -04:00
|
|
|
baseUrl: 'http://localhost',
|
|
|
|
useAbsoluteUrl: true,
|
2020-06-11 12:42:31 -04:00
|
|
|
}
|
|
|
|
}]);
|
|
|
|
const ref = await platform.bootstrapModule(HttpClientExampleModule);
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('/testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
|
|
|
});
|
|
|
|
mock.expectOne('http://other/testing').flush('success!');
|
|
|
|
});
|
|
|
|
});
|
2020-05-28 22:33:15 -04:00
|
|
|
});
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('requests are macrotasks', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
const platform = platformDynamicServer(
|
|
|
|
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
|
|
|
platform.bootstrapModule(HttpClientExampleModule).then(ref => {
|
|
|
|
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
|
|
|
const http = ref.injector.get(HttpClient);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('http://localhost/testing').subscribe((body: string) => {
|
|
|
|
expect(body).toEqual('success!');
|
2017-07-18 15:45:47 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeTruthy();
|
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
|
|
|
expect(ref.injector.get<NgZone>(NgZone).hasPendingMacrotasks).toBeFalsy();
|
2017-07-18 15:45:47 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -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);
|
|
|
|
ref.injector.get(NgZone).run(() => {
|
|
|
|
http.get<string>('http://localhost/testing').subscribe((body: string) => {
|
|
|
|
NgZone.assertInAngularZone();
|
|
|
|
expect(body).toEqual('success!');
|
2018-05-31 13:35:51 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
mock.expectOne('http://localhost/testing').flush('success!');
|
2018-05-31 13:35:51 -04:00
|
|
|
});
|
|
|
|
});
|
2017-07-18 15:45:47 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
});
|
2017-09-11 03:18:55 -04:00
|
|
|
|
2020-04-13 19:40:21 -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>';
|
2017-09-11 03:18:55 -04:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
beforeEach(() => {
|
|
|
|
called = false;
|
|
|
|
});
|
|
|
|
afterEach(() => {
|
|
|
|
expect(called).toBe(true);
|
2017-09-11 03:18:55 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('adds transfer script tag when using renderModule', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => {
|
|
|
|
expect(output).toBe(defaultExpectedOutput);
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
|
|
|
it('adds transfer script tag when using renderModuleFactory',
|
2020-07-31 15:43:18 -04:00
|
|
|
waitForAsync(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
|
2020-04-13 19:40:21 -04:00
|
|
|
const compilerFactory: CompilerFactory =
|
|
|
|
defaultPlatform.injector.get(CompilerFactory, null)!;
|
|
|
|
const moduleFactory =
|
|
|
|
compilerFactory.createCompiler().compileModuleSync(TransferStoreModule);
|
|
|
|
renderModuleFactory(moduleFactory, {document: '<app></app>'}).then(output => {
|
|
|
|
expect(output).toBe(defaultExpectedOutput);
|
|
|
|
called = true;
|
|
|
|
});
|
|
|
|
})));
|
|
|
|
|
2020-07-31 15:43:18 -04:00
|
|
|
it('cannot break out of <script> tag in serialized output', waitForAsync(() => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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-02-14 19:14:40 -05:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
});
|
2017-12-16 17:42:55 -05:00
|
|
|
})();
|