Skip to content

Commit

Permalink
More unit test coverage around browser emulation code
Browse files Browse the repository at this point in the history
  • Loading branch information
clbond committed Apr 22, 2017
1 parent 99c8e1e commit 454c8e3
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 157 deletions.
6 changes: 3 additions & 3 deletions source/application/builder/application-base.ts
Expand Up @@ -35,14 +35,14 @@ export abstract class ApplicationBase<V, M> implements Application<V> {
return this.renderToStream(this.render);
}

renderUri(uri: string, variant?: V): Promise<Snapshot<V>> {
async renderUri(uri: string, variant?: V): Promise<Snapshot<V>> {
uri = resolveToAbsoluteUri(uri);

const transition = composeTransitions(this.render.variants, variant);

const vop: RenderVariantOperation<V> = {scope: this.render, uri, variant, transition};

return this.renderVariant(vop);
return await this.renderVariant(vop);
}

async discoverRoutes(): Promise<Array<Route>> {
Expand Down Expand Up @@ -89,7 +89,7 @@ export abstract class ApplicationBase<V, M> implements Application<V> {
}
};

return forkZone(templateDocument, uri, execute);
return await forkZone(templateDocument, uri, execute);
}
}

Expand Down
46 changes: 14 additions & 32 deletions source/application/builder/builder-base.ts
Expand Up @@ -26,56 +26,38 @@ export abstract class ApplicationBuilderBase<V> implements ApplicationBuilder<V>
}

bootstrap(bootstrapper?: ApplicationBootstrapper) {
if (bootstrapper !== undefined) {
if (this.operation.bootstrappers == null) {
this.operation.bootstrappers = [];
}
this.operation.bootstrappers.push(bootstrapper);
if (this.operation.bootstrappers == null) {
this.operation.bootstrappers = [];
}
return this.operation.bootstrappers || [];
this.operation.bootstrappers.push(bootstrapper);
}

postprocess(transform?: Postprocessor) {
if (transform !== undefined) {
if (this.operation.postprocessors == null) {
this.operation.postprocessors = [];
}
this.operation.postprocessors.push(transform);
if (this.operation.postprocessors == null) {
this.operation.postprocessors = [];
}
return this.operation.postprocessors || [];
this.operation.postprocessors.push(transform);
}

variants(map: VariantsMap) {
if (map !== undefined) {
this.operation.variants = map;
}
return this.operation.variants;
this.operation.variants = map;
}

routes(routes?: Array<Route>) {
if (routes !== undefined) {
this.operation.routes = routes;
}
return this.operation.routes || [];
this.operation.routes = routes;
}

preboot(preboot?: PrebootConfiguration | boolean) {
if (preboot !== undefined) {
if (typeof preboot === 'boolean') {
this.operation.preboot = preboot ? {} as PrebootQueryable : null;
}
else {
this.operation.preboot = preboot as PrebootQueryable;
}
if (typeof preboot === 'boolean') {
this.operation.preboot = preboot ? {} as PrebootQueryable : null;
}
else {
this.operation.preboot = preboot as PrebootQueryable;
}
return this.operation.preboot as PrebootConfiguration;
}

stateReader<R>(stateReader?: ApplicationStateReader<R>) {
if (stateReader !== undefined) {
this.operation.stateReader = stateReader;
}
return this.operation.stateReader as any;
this.operation.stateReader = stateReader;
}

stabilizeTimeout(milliseconds?: number): number | null {
Expand Down
16 changes: 11 additions & 5 deletions source/application/builder/builder.ts
Expand Up @@ -28,7 +28,7 @@ export interface ApplicationBuilder<V> {
// Bootstrap methods should be specialized things that you only have to bootstrap on the
// server. Generic bootstrap or initialization code belongs in the application code, not
// in the server.
bootstrap(bootstrapper: ApplicationBootstrapper): Array<ApplicationBootstrapper>;
bootstrap(bootstrapper: ApplicationBootstrapper): void;

// Define the variants of this application. For applications that wish to render different
// variants such as languages or anonymous vs authenticated, you can define those variants
Expand All @@ -38,22 +38,28 @@ export interface ApplicationBuilder<V> {
// Provide an optional array of routes that you wish to pre-render. If you do not specify
// these, angular-ssr will query the router for all routes defined in the application, and
// then filter out routes which accept parameters (like /foo/:bar)
routes(routes?: Array<Route>): Array<Route>;
routes(routes?: Array<Route>): void;

// Provide an optional state reader function which can query application services or ngrx
// and return that state to the client, so that it will be available in a global variable
// called bootstrapApplicationState. This is how you do state transfer in angular-ssr.
stateReader<R>(stateReader?: ApplicationStateReader<R>): ApplicationStateReader<R>;
stateReader<R>(stateReader?: ApplicationStateReader<R>): void;

// Apply optional postprocessing of rendered documents. For example, perhaps your index.html
// has some kind of placeholder which you wish to replace with some code or text. These
// postprocessing functions will be called in order and each successive transform will receive
// as its argument the result of the prior postprocessor.
postprocess(transform?: Postprocessor): Array<Postprocessor>;
postprocess(transform?: Postprocessor): void;

// Enable preboot integration and specify options that will be passed to preboot when the
// inline code is generated and injected into the document. If you just specify true,
// then we will automatically look up the root element tags based on the components that
// your application bootstraps.
preboot(preboot?: PrebootConfiguration | boolean): PrebootConfiguration;
preboot(preboot?: PrebootConfiguration | boolean): void;

// Configure how long we will wait for the application to stabilize itself before assuming it
// never will stabilize and failing the render operation. For build-time rendering, this can
// be a comfortably high number. For on-demand rendering, you should set this very low so
// that you can catch performance problems.
stabilizeTimeout(milliseconds?: number): number | null
}
27 changes: 27 additions & 0 deletions source/application/builder/tests/from-module.ts
Expand Up @@ -128,6 +128,19 @@ describe('ApplicationBuilderFromModule', () => {
});
});

it('should fail if state reader throws an exception or returns a rejected promise', async () => {
const application = loadApplicationFixtureFromModule(BasicRoutedModule,
builder => {
builder.stateReader(() => Promise.reject('This is an expected exception'));
});

const stream = await application.prerender();

return new Promise<void>((resolve, reject) => {
stream.subscribe(s => reject(new Error('Should have thrown an exception and failed')), resolve);
});
});

it('should be able to transmit state from the server to the client in the prerendered document', async () => {
const application = loadApplicationFixtureFromModule(BasicRoutedModule,
builder => {
Expand Down Expand Up @@ -349,4 +362,18 @@ describe('ApplicationBuilderFromModule', () => {
application.dispose();
}
});

it('should fail if postprocessor fails', async () => {
const application = loadApplicationFixtureFromModule(BasicInlineModule,
builder => builder.postprocess(
doc => {
throw new Error('This is an expected failure');
}));

const stream = await application.prerender();

return new Promise<void>((resolve, reject) => {
stream.subscribe(s => reject(new Error('Should have thrown an exception and failed')), resolve);
});
});
});
44 changes: 23 additions & 21 deletions source/application/compiler/webpack/config/cli.ts
Expand Up @@ -10,15 +10,35 @@ export class CliLoader implements ConfigurationLoader {
load(project: Project) {
const options = CliConfig.fromProject(project.basePath.toString());

const app = matchApplication(options.get('apps') || [], project.identifier);
const app = applicationFromIdentifier(options.get('apps'), project.identifier);

const cli = new NgCliWebpackConfig(baseOptions(project), app);
const composedOptions = {
target: project.environment,
environment: project.environment || process.env.NODE_ENV || 'prod',
outputPath: project.workingPath ? project.workingPath.toString() : null,
aot: false,
sourcemaps: true,
vendorChunk: false,
verbose: true,
progress: false,
extractCss: true,
watch: false,
outputHashing: null,
poll: null,
app: project.identifier ? project.identifier.toString() : null
};

const cli = new NgCliWebpackConfig(composedOptions, app);

return cli.buildConfig();
}
}

const matchApplication = (apps: Array<any>, identifier?: string | number) => {
const applicationFromIdentifier = (apps: Array<any>, identifier?: string | number) => {
if (apps == null) {
throw new CompilerException(`No apps are defined in ng configuration`);
}

switch (typeof identifier) {
case 'object':
case 'undefined':
Expand All @@ -45,21 +65,3 @@ const matchApplication = (apps: Array<any>, identifier?: string | number) => {
throw new CompilerException(`Invalid application identifier type: ${typeof identifier}`)
}
};

const baseOptions = (project: Project) => {
return {
target: project.environment,
environment: project.environment || process.env.NODE_ENV || 'prod',
outputPath: project.workingPath ? project.workingPath.toString() : null,
aot: false,
sourcemaps: true,
vendorChunk: false,
verbose: true,
progress: false,
extractCss: false,
watch: false,
outputHashing: null,
poll: null,
app: project.identifier ? project.identifier.toString() : null
}
};
11 changes: 2 additions & 9 deletions source/platform/document/providers.ts
Expand Up @@ -2,22 +2,15 @@ import {Provider} from '@angular/core';

import {DocumentContainer} from './container';
import {TemplateDocument, RequestUri} from './tokens';
import {ZoneProperties} from '../zone/properties';

export const PLATFORM_DOCUMENT_PROVIDERS: Array<Provider> = [
DocumentContainer,
{
provide: TemplateDocument,
useFactory: (zone: ZoneProperties) => {
return zone.parameter<string>('documentTemplate');
},
deps: [ZoneProperties],
useFactory: () => Zone.current.get('documentTemplate'),
},
{
provide: RequestUri,
useFactory: (zone: ZoneProperties) => {
return zone.parameter<string>('requestUri');
},
deps: [ZoneProperties],
useFactory: () => Zone.current.get('requestUri'),
},
];
4 changes: 2 additions & 2 deletions source/platform/factory.ts
Expand Up @@ -14,15 +14,15 @@ import {

import {PLATFORM_COLLECTOR_PROVIDERS} from './collectors';
import {PLATFORM_RESOURCE_LOADER_PROVIDERS} from './resource-loader';

import {ServerPlatform} from './platform';
import {ZoneProperties} from './zone';

import {randomizedApplicationId} from '../static';

const baseProviders: Array<Provider> = [
...PLATFORM_COLLECTOR_PROVIDERS,
{provide: APP_ID, useFactory: randomizedApplicationId},
{provide: PlatformRef, useClass: ServerPlatform},
{provide: ZoneProperties, useClass: ZoneProperties},
];

export const createStaticPlatform = createPlatformFactory(platformCore, 'node/static', baseProviders);
Expand Down
3 changes: 1 addition & 2 deletions source/platform/zone/index.ts
@@ -1,4 +1,3 @@
export * from './environment';
export * from './fork';
export * from './injector-map';
export * from './properties';
export * from './injector-map';
17 changes: 0 additions & 17 deletions source/platform/zone/properties.ts

This file was deleted.

0 comments on commit 454c8e3

Please sign in to comment.