fix(navigation): reduce urls to minimum set of fields

* wip

* simple-nav, simple-tabs, simple-nav-then-tabs, simple-nested-navs all pass w00t w00t

* updates

* fix tests

* update test
This commit is contained in:
Dan Bucholtz
2017-07-21 23:47:45 -05:00
committed by GitHub
parent 97f9522110
commit a8ceee467b
41 changed files with 1195 additions and 388 deletions

View File

@@ -419,8 +419,31 @@ export class App {
}
}
getNavByIdOrName(id: string) {
const navs = Array.from(this._rootNavs.values());
for (const navContainer of navs) {
const match = getNavByIdOrName(navContainer, id);
if (match) {
return match;
}
}
return null;
}
}
export function getNavByIdOrName(nav: NavigationContainer, id: string): NavigationContainer {
if (nav.id === id || nav.name === id) {
return nav;
}
for (const child of nav.getAllChildNavs()) {
const tmp = getNavByIdOrName(child, id);
if (tmp) {
return tmp;
}
}
return null;
}
function getPoppableNav(nav: NavControllerBase): NavControllerBase {
if (!nav) {
@@ -455,6 +478,7 @@ export function findTopNavs(nav: NavigationContainer): NavigationContainer[] {
return containers;
}
const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA'];
const ACTIVE_SCROLLING_TIME = 100;
const CLICK_BLOCK_BUFFER_IN_MILLIS = 64;

View File

@@ -767,6 +767,149 @@ describe('App', () => {
});
});
describe('getNavByIdOrName', () => {
it('should return a basic root nav', () => {
const nav = mockNavController();
app.registerRootNav(nav);
const result = app.getNavByIdOrName(nav.id);
expect(result).toEqual(nav);
});
it('should return a child nav', () => {
const rootNav = mockNavController();
app.registerRootNav(rootNav);
const childNav = mockNavController();
childNav.parent = rootNav;
rootNav.registerChildNav(childNav);
const childChildNav = mockNavController();
childChildNav.parent = childNav;
childNav.registerChildNav(childChildNav);
const expectedChildNav = app.getNavByIdOrName(childNav.id);
expect(expectedChildNav).toEqual(childNav);
const expectedChildChildNav = app.getNavByIdOrName(childChildNav.id);
expect(expectedChildChildNav).toEqual(childChildNav);
});
it('should return a child nav when there is a tabs in there', () => {
const rootNav = mockNavController();
app.registerRootNav(rootNav);
const tabs = mockTabs();
tabs.parent = rootNav;
rootNav.registerChildNav(tabs);
const tab1 = mockTab(tabs);
const tab2 = mockTab(tabs);
const tab3 = mockTab(tabs);
const tabChildNav = mockNavController();
tabChildNav.parent = tab2;
tab2.registerChildNav(tabChildNav);
const tabChildChildNav = mockNavController();
tabChildChildNav.parent = tabChildNav;
tabChildNav.registerChildNav(tabChildChildNav);
const expectedTab1 = app.getNavByIdOrName(tab1.id);
expect(expectedTab1).toEqual(tab1);
const expectedTab2 = app.getNavByIdOrName(tab2.id);
expect(expectedTab2).toEqual(tab2);
const expectedTab3 = app.getNavByIdOrName(tab3.id);
expect(expectedTab3).toEqual(tab3);
const expectedTabChildNav = app.getNavByIdOrName(tabChildNav.id);
expect(expectedTabChildNav).toEqual(tabChildNav);
const expectedTabChildChildNav = app.getNavByIdOrName(tabChildChildNav.id);
expect(expectedTabChildChildNav).toEqual(tabChildChildNav);
});
it('should return a basic root nav when the are multiple root navs', () => {
const rootNavOne = mockNavController();
const rootNavTwo = mockNavController();
const rootNavThree = mockNavController();
app.registerRootNav(rootNavOne);
app.registerRootNav(rootNavTwo);
app.registerRootNav(rootNavThree);
const expectedRootNavOne = app.getNavByIdOrName(rootNavOne.id);
expect(expectedRootNavOne).toEqual(rootNavOne);
const expectedRootNavTwo = app.getNavByIdOrName(rootNavTwo.id);
expect(expectedRootNavTwo).toEqual(rootNavTwo);
const expectedRootNavThree = app.getNavByIdOrName(rootNavThree.id);
expect(expectedRootNavThree).toEqual(rootNavThree);
});
it('should return a proper navs when there are multiple root navs with nested navs', () => {
const rootNavOne = mockNavController();
const rootNavTwo = mockNavController();
const rootNavThree = mockNavController();
app.registerRootNav(rootNavOne);
app.registerRootNav(rootNavTwo);
app.registerRootNav(rootNavThree);
const childNavOne = mockNavController();
childNavOne.parent = rootNavOne;
rootNavOne.registerChildNav(childNavOne);
const childChildNavOne = mockNavController();
childChildNavOne.parent = childNavOne;
childNavOne.registerChildNav(childChildNavOne);
const childNavTwo = mockNavController();
childNavOne.parent = rootNavTwo;
rootNavTwo.registerChildNav(childNavTwo);
const childChildNavTwo = mockNavController();
childChildNavTwo.parent = childNavTwo;
childNavTwo.registerChildNav(childChildNavTwo);
const childNavThree = mockNavController();
childNavThree.parent = rootNavThree;
rootNavThree.registerChildNav(childNavThree);
const childChildNavThree = mockNavController();
childChildNavThree.parent = childNavThree;
childNavThree.registerChildNav(childChildNavThree);
const expectedRootNavOne = app.getNavByIdOrName(rootNavOne.id);
expect(expectedRootNavOne).toEqual(rootNavOne);
const expectedChildNavOne = app.getNavByIdOrName(childNavOne.id);
expect(expectedChildNavOne).toEqual(childNavOne);
const expectedChildChildNavOne = app.getNavByIdOrName(childChildNavOne.id);
expect(expectedChildChildNavOne).toEqual(childChildNavOne);
const expectedRootNavTwo = app.getNavByIdOrName(rootNavTwo.id);
expect(expectedRootNavTwo).toEqual(rootNavTwo);
const expectedChildNavTwo = app.getNavByIdOrName(childNavTwo.id);
expect(expectedChildNavTwo).toEqual(childNavTwo);
const expectedChildChildNavTwo = app.getNavByIdOrName(childChildNavTwo.id);
expect(expectedChildChildNavTwo).toEqual(childChildNavTwo);
const expectedRootNavThree = app.getNavByIdOrName(rootNavThree.id);
expect(expectedRootNavThree).toEqual(rootNavThree);
const expectedChildNavThree = app.getNavByIdOrName(childNavThree.id);
expect(expectedChildNavThree).toEqual(childNavThree);
const expectedChildChildNavThree = app.getNavByIdOrName(childChildNavThree.id);
expect(expectedChildChildNavThree).toEqual(childChildNavThree);
});
});
let app: App;
let config: Config;
let plt: MockPlatform;

View File

@@ -3,8 +3,8 @@ import { Component } from '@angular/core';
@Component({
template: `
<ion-split-pane>
<ion-nav [root]="rootOne"></ion-nav>
<ion-nav [root]="rootTwo" main #content></ion-nav>
<ion-nav [root]="rootOne" name="left"></ion-nav>
<ion-nav [root]="rootTwo" main #content name="right"></ion-nav>
</ion-split-pane>
`

View File

@@ -19,7 +19,6 @@ import { IonicPage, NavController, NavParams } from '../../../../../../..';
<div>
Name: {{paramTwo}}
</div>
<button ion-button (click)="goToNext()">Next</button>
</ion-content>
`
})

View File

@@ -14,10 +14,10 @@ import { IonicPage, NavController, NavParams } from '../../../../../../..';
<ion-content>
Tabs 1 Tab 2 Page 3
<div>
Param One: {{userId}}
Param One: {{paramOne}}
</div>
<div>
Param Two: {{name}}
Param Two: {{paramTwo}}
</div>
</ion-content>
`

View File

@@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
template: `<ion-nav [root]="root"></ion-nav>`
})
export class AppComponent {
root = 'LoginPage';
}

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule } from '../../../../..';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
IonicModule.forRoot(AppComponent, { swipeBackEnabled: true, preloadModules: true }),
],
bootstrap: [IonicApp]
})
export class AppModule {}

View File

@@ -0,0 +1,5 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../..';
import { LandingPage } from './landing-page';
@NgModule({
imports: [
IonicPageModule.forChild(LandingPage)
],
declarations: [
LandingPage
]
})
export class LandingPageModule { }

View File

@@ -0,0 +1,30 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../..';
@IonicPage()
@Component({
template: `
<ion-header>
<ion-navbar>
<ion-title>My Super Cool, multi-pane App</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-grid>
<ion-row>
<ion-col style="height: 1000px; background-color: blue">
<ion-nav root="FirstPage" name="left"></ion-nav>
</ion-col>
<ion-col style="height: 1000px; background-color: green">
<ion-nav root="FourthPage" name="right"></ion-nav>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
`
})
export class LandingPage {
constructor(public nav: NavController) {
}
}

View File

@@ -0,0 +1,9 @@
<ion-header>
<ion-navbar>
<ion-title>Page One</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Page One</h2>
<button ion-button (click)="goToPageTwo()">Go to Page Two</button>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { FirstPage } from './first-page';
@NgModule({
imports: [
IonicPageModule.forChild(FirstPage)
],
declarations: [
FirstPage
]
})
export class FirstPageModule { }

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../../..';
@IonicPage()
@Component({
templateUrl: 'first-page.html'
})
export class FirstPage {
constructor(public nav: NavController) {
}
goToPageTwo() {
this.nav.push('SecondPage', { userId: '123', name: 'Michael Scott'});
}
}

View File

@@ -0,0 +1,15 @@
<ion-header>
<ion-navbar>
<ion-title>Page Two</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Page Two</h2>
<div>
User ID: {{userId}}
</div>
<div>
Name {{name}}
</div>
<button ion-button (click)="goToNextPage()">Go to Next</button>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { SecondPage } from './second-page';
@NgModule({
imports: [
IonicPageModule.forChild(SecondPage)
],
declarations: [
SecondPage
]
})
export class SecondPageModule { }

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from '../../../../../../..';
@IonicPage({
segment: 'pageTwo/user/:userId/name/:name'
})
@Component({
templateUrl: 'second-page.html'
})
export class SecondPage {
userId: string;
name: string;
constructor(public nav: NavController, public params: NavParams) {
this.userId = this.params.data.userId;
this.name = this.params.data.name;
}
goToNextPage() {
this.nav.push('ThirdPage', { paramOne: 'mono', paramTwo: 'stereo'});
}
}

View File

@@ -0,0 +1,14 @@
<ion-header>
<ion-navbar>
<ion-title>Page Three</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
Page Three
<div>
Param One: {{paramOne}}
</div>
<div>
Param Two: {{paramTwo}}
</div>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { ThirdPage } from './third-page';
@NgModule({
imports: [
IonicPageModule.forChild(ThirdPage)
],
declarations: [
ThirdPage
]
})
export class ThirdPageModule { }

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams} from '../../../../../../..';
@IonicPage({
segment: 'thirdPage/paramOne/:paramOne/paramTwo/:paramTwo'
})
@Component({
templateUrl: 'third-page.html'
})
export class ThirdPage {
paramOne: string;
paramTwo: string;
constructor(public nav: NavController, public params: NavParams) {
this.paramOne = params.data.paramOne;
this.paramTwo = params.data.paramTwo;
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../..';
import { LoginPage } from './login-page';
@NgModule({
imports: [
IonicPageModule.forChild(LoginPage)
],
declarations: [
LoginPage
]
})
export class LoginPageModule { }

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../..';
@IonicPage()
@Component({
template: `
<ion-header>
<ion-navbar>
<ion-title>Login</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<button ion-button (click)="clickMe()">Login</button>
</ion-content>
`
})
export class LoginPage {
constructor(public nav: NavController) {
}
clickMe() {
this.nav.push('LandingPage');
}
}

View File

@@ -0,0 +1,15 @@
<ion-header>
<ion-navbar>
<ion-title>Page Five</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Page Five</h2>
<div>
User ID: {{userId}}
</div>
<div>
Name {{name}}
</div>
<button ion-button (click)="goToNextPage()">Go to Next</button>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { FifthPage } from './fifth-page';
@NgModule({
imports: [
IonicPageModule.forChild(FifthPage)
],
declarations: [
FifthPage
]
})
export class FifthPageModule { }

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from '../../../../../../..';
@IonicPage({
segment: 'pageFive/user/:userId/name/:name'
})
@Component({
templateUrl: 'fifth-page.html'
})
export class FifthPage {
userId: string;
name: string;
constructor(public nav: NavController, public params: NavParams) {
this.userId = this.params.data.userId;
this.name = this.params.data.name;
}
goToNextPage() {
this.nav.push('SixthPage', { paramOne: 'Tobey', paramTwo: 'Holly'});
}
}

View File

@@ -0,0 +1,9 @@
<ion-header>
<ion-navbar>
<ion-title>Page Four</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<h2>Page Four</h2>
<button ion-button (click)="goToPageTwo()">Next</button>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { FourthPage } from './fourth-page';
@NgModule({
imports: [
IonicPageModule.forChild(FourthPage)
],
declarations: [
FourthPage
]
})
export class FourthPageModule { }

View File

@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../../..';
@IonicPage()
@Component({
templateUrl: 'fourth-page.html'
})
export class FourthPage {
constructor(public nav: NavController) {
}
goToPageTwo() {
this.nav.push('FifthPage', { userId: '567', name: 'Pamela Beasley'});
}
}

View File

@@ -0,0 +1,14 @@
<ion-header>
<ion-navbar>
<ion-title>Page Six</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
Page Six
<div>
Param One: {{paramOne}}
</div>
<div>
Param Two: {{paramTwo}}
</div>
</ion-content>

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../../..';
import { SixthPage } from './sixth-page';
@NgModule({
imports: [
IonicPageModule.forChild(SixthPage)
],
declarations: [
SixthPage
]
})
export class SixthPageModule { }

View File

@@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams} from '../../../../../../..';
@IonicPage({
segment: 'sixthPage/paramOne/:paramOne/paramTwo/:paramTwo'
})
@Component({
templateUrl: 'sixth-page.html'
})
export class SixthPage {
paramOne: string;
paramTwo: string;
constructor(public nav: NavController, public params: NavParams) {
this.paramOne = params.data.paramOne;
this.paramTwo = params.data.paramTwo;
}
}

View File

@@ -507,6 +507,13 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs, Navigat
return selected ? [selected] : [];
}
/**
* @internal
*/
getAllChildNavs(): any[] {
return this._tabs;
}
/**
* @internal
*/

View File

@@ -445,7 +445,7 @@ export class IonicModule {
{ provide: ModuleLoader, useFactory: provideModuleLoader, deps: [NgModuleLoader, Injector]},
{ provide: LocationStrategy, useFactory: provideLocationStrategy, deps: [ PlatformLocation, [new Inject(APP_BASE_HREF), new Optional()], Config ] },
{ provide: UrlSerializer, useFactory: setupUrlSerializer, deps: [ DeepLinkConfigToken ] },
{ provide: UrlSerializer, useFactory: setupUrlSerializer, deps: [ App, DeepLinkConfigToken ] },
{ provide: DeepLinker, useFactory: setupDeepLinker, deps: [ App, UrlSerializer, Location, ModuleLoader, ComponentFactoryResolver ] },
]
};

View File

@@ -131,18 +131,23 @@ export class DeepLinker {
*/
navChange(direction: string) {
if (direction) {
const rootNavContainers = this._app.getActiveNavContainers();
const activeNavContainers = this._app.getActiveNavContainers();
// the only time you'll ever get a TABS here is when loading directly from a URL
// this method will be called again when the TAB is loaded
// so just don't worry about the TABS for now
// if you encounter a TABS, just return
let segments: NavSegment[] = [];
for (const rootNavContainer of rootNavContainers) {
if (isTabs(rootNavContainer) || (rootNavContainer as NavController).isTransitioning()) {
for (const activeNavContainer of activeNavContainers) {
if (isTabs(activeNavContainer) || (activeNavContainer as NavController).isTransitioning()) {
return;
}
const segmentsForNav = this.getSegmentsFromNav(rootNavContainer);
segments = segments.concat(segmentsForNav);
}
// okay, get the root navs and build the segments up
let segments: NavSegment[] = [];
const navContainers: NavigationContainer[] = this._app.getRootNavs();
for (const navContainer of navContainers) {
const segmentsForNav = this.getSegmentsFromNav(navContainer);
segments = segments.concat(segmentsForNav);
}
segments = segments.filter(segment => !!segment);
if (segments.length) {
@@ -153,19 +158,16 @@ export class DeepLinker {
}
getSegmentsFromNav(nav: NavigationContainer): NavSegment[] {
const segments: NavSegment[] = [];
while (nav) {
if (isNav(nav)) {
segments.push(this.getSegmentFromNav(nav as NavController));
nav = nav.parent;
} else if (isTab(nav)) {
segments.push(this.getSegmentFromTab(nav));
nav = nav.parent && nav.parent.parent;
} else {
nav = nav.parent;
}
let segments: NavSegment[] = [];
if (isNav(nav)) {
segments.push(this.getSegmentFromNav(nav as NavController));
} else if (isTab(nav)) {
segments.push(this.getSegmentFromTab(nav));
}
return segments.reverse();
nav.getActiveChildNavs().forEach(child => {
segments = segments.concat(this.getSegmentsFromNav(child));
});
return segments;
}
getSegmentFromNav(nav: NavController, component?: any, data?: any): NavSegment {
@@ -176,7 +178,7 @@ export class DeepLinker {
data = viewController.data;
}
}
return this._serializer.serializeComponent({ navId: nav.name && nav.name.length ? nav.name : nav.id, secondaryId: null, type: 'nav'}, component, data);
return this._serializer.serializeComponent(nav, component, data);
}
getSegmentFromTab(navContainer: NavigationContainer, component?: any, data?: any): NavSegment {
@@ -190,7 +192,7 @@ export class DeepLinker {
component = viewController.component;
data = viewController.data;
}
return this._serializer.serializeComponent({ navId: tabsNavContainer.name || tabsNavContainer.id, secondaryId: tabsNavContainer.getSecondaryIdentifier(), type: 'tabs'}, component, data);
return this._serializer.serializeComponent(tabsNavContainer, component, data);
}
}
}
@@ -415,7 +417,7 @@ export class DeepLinker {
return navController.popTo(viewController, {
animate: false,
updateUrl: false,
}, done);
}, {}, done);
}
}
}

View File

@@ -991,6 +991,10 @@ export class NavControllerBase extends Ion implements NavController {
return this._children;
}
getAllChildNavs(): any[] {
return this._children;
}
registerChildNav(container: NavigationContainer) {
this._children.push(container);
}

View File

@@ -599,6 +599,11 @@ export abstract class NavController implements NavigationContainer {
*/
abstract getActiveChildNav(): any;
/**
* Returns a list of all child navigation containers
*/
abstract getAllChildNavs(): any[];
/**
* Returns if the nav controller is actively transitioning or not.

View File

@@ -163,16 +163,26 @@ export interface NavResult {
direction?: string;
}
export interface NavSegment {
export interface NavSegment extends DehydratedSegment {
type: string;
navId: string;
secondaryId: string;
requiresExplicitNavPrefix?: boolean;
}
export interface DehydratedSegment {
id: string;
name: string;
component?: any;
loadChildren?: string;
data: any;
type: string;
navId: string;
secondaryId: string;
defaultHistory?: NavSegment[];
secondaryId?: string;
}
export interface DehydratedSegmentPair {
segments: DehydratedSegment[];
navGroup: NavGroup;
}
export interface NavGroup {

View File

@@ -1,10 +1,14 @@
import { NavController } from './nav-controller';
/**
* @hidden
*/
export interface NavigationContainer {
id: string;
name: string;
parent: NavController;
getActiveChildNavs(): NavigationContainer[];
getAllChildNavs?(): NavigationContainer[];
getType(): string;
getSecondaryIdentifier(): string;
}

View File

@@ -457,9 +457,7 @@ describe('DeepLinker', () => {
linker.getSegmentFromNav(mockNav, null, null);
expect(spy.calls.first().args[0].navId).toEqual(mockNav.name);
expect(spy.calls.first().args[0].secondaryId).toBeFalsy();
expect(spy.calls.first().args[0].type).toEqual('nav');
expect(spy.calls.first().args[0]).toEqual(mockNav);
});
it('should use the id of the nav when name doesnt exists', () => {
@@ -472,9 +470,7 @@ describe('DeepLinker', () => {
linker.getSegmentFromNav(mockNav, null, null);
expect(spy.calls.first().args[0].navId).toEqual(mockNav.id);
expect(spy.calls.first().args[0].secondaryId).toBeFalsy();
expect(spy.calls.first().args[0].type).toEqual('nav');
expect(spy.calls.first().args[0]).toEqual(mockNav);
});
});
@@ -493,7 +489,7 @@ describe('DeepLinker', () => {
linker.getSegmentFromTab(tabOne, null, null);
expect(spy).toHaveBeenCalled();
expect(spy.calls.first().args[0].navId).toEqual(tabs.name);
expect(spy.calls.first().args[0]).toEqual(tabs);
});
@@ -511,7 +507,7 @@ describe('DeepLinker', () => {
linker.getSegmentFromTab(tabOne, null, null);
expect(spy).toHaveBeenCalled();
expect(spy.calls.first().args[0].navId).toEqual(tabs.id);
expect(spy.calls.first().args[0]).toEqual(tabs);
});
});
@@ -685,7 +681,7 @@ describe('DeepLinker', () => {
beforeEach(() => {
let linkConfig = mockDeepLinkConfig();
serializer = new UrlSerializer(linkConfig);
serializer = new UrlSerializer(mockApp(), linkConfig);
let moduleLoader = mockModuleLoader();
let baseCfr: any = null;

View File

@@ -1,17 +1,18 @@
import { NavLink, NavSegment } from '../nav-util';
import {
NavGroup,
UrlSerializer,
convertUrlToDehydratedSegments,
convertUrlToSegments,
createMatchedData,
findLinkByComponentData,
formatUrlPart,
isPartMatch,
navGroupStringtoObjects,
normalizeLinks,
parseUrlParts,
urlToNavGroupStrings,
urlToNavGroupStringsTwo,
} from '../url-serializer';
import { MockView1, MockView2, MockView3, mockDeepLinkConfig, mockNavController, noop } from '../../util/mock-providers';
import { MockView1, MockView2, MockView3, mockApp, mockDeepLinkConfig, mockNavController, mockTab, mockTabs, noop } from '../../util/mock-providers';
describe('UrlSerializer', () => {
@@ -22,32 +23,37 @@ describe('UrlSerializer', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view/:param1' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view/:param1/:param2' };
const navGroup: NavGroup = { type: 'nav', navId: 'n1', secondaryId: null, segmentPieces: ['view']};
serializer = mockSerializer([link1, link2, link3]);
serializer._createSegment = noop;
spyOn(serializer, '_createSegment');
serializer.serializeComponent(navGroup, MockView1, null);
expect(serializer._createSegment).toHaveBeenCalledWith(navGroup, link1, null);
const nav = mockNavController();
serializer.serializeComponent(nav, MockView1, null);
expect(serializer._createSegment).toHaveBeenCalledWith(serializer._app, nav, link1, null);
});
it('should create segment if component found in links', () => {
serializer._createSegment = noop;
spyOn(serializer, '_createSegment');
serializer.serializeComponent({ type: 'nav', navId: 'n1', secondaryId: null, segmentPieces: ['view']}, MockView1, null);
const nav = mockNavController();
serializer.serializeComponent(nav, MockView1, null);
expect(serializer._createSegment).toHaveBeenCalled();
});
it('should return null if component not found in links', () => {
serializer._createSegment = noop;
spyOn(serializer, '_createSegment');
serializer.serializeComponent({ type: 'nav', navId: 'n1', secondaryId: null, segmentPieces: ['view']}, NotFound, null);
const nav = mockNavController();
serializer.serializeComponent(nav, NotFound, null);
expect(serializer._createSegment).not.toHaveBeenCalled();
});
it('should create tab segment if component found in deep links', () => {
serializer._createSegment = noop;
spyOn(serializer, '_createSegment');
serializer.serializeComponent({ type: 'nav', navId: 'n1', secondaryId: null, segmentPieces: ['view']}, MockView1, null);
const tabs = mockTabs();
const tab = mockTab(tabs);
serializer.serializeComponent(tab, MockView1, null);
expect(serializer._createSegment).toHaveBeenCalled();
});
@@ -65,7 +71,8 @@ describe('UrlSerializer', () => {
name: 'jenny'
};
const segment = serializer._createSegment({ navId: '1', type: 'nav', secondaryId: null}, link, data);
const nav = mockNavController();
const segment = serializer._createSegment(serializer._app, nav, link, data);
expect(segment.id).toEqual('userId/8675309/name/jenny');
expect(segment.component).toEqual(link.component);
expect(segment.data.id).toEqual(data.id);
@@ -84,7 +91,8 @@ describe('UrlSerializer', () => {
id: char,
name: 'jenny'
};
const segment = serializer._createSegment({ navId: '1', type: 'nav', secondaryId: null}, link, data);
const nav = mockNavController();
const segment = serializer._createSegment(serializer._app, nav, link, data);
expect(segment.id).toEqual(`userId/${encoded}/name/${data.name}`);
expect(segment.component).toEqual(MockView1);
expect(segment.data.id).toEqual(char);
@@ -95,7 +103,8 @@ describe('UrlSerializer', () => {
segmentParts: ['settings-view'],
component: MockView1
};
const segment = serializer._createSegment({ navId: '1', type: 'nav', secondaryId: null}, link, null);
const nav = mockNavController();
const segment = serializer._createSegment(serializer._app, nav, link, null);
expect(segment.id).toEqual('settings-view');
expect(segment.component).toEqual(MockView1);
expect(segment.data).toEqual(null);
@@ -179,106 +188,51 @@ describe('UrlSerializer', () => {
describe('navGroupStringtoObjects', () => {
it('should convert the nav group strings to objects', () => {
const urlChunks = ['nav/1/chunk/of/segment', 'tabs/1/tab-one/chunk/of/segment'];
const urlChunks = ['taco/burrito/pizza/nachos', 'nav/1/chunk/of/segment', 'tabs/1/tab-one/chunk/of/segment'];
const objects = navGroupStringtoObjects(urlChunks);
expect(objects.length).toEqual(2);
expect(objects[0].type).toEqual('nav');
expect(objects[0].navId).toEqual('1');
expect(objects.length).toEqual(3);
expect(objects[0].type).toEqual(null);
expect(objects[0].navId).toEqual(null);
expect(objects[0].secondaryId).toEqual(null);
expect(objects[0].segmentPieces.length).toEqual(3);
expect(objects[0].segmentPieces[0]).toEqual('chunk');
expect(objects[0].segmentPieces[1]).toEqual('of');
expect(objects[0].segmentPieces[2]).toEqual('segment');
expect(objects[1].type).toEqual('tabs');
expect(objects[0].segmentPieces.length).toEqual(4);
expect(objects[0].segmentPieces[0]).toEqual('taco');
expect(objects[0].segmentPieces[1]).toEqual('burrito');
expect(objects[0].segmentPieces[2]).toEqual('pizza');
expect(objects[0].segmentPieces[3]).toEqual('nachos');
expect(objects[1].type).toEqual('nav');
expect(objects[1].navId).toEqual('1');
expect(objects[1].secondaryId).toEqual('tab-one');
expect(objects[1].secondaryId).toEqual(null);
expect(objects[1].segmentPieces.length).toEqual(3);
expect(objects[1].segmentPieces[0]).toEqual('chunk');
expect(objects[1].segmentPieces[1]).toEqual('of');
expect(objects[1].segmentPieces[2]).toEqual('segment');
});
});
describe('parse', () => {
it('should return empty list of segments for bogus url', () => {
serializer = mockSerializer([]);
const segments = serializer.parse('/some/bogus/url');
expect(segments.length).toEqual(0);
});
it('should return empty list of segments when there isnt a match', () => {
serializer = mockSerializer([
{ segment: 'some/chunk/of/url', name: 'viewone', component: MockView1 },
{ segment: 'another/section/of/url', name: 'viewtwo', component: MockView2 }
]);
const segments = serializer.parse('/nav/n1/not/a/matching/url');
expect(segments.length).toEqual(0);
});
it('should return the segments from the url with multiple navs', () => {
serializer = mockSerializer([
{ segment: 'userId/:id/name/:name', name: 'viewone', component: MockView1 },
{ segment: 'selectedId/:id/food/:food', name: 'viewtwo', component: MockView2 }
]);
const segments = serializer.parse('/nav/n1/userId/123/name/Stanley/nav/n2/selectedId/456/food/tacos');
expect(segments.length).toEqual(2);
expect(segments[0].name).toEqual('viewone');
expect(segments[0].data.id).toEqual('123');
expect(segments[0].data.name).toEqual('Stanley');
expect(segments[1].name).toEqual('viewtwo');
expect(segments[1].data.id).toEqual('456');
expect(segments[1].data.food).toEqual('tacos');
});
it('should return the segments from the url with multiple tabs', () => {
serializer = mockSerializer([
{ segment: 'userId/:id/name/:name', name: 'viewone', component: MockView1 },
{ segment: 'selectedId/:id/food/:food', name: 'viewtwo', component: MockView2 }
]);
const segments = serializer.parse('/tabs/t1/tab-one/userId/123/name/Stanley/tabs/t2/tab-three/selectedId/456/food/tacos');
expect(segments.length).toEqual(2);
expect(segments[0].name).toEqual('viewone');
expect(segments[0].navId).toEqual('t1');
expect(segments[0].data.id).toEqual('123');
expect(segments[0].data.name).toEqual('Stanley');
expect(segments[0].secondaryId).toEqual('tab-one');
expect(segments[1].name).toEqual('viewtwo');
expect(segments[1].navId).toEqual('t2');
expect(segments[1].data.id).toEqual('456');
expect(segments[1].data.food).toEqual('tacos');
expect(segments[1].secondaryId).toEqual('tab-three');
});
it('should return the segments from a mixed nav/tabs url', () => {
serializer = mockSerializer([
{ segment: 'userId/:id/name/:name', name: 'viewone', component: MockView1 },
{ segment: 'selectedId/:id/food/:food', name: 'viewtwo', component: MockView2 }
]);
const segments = serializer.parse('/tabs/t1/tab-one/userId/123/name/Stanley/nav/n1/selectedId/456/food/tacos');
expect(segments.length).toEqual(2);
expect(segments[0].name).toEqual('viewone');
expect(segments[0].navId).toEqual('t1');
expect(segments[0].data.id).toEqual('123');
expect(segments[0].data.name).toEqual('Stanley');
expect(segments[0].secondaryId).toEqual('tab-one');
expect(segments[1].name).toEqual('viewtwo');
expect(segments[1].navId).toEqual('n1');
expect(segments[1].data.id).toEqual('456');
expect(segments[1].data.food).toEqual('tacos');
expect(segments[1].secondaryId).toEqual(null);
expect(objects[2].type).toEqual('tabs');
expect(objects[2].navId).toEqual('1');
expect(objects[2].secondaryId).toEqual('tab-one');
expect(objects[2].segmentPieces.length).toEqual(3);
expect(objects[2].segmentPieces[0]).toEqual('chunk');
expect(objects[2].segmentPieces[1]).toEqual('of');
expect(objects[2].segmentPieces[2]).toEqual('segment');
});
});
describe('serialize', () => {
it('should serialize multiple segments into a url with explicit prefixs', () => {
let paths: NavSegment[] = [
{ type: 'nav', navId: 'whatup', secondaryId: null, id: 'some/url/chunks', name: 'viewOne', component: MockView1, data: null, requiresExplicitNavPrefix: true},
{ type: 'tabs', navId: 't1', secondaryId: 'tab-one', id: 'some/more/url/chunks', name: 'viewTwo', component: MockView1, data: null, requiresExplicitNavPrefix: true }
];
const result = serializer.serialize(paths);
expect(result).toEqual('/nav/whatup/some/url/chunks/tabs/t1/tab-one/some/more/url/chunks');
});
it('should serialize multiple segments into a url', () => {
let paths: NavSegment[] = [
{ type: 'nav', navId: 'whatup', secondaryId: null, id: 'some/url/chunks', name: 'viewOne', component: MockView1, data: null },
{ type: 'tabs', navId: 't1', secondaryId: 'tab-one', id: 'some/more/url/chunks', name: 'viewTwo', component: MockView1, data: null }
];
const result = serializer.serialize(paths);
expect(result).toEqual('/nav/whatup/some/url/chunks/tabs/t1/tab-one/some/more/url/chunks');
expect(result).toEqual('/some/url/chunks/tab-one/some/more/url/chunks');
});
it('should return default url when given empty list of segments', () => {
@@ -342,194 +296,6 @@ describe('UrlSerializer', () => {
});
describe('parseUrlParts', () => {
it('should return a single matching segment', () => {
// arrange
const navGroups = [];
const configLinks = [];
const segmentPieces = ['some', 'part', 'of', 'url'];
const navGroup = { type: 'nav', navId: '1', secondaryId: '', segmentPieces: segmentPieces };
navGroups.push(navGroup);
const configLink = {
segmentParts: segmentPieces,
segmentPartsLen: 4,
component: {},
name: 'someName',
loadChildren: 'someValue',
};
configLinks.push(configLink);
// act
const segments = parseUrlParts(navGroups, configLinks);
// assert
expect(segments.length).toEqual(1);
expect(segments[0].id).toEqual(configLink.segmentParts.join('/'));
expect(segments[0].component).toEqual(configLink.component);
expect(segments[0].name).toEqual(configLink.name);
expect(segments[0].loadChildren).toEqual(configLink.loadChildren);
expect(segments[0].type).toEqual(navGroup.type);
expect(segments[0].navId).toEqual(navGroup.navId);
expect(segments[0].secondaryId).toEqual(navGroup.secondaryId);
});
it('should return single matching segment for tabs', () => {
// arrange
const navGroups = [];
const configLinks = [];
const segmentPieces = ['some', 'part', 'of', 'url'];
const navGroup = { type: 'tabs', navId: '1', secondaryId: 'tab-one', segmentPieces: segmentPieces };
navGroups.push(navGroup);
const configLink = {
segmentParts: ['some', ':someVariable', 'of', ':someVariable2'],
segmentPartsLen: 4,
component: {},
name: 'someName',
loadChildren: 'someValue',
};
configLinks.push(configLink);
// act
const segments = parseUrlParts(navGroups, configLinks);
// assert
expect(segments.length).toEqual(1);
expect(segments[0].id).toEqual(configLink.segmentParts.join('/'));
expect(segments[0].component).toEqual(configLink.component);
expect(segments[0].name).toEqual(configLink.name);
expect(segments[0].loadChildren).toEqual(configLink.loadChildren);
expect(segments[0].type).toEqual(navGroup.type);
expect(segments[0].navId).toEqual(navGroup.navId);
expect(segments[0].secondaryId).toEqual(navGroup.secondaryId);
expect(segments[0].data.someVariable).toEqual('part');
expect(segments[0].data.someVariable2).toEqual('url');
});
it('should return an empty list of segments when there isnt a nav group', () => {
// arrange
const configLinks = [];
const configLink = {
segmentParts: ['some', ':someVariable', 'of', ':someVariable2'],
segmentPartsLen: 4,
component: {},
name: 'someName',
loadChildren: 'someValue',
};
configLinks.push(configLink);
// act
const segments = parseUrlParts([], configLinks);
// assert
expect(segments.length).toEqual(0);
});
it('should return a list of segments', () => {
// arrange
const navGroups = [];
const configLinks = [];
const segmentPiecesOne = ['some', 'part', 'of', 'url'];
const navGroup = { type: 'tabs', navId: '1', secondaryId: 'tab-one', segmentPieces: segmentPiecesOne };
const segmentPiecesTwo = ['userId', '123', 'name', 'Stanley Hudson'];
const navGroupTwo = { type: 'nav', navId: '2', secondaryId: '', segmentPieces: segmentPiecesTwo };
navGroups.push(navGroup);
navGroups.push(navGroupTwo);
const configLink = {
segmentParts: ['some', ':someVariable', 'of', ':someVariable2'],
segmentPartsLen: 4,
component: {},
name: 'someName',
loadChildren: 'someValue',
};
const configLinkTwo = {
segmentParts: ['userId', ':userId', 'name', ':name'],
segmentPartsLen: 4,
component: {},
name: 'nameTwo',
loadChildren: 'valueTwo',
};
configLinks.push(configLink);
configLinks.push(configLinkTwo);
// act
const segments = parseUrlParts(navGroups, configLinks);
// assert
expect(segments.length).toEqual(2);
expect(segments[0].id).toEqual(configLink.segmentParts.join('/'));
expect(segments[0].component).toEqual(configLink.component);
expect(segments[0].name).toEqual(configLink.name);
expect(segments[0].loadChildren).toEqual(configLink.loadChildren);
expect(segments[0].type).toEqual(navGroup.type);
expect(segments[0].navId).toEqual(navGroup.navId);
expect(segments[0].secondaryId).toEqual(navGroup.secondaryId);
expect(segments[0].data.someVariable).toEqual('part');
expect(segments[0].data.someVariable2).toEqual('url');
expect(segments[1].id).toEqual(configLinkTwo.segmentParts.join('/'));
expect(segments[1].component).toEqual(configLinkTwo.component);
expect(segments[1].name).toEqual(configLinkTwo.name);
expect(segments[1].loadChildren).toEqual(configLinkTwo.loadChildren);
expect(segments[1].type).toEqual(navGroupTwo.type);
expect(segments[1].navId).toEqual(navGroupTwo.navId);
expect(segments[1].secondaryId).toEqual(navGroupTwo.secondaryId);
expect(segments[1].data.userId).toEqual('123');
expect(segments[1].data.name).toEqual('Stanley Hudson');
});
it('should return only matching segments for the nav groups', () => {
// arrange
const navGroups = [];
const configLinks = [];
const segmentPiecesOne = ['some', 'part', 'of', 'url'];
const navGroup = { type: 'tabs', navId: '1', secondaryId: 'tab-one', segmentPieces: segmentPiecesOne };
const segmentPiecesTwo = ['userId', '123', 'name', 'Stanley Hudson'];
const navGroupTwo = { type: 'nav', navId: '2', secondaryId: '', segmentPieces: segmentPiecesTwo };
navGroups.push(navGroup);
navGroups.push(navGroupTwo);
const configLink = {
segmentParts: ['some', ':someVariable', 'of', ':someVariable2'],
segmentPartsLen: 4,
component: {},
name: 'someName',
loadChildren: 'someValue',
};
const configLinkTwo = {
segmentParts: ['some', 'bogus', 'content', 'wewontmatch'],
segmentPartsLen: 4,
component: {},
name: 'nameTwo',
loadChildren: 'valueTwo',
};
const configLinkThree = {
segmentParts: ['hi'],
segmentPartsLen: 1,
component: {},
name: 'nameThree',
loadChildren: 'valueThree',
};
configLinks.push(configLink);
configLinks.push(configLinkTwo);
configLinks.push(configLinkThree);
const segments = parseUrlParts(navGroups, configLinks);
expect(segments.length).toEqual(1);
expect(segments[0].name).toEqual(configLink.name);
});
});
describe('isPartMatch', () => {
it('should match if parts are equal', () => {
@@ -764,6 +530,289 @@ describe('UrlSerializer', () => {
});
describe('urlToNavGroupStringsTwo', () => {
it('should get an array with a single piece url back', () => {
const url = 'test';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(1);
expect(result[0]).toEqual('test');
});
it('should get an array with multiple pieces back', () => {
const url = 'the/dog/jumps/high';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(1);
expect(result[0]).toEqual('the/dog/jumps/high');
});
it('should return a single entry with the nav prefix', () => {
const url = 'nav/myApp/the/dog/jumps/high';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(1);
expect(result[0]).toEqual('nav/myApp/the/dog/jumps/high');
});
it('should return a single entry with the tabs prefix', () => {
const url = 'tabs/myApp/tab-one/the/dog/jumps/high';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(1);
expect(result[0]).toEqual('tabs/myApp/tab-one/the/dog/jumps/high');
});
it('should return multiple entries with the nav prefix', () => {
const url = 'nav/myApp/the/dog/jumps/high/nav/someSubNav/taco/burrito/nav/thirdNav/banana/apple/orange';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(3);
expect(result[0]).toEqual('nav/myApp/the/dog/jumps/high');
expect(result[1]).toEqual('nav/someSubNav/taco/burrito');
expect(result[2]).toEqual('nav/thirdNav/banana/apple/orange');
});
it('should return multiple entries with the tabs prefix', () => {
const url = 'tabs/myApp/tab-one/the/dog/jumps/high/tabs/someSubNav/tab-two/taco/burrito/tabs/thirdNav/tab-three/banana/apple/orange';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(3);
expect(result[0]).toEqual('tabs/myApp/tab-one/the/dog/jumps/high');
expect(result[1]).toEqual('tabs/someSubNav/tab-two/taco/burrito');
expect(result[2]).toEqual('tabs/thirdNav/tab-three/banana/apple/orange');
});
it('should handle a nav in the middle of the url', () => {
const url = 'the/dog/jumps/high/nav/someSubNav/taco/burrito/nav/thirdNav/banana/apple/orange';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(3);
expect(result[0]).toEqual('the/dog/jumps/high');
expect(result[1]).toEqual('nav/someSubNav/taco/burrito');
expect(result[2]).toEqual('nav/thirdNav/banana/apple/orange');
});
it('should handle a tabs in the middle of the url', () => {
const url = 'the/dog/jumps/high/tabs/someSubNav/tab-two/taco/burrito/tabs/thirdNav/tab-three/banana/apple/orange';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(3);
expect(result[0]).toEqual('the/dog/jumps/high');
expect(result[1]).toEqual('tabs/someSubNav/tab-two/taco/burrito');
expect(result[2]).toEqual('tabs/thirdNav/tab-three/banana/apple/orange');
});
it('should handle a mixed url', () => {
const url = 'the/dog/jumps/high/tabs/someSubNav/tab-two/taco/burrito/nav/thirdNav/banana/apple/orange';
const result = urlToNavGroupStringsTwo(url);
expect(result.length).toEqual(3);
expect(result[0]).toEqual('the/dog/jumps/high');
expect(result[1]).toEqual('tabs/someSubNav/tab-two/taco/burrito');
expect(result[2]).toEqual('nav/thirdNav/banana/apple/orange');
});
});
describe('convertUrlToDehydratedSegments', () => {
it('it should return a vanilla single segment', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three' };
const links = normalizeLinks([link1, link2, link3]);
const url = 'view-two';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(1);
expect(segmentPairs[0].segments.length).toEqual(1);
expect(segmentPairs[0].segments[0].id).toEqual('view-two');
});
it('it should return a data-driven single segment', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/paramOne/:paramOne/paramTwo/:paramTwo' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three' };
const links = normalizeLinks([link1, link2, link3]);
const url = 'view-two/paramOne/taco/paramTwo/burrito';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(1);
expect(segmentPairs[0].segments.length).toEqual(1);
expect(segmentPairs[0].segments[0].id).toEqual('view-two/paramOne/taco/paramTwo/burrito');
expect(segmentPairs[0].segments[0].data.paramOne).toEqual('taco');
expect(segmentPairs[0].segments[0].data.paramTwo).toEqual('burrito');
});
it('it should return a vanilla set of segments', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three' };
const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' };
const links = normalizeLinks([link1, link2, link3, link4]);
const url = 'view-two/view-one/view-three';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(1);
expect(segmentPairs[0].segments.length).toEqual(3);
expect(segmentPairs[0].segments[0].id).toEqual('view-two');
expect(segmentPairs[0].segments[1].id).toEqual('view-one');
expect(segmentPairs[0].segments[2].id).toEqual('view-three');
});
it('it should return a data-driven set of segments', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' };
const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' };
const links = normalizeLinks([link1, link2, link3, link4]);
const url = 'view-two/user/fred/view-one/paramOne/taco/paramTwo/burrito/view-three/12345';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(1);
expect(segmentPairs[0].segments.length).toEqual(3);
expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred');
expect(segmentPairs[0].segments[0].data.userId).toEqual('fred');
expect(segmentPairs[0].segments[1].id).toEqual('view-one/paramOne/taco/paramTwo/burrito');
expect(segmentPairs[0].segments[1].data.paramOne).toEqual('taco');
expect(segmentPairs[0].segments[1].data.paramTwo).toEqual('burrito');
expect(segmentPairs[0].segments[2].id).toEqual('view-three/12345');
expect(segmentPairs[0].segments[2].data.itemId).toEqual('12345');
});
it('it should return a data-driven set of segments with a root nav prefix', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' };
const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' };
const links = normalizeLinks([link1, link2, link3, link4]);
const url = 'nav/123/view-two/user/fred/view-one/paramOne/taco/paramTwo/burrito/view-three/12345';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(1);
expect(segmentPairs[0].segments.length).toEqual(3);
expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred');
expect(segmentPairs[0].segments[0].data.userId).toEqual('fred');
expect(segmentPairs[0].segments[1].id).toEqual('view-one/paramOne/taco/paramTwo/burrito');
expect(segmentPairs[0].segments[1].data.paramOne).toEqual('taco');
expect(segmentPairs[0].segments[1].data.paramTwo).toEqual('burrito');
expect(segmentPairs[0].segments[2].id).toEqual('view-three/12345');
expect(segmentPairs[0].segments[2].data.itemId).toEqual('12345');
});
it('it should return a data-driven set of segments with multiple nav segments in the middle', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' };
const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' };
const links = normalizeLinks([link1, link2, link3, link4]);
const url = 'view-two/user/fred/nav/123/view-one/paramOne/taco/paramTwo/burrito/nav/456/view-three/12345';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(3);
expect(segmentPairs[0].segments.length).toEqual(1);
expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred');
expect(segmentPairs[0].segments[0].data.userId).toEqual('fred');
expect(segmentPairs[1].segments.length).toEqual(1);
expect(segmentPairs[1].segments[0].id).toEqual('view-one/paramOne/taco/paramTwo/burrito');
expect(segmentPairs[1].segments[0].data.paramOne).toEqual('taco');
expect(segmentPairs[1].segments[0].data.paramTwo).toEqual('burrito');
expect(segmentPairs[2].segments.length).toEqual(1);
expect(segmentPairs[2].segments[0].id).toEqual('view-three/12345');
expect(segmentPairs[2].segments[0].data.itemId).toEqual('12345');
});
it('it should return a data-driven set of segments with a root nav and multiple nav segments in the middle', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' };
const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' };
const links = normalizeLinks([link1, link2, link3, link4]);
const url = 'nav/app/view-two/user/fred/nav/123/view-one/paramOne/taco/paramTwo/burrito/nav/456/view-three/12345';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(3);
expect(segmentPairs[0].segments.length).toEqual(1);
expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred');
expect(segmentPairs[0].segments[0].data.userId).toEqual('fred');
expect(segmentPairs[1].segments.length).toEqual(1);
expect(segmentPairs[1].segments[0].id).toEqual('view-one/paramOne/taco/paramTwo/burrito');
expect(segmentPairs[1].segments[0].data.paramOne).toEqual('taco');
expect(segmentPairs[1].segments[0].data.paramTwo).toEqual('burrito');
expect(segmentPairs[2].segments.length).toEqual(1);
expect(segmentPairs[2].segments[0].id).toEqual('view-three/12345');
expect(segmentPairs[2].segments[0].data.itemId).toEqual('12345');
});
it('it should return a data-driven set of segments with a root tabs and multiple tabs segments in the middle', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' };
const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' };
const links = normalizeLinks([link1, link2, link3, link4]);
const url = 'tabs/app/tab-two/view-two/user/fred/tabs/123/tab-three/view-one/paramOne/taco/paramTwo/burrito/tabs/456/tab-four/view-three/12345';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(3);
expect(segmentPairs[0].segments.length).toEqual(1);
expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred');
expect(segmentPairs[0].segments[0].data.userId).toEqual('fred');
expect(segmentPairs[1].segments.length).toEqual(1);
expect(segmentPairs[1].segments[0].id).toEqual('view-one/paramOne/taco/paramTwo/burrito');
expect(segmentPairs[1].segments[0].data.paramOne).toEqual('taco');
expect(segmentPairs[1].segments[0].data.paramTwo).toEqual('burrito');
expect(segmentPairs[2].segments.length).toEqual(1);
expect(segmentPairs[2].segments[0].id).toEqual('view-three/12345');
expect(segmentPairs[2].segments[0].data.itemId).toEqual('12345');
});
it('should return a data-driven set of segments where the root tabs is implied but the secondary identifier is actually grabed', () => {
const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' };
const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' };
const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' };
const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' };
const links = normalizeLinks([link1, link2, link3, link4]);
const url = 'tab-two/view-two/user/fred/tab-three/view-one/paramOne/taco/paramTwo/burrito/tab-four/view-three/12345';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(1);
expect(segmentPairs[0].segments.length).toEqual(3);
expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred');
expect(segmentPairs[0].segments[0].name).toEqual('viewtwo');
expect(segmentPairs[0].segments[0].secondaryId).toEqual('tab-two');
expect(segmentPairs[0].segments[0].data.userId).toEqual('fred');
expect(segmentPairs[0].segments[1].id).toEqual('view-one/paramOne/taco/paramTwo/burrito');
expect(segmentPairs[0].segments[1].name).toEqual('viewone');
expect(segmentPairs[0].segments[1].data.paramOne).toEqual('taco');
expect(segmentPairs[0].segments[1].data.paramTwo).toEqual('burrito');
expect(segmentPairs[0].segments[1].secondaryId).toEqual('tab-three');
expect(segmentPairs[0].segments[2].id).toEqual('view-three/12345');
expect(segmentPairs[0].segments[2].name).toEqual('viewthree');
expect(segmentPairs[0].segments[2].data.itemId).toEqual('12345');
expect(segmentPairs[0].segments[2].secondaryId).toEqual('tab-four');
});
describe('convertUrlToSegments', () => {
it('it should return a vanilla single segment', () => {
const link1 = { component: MockView1, name: 'login-page', segment: 'login-page' };
const link2 = { component: MockView1, name: 'settings-page', segment: 'settings-page' };
const link3 = { component: MockView1, name: 'details-page', segment: 'details-page' };
const mockNav = mockNavController();
serializer._app.registerRootNav(mockNav);
const links = normalizeLinks([link1, link2, link3]);
const url = 'settings-page';
const segments = convertUrlToSegments(serializer._app, url, links);
expect(segments.length).toEqual(1);
expect(segments[0].type).toEqual('nav');
expect(segments[0].navId).toEqual(mockNav.id);
});
});
});
var serializer: UrlSerializer;
beforeEach(() => {
@@ -778,5 +827,5 @@ class NotFound {}
function mockSerializer(navLinks?: NavLink[]) {
let deepLinkConfig = mockDeepLinkConfig(navLinks);
return new UrlSerializer(deepLinkConfig);
return new UrlSerializer(mockApp(), deepLinkConfig);
}

View File

@@ -1,7 +1,8 @@
import { OpaqueToken } from '@angular/core';
import { App } from '../components/app/app';
import { NavigationContainer } from './navigation-container';
import { DeepLinkConfig, NavLink, NavSegment } from './nav-util';
import { DeepLinkConfig, DehydratedSegment, DehydratedSegmentPair, NavGroup, NavLink, NavSegment } from './nav-util';
import { isArray, isBlank, isPresent } from '../util/util';
@@ -11,7 +12,7 @@ import { isArray, isBlank, isPresent } from '../util/util';
export class UrlSerializer {
links: NavLink[];
constructor(config: DeepLinkConfig) {
constructor(public _app: App, config: DeepLinkConfig) {
if (config && isArray(config.links)) {
this.links = normalizeLinks(config.links);
@@ -31,9 +32,7 @@ export class UrlSerializer {
// trim off data after ? and #
browserUrl = browserUrl.split('?')[0].split('#')[0];
const navGroupStrings = urlToNavGroupStrings(browserUrl);
const navGroups = navGroupStringtoObjects(navGroupStrings);
return parseUrlParts(navGroups, this.links);
return convertUrlToSegments(this._app, browserUrl, this.links);
}
@@ -41,7 +40,7 @@ export class UrlSerializer {
createSegmentFromName(navContainer: NavigationContainer, nameOrComponent: any): NavSegment {
const configLink = this.getLinkFromName(nameOrComponent);
if (configLink) {
return this._createSegment({ navId: navContainer.id, secondaryId: navContainer.getSecondaryIdentifier(), type: 'tabs'}, configLink, null);
return this._createSegment(this._app, navContainer, configLink, null);
}
return null;
}
@@ -63,9 +62,16 @@ export class UrlSerializer {
}
const sections = segments.map(segment => {
if (segment.type === 'tabs') {
return `/${segment.type}/${segment.navId}/${segment.secondaryId}/${segment.id}`;
if (segment.requiresExplicitNavPrefix) {
return `/${segment.type}/${segment.navId}/${segment.secondaryId}/${segment.id}`;
}
return `/${segment.secondaryId}/${segment.id}`;
}
return `/${segment.type}/${segment.navId}/${segment.id}`;
// it's a nav
if (segment.requiresExplicitNavPrefix) {
return `/${segment.type}/${segment.navId}/${segment.id}`;
}
return `/${segment.id}`;
});
return sections.join('');
}
@@ -73,18 +79,20 @@ export class UrlSerializer {
/**
* Serializes a component and its data into a NavSegment.
*/
serializeComponent(navGroup: NavGroup, component: any, data: any): NavSegment {
serializeComponent(navContainer: NavigationContainer, component: any, data: any): NavSegment {
if (component) {
const link = findLinkByComponentData(this.links, component, data);
if (link) {
return this._createSegment(navGroup, link, data);
return this._createSegment(this._app, navContainer, link, data);
}
}
return null;
}
/** @internal */
_createSegment(navGroup: NavGroup, configLink: NavLink, data: any): NavSegment {
/**
* @internal
*/
_createSegment(app: App, navContainer: NavigationContainer, configLink: NavLink, data: any): NavSegment {
let urlParts = configLink.segmentParts;
if (isPresent(data)) {
@@ -110,6 +118,14 @@ export class UrlSerializer {
}
}
let requiresExplicitPrefix = true;
if (navContainer.parent) {
requiresExplicitPrefix = navContainer.parent && navContainer.parent.getAllChildNavs().length > 1;
} else {
// if it's a root nav, and there are multiple root navs, we need an explicit prefix
requiresExplicitPrefix = app.getRootNavById(navContainer.id) && app.getRootNavs().length > 1;
}
return {
id: urlParts.join('/'),
name: configLink.name,
@@ -117,9 +133,10 @@ export class UrlSerializer {
loadChildren: configLink.loadChildren,
data: data,
defaultHistory: configLink.defaultHistory,
navId: navGroup.navId,
type: navGroup.type,
secondaryId: navGroup.secondaryId
navId: navContainer.name || navContainer.id,
type: navContainer.getType(),
secondaryId: navContainer.getSecondaryIdentifier(),
requiresExplicitNavPrefix: requiresExplicitPrefix
};
}
}
@@ -141,39 +158,6 @@ export function formatUrlPart(name: string): string {
return encodeURIComponent(name);
}
export const parseUrlParts = (navGroups: NavGroup[], configLinks: NavLink[]): NavSegment[] => {
const segments: NavSegment[] = [];
for (const link of configLinks) {
for (const navGroup of navGroups) {
if (link.segmentPartsLen === navGroup.segmentPieces.length) {
// check if the segment pieces are a match
let allSegmentsMatch = true;
for (let i = 0; i < navGroup.segmentPieces.length; i++) {
if (!isPartMatch(navGroup.segmentPieces[i], link.segmentParts[i])) {
allSegmentsMatch = false;
break;
}
}
// sweet, we found a match!
if (allSegmentsMatch) {
segments.push({
id: link.segmentParts.join('/'),
name: link.name,
component: link.component,
loadChildren: link.loadChildren,
data: createMatchedData(navGroup.segmentPieces, link),
defaultHistory: link.defaultHistory,
navId: navGroup.navId,
type: navGroup.type,
secondaryId: navGroup.secondaryId
});
}
}
}
}
return segments;
};
export const isPartMatch = (urlPart: string, configLinkPart: string) => {
if (isPresent(urlPart) && isPresent(configLinkPart)) {
if (configLinkPart.charAt(0) === ':') {
@@ -300,8 +284,8 @@ const URL_REPLACE_REG = /\s+|\?|\!|\$|\,|\.|\+|\"|\'|\*|\^|\||\/|\\|\[|\]|#|%|`|
*/
export const DeepLinkConfigToken = new OpaqueToken('USERLINKS');
export function setupUrlSerializer(userDeepLinkConfig: any): UrlSerializer {
return new UrlSerializer(userDeepLinkConfig);
export function setupUrlSerializer(app: App, userDeepLinkConfig: any): UrlSerializer {
return new UrlSerializer(app, userDeepLinkConfig);
}
export function urlToNavGroupStrings(url: string): string[] {
@@ -334,20 +318,227 @@ export function navGroupStringtoObjects(navGroupStrings: string[]): NavGroup[] {
secondaryId: null,
segmentPieces: sections.splice(2)
};
} else if (sections[0] === 'tabs') {
return {
type: 'tabs',
navId: sections[1],
niceId: sections[1],
secondaryId: sections[2],
segmentPieces: sections.splice(3)
};
}
return {
type: 'tabs',
navId: sections[1],
niceId: sections[1],
secondaryId: sections[2],
segmentPieces: sections.splice(3)
type: null,
navId: null,
niceId: null,
secondaryId: null,
segmentPieces: sections
};
});
}
export interface NavGroup {
type: string;
navId: string;
secondaryId: string;
segmentPieces?: string[];
export function urlToNavGroupStringsTwo(url: string) {
const tokens = url.split('/');
const keywordIndexes = [];
for (let i = 0; i < tokens.length; i++) {
if (i !== 0 && (tokens[i] === 'nav' || tokens[i] === 'tabs')) {
keywordIndexes.push(i);
}
}
// append the last index + 1 to the list no matter what
keywordIndexes.push(tokens.length);
const groupings: string[] = [];
let activeKeywordIndex = 0;
let tmpArray: string[] = [];
for (let i = 0; i < tokens.length; i++) {
if (i >= keywordIndexes[activeKeywordIndex]) {
groupings.push(tmpArray.join('/'));
tmpArray = [];
activeKeywordIndex++;
}
tmpArray.push(tokens[i]);
}
// okay, after the loop we've gotta push one more time just to be safe
groupings.push(tmpArray.join('/'));
return groupings;
}
export function convertUrlToSegments(app: App, url: string, navLinks: NavLink[]): NavSegment[] {
const pairs = convertUrlToDehydratedSegments(url, navLinks);
return hydrateSegmentsWithNav(app, pairs);
}
export function convertUrlToDehydratedSegments(url: string, navLinks: NavLink[]): DehydratedSegmentPair[] {
const navGroupStrings = urlToNavGroupStringsTwo(url);
const navGroups = navGroupStringtoObjects(navGroupStrings);
return getSegmentsFromNavGroups(navGroups, navLinks);
}
export function hydrateSegmentsWithNav(app: App, dehydratedSegmentPairs: DehydratedSegmentPair[]) {
const segments: NavSegment[] = [];
for (let i = 0; i < dehydratedSegmentPairs.length; i++) {
let navs = getNavFromNavGroup(dehydratedSegmentPairs[i].navGroup, app);
// okay, cool, let's walk through the segments and hydrate them
for (const dehydratedSegment of dehydratedSegmentPairs[i].segments) {
if (navs.length === 1) {
segments.push(hydrateSegment(dehydratedSegment, navs[0]));
} else if (navs.length > 1 || navs.length <= 0) {
break;
// throw new Error('Invalid URL - could not determine which nav to use');
}
navs = navs[0].getActiveChildNavs();
}
}
return segments;
}
export function getNavFromNavGroup(navGroup: NavGroup, app: App): NavigationContainer[] {
if (navGroup.navId) {
const rootNav = app.getNavByIdOrName(navGroup.navId);
if (rootNav) {
return [rootNav];
}
return [];
}
// we don't know what nav to use, so just use the root nav.
// if there is more than one root nav, throw an error
return app.getRootNavs();
}
/*
* Let's face the facts: Getting a dehydrated segment from the url is really hard
* because we need to do a ton of crazy looping
* the are chunks of a url that are totally irrelevant at this stage, such as the secondary identifier
* stating which tab is selected, etc.
* but is necessary. Ultimately, let's say that we have a url that looks like this
* /the/dog/jumps/high
* We need to check for a segment match this looping pattern
* the
* the dog
* the dog jumps
* the dog jumps high
* dog
* dog jumps
* dog jumps high
* jumps
* jumps high
* high
**/
export function getSegmentsFromNavGroups(navGroups: NavGroup[], navLinks: NavLink[]) {
const pairs: DehydratedSegmentPair[] = [];
for (const navGroup of navGroups) {
const segments: DehydratedSegment[] = [];
// loop over each nav segment piece
for (let i = 0; i < navGroup.segmentPieces.length; i++) {
// do a second loop the length - i times
let created = false;
for (let j = 0; j < navGroup.segmentPieces.length - i; j++) {
// grab the subsection of the url to match the pattern above
const endIndex = 1 + i + j;
const subsetOfUrl = navGroup.segmentPieces.slice(i, endIndex);
for (const navLink of navLinks) {
const segment = getSegmentsFromUrlPieces(subsetOfUrl, navLink);
if (segment) {
created = true;
// sweet, we found a segment
segments.push(segment);
// now we want to null out the url subsection in the segmentPieces
for (let k = i; k < endIndex; k++) {
navGroup.segmentPieces[k] = null;
}
break;
}
}
}
if (!created && navGroup.segmentPieces[i]) {
// this is very likely a tab's secondary identifier
segments.push({
id: null,
name: null,
secondaryId: navGroup.segmentPieces[i],
component: null,
loadChildren: null,
data: null,
defaultHistory: null
});
}
}
segments.forEach(segment => console.log('segment: ', segment));
// okay, this is the lazy persons approach here.
// so here's the deal! Right now if section of the url is not a part of a segment
// it is almost certainly the secondaryId for a tabs component
// basically, knowing the segment for the `tab` itself is good, but we also need to know
// which tab is selected, so we have an identifer in the url that is associated with the tabs component
// telling us which tab is selected. With that in mind, we are going to go through and find the segments with only secondary identifiers,
// and simply add the secondaryId to the next segment, and then remove the empty segment from the list
for (let i = 0; i < segments.length; i++) {
if (segments[i].secondaryId && !segments[i].id && ((i + 1) <= segments.length - 1)) {
segments[i + 1].secondaryId = segments[i].secondaryId;
segments[i] = null;
}
}
const cleanedSegments = segments.filter(segment => !!segment);
// if the nav group has a secondary id, make sure the first segment also has it set
if (navGroup.secondaryId && segments.length) {
cleanedSegments[0].secondaryId = navGroup.secondaryId;
}
pairs.push({
navGroup: navGroup,
segments: cleanedSegments
});
}
return pairs;
}
export function getSegmentsFromUrlPieces(urlSections: string[], navLink: NavLink): DehydratedSegment {
if (navLink.segmentPartsLen !== urlSections.length) {
return null;
}
for (let i = 0; i < urlSections.length; i++) {
if (!isPartMatch(urlSections[i], navLink.segmentParts[i])) {
// just return an empty array if the part doesn't match
return null;
}
}
return {
id: urlSections.join('/'),
name: navLink.name,
component: navLink.component,
loadChildren: navLink.loadChildren,
data: createMatchedData(urlSections, navLink),
defaultHistory: navLink.defaultHistory
};
}
export function hydrateSegment(segment: DehydratedSegment, nav: NavigationContainer) {
const hydratedSegment = Object.assign({}, segment) as NavSegment;
hydratedSegment.type = nav.getType();
hydratedSegment.navId = nav.id;
// secondaryId is set on an empty dehydrated segment in the case of tabs to identify which tab is selected
hydratedSegment.secondaryId = segment.secondaryId;
return hydratedSegment;
}
export function getNonHydratedSegmentIfLinkAndUrlMatch(urlChunks: string[], navLink: NavLink): DehydratedSegment {
let allSegmentsMatch = true;
for (let i = 0; i < urlChunks.length; i++) {
if (!isPartMatch(urlChunks[i], navLink.segmentParts[i])) {
allSegmentsMatch = false;
break;
}
}
if (allSegmentsMatch) {
return {
id: navLink.segmentParts.join('/'),
name: navLink.name,
component: navLink.component,
loadChildren: navLink.loadChildren,
data: createMatchedData(urlChunks, navLink),
defaultHistory: navLink.defaultHistory
};
}
return null;
}

View File

@@ -221,7 +221,7 @@ export function mockContent(): Content {
}
export function mockZone(): NgZone {
return new NgZone(false);
return new NgZone({enableLongStackTrace: false});
}
export function mockChangeDetectorRef(): ChangeDetectorRef {
@@ -390,8 +390,8 @@ export function mockComponentRef(): ComponentRef<any> {
}
export function mockDeepLinker(linkConfig: DeepLinkConfig = null, app?: App) {
let serializer = new UrlSerializer(linkConfig);
app = app || mockApp(mockConfig(), mockPlatform());
let serializer = new UrlSerializer(app, linkConfig);
let location = mockLocation();
return new DeepLinker(app || mockApp(), serializer, location, null, null);
@@ -449,7 +449,7 @@ export function mockOverlayPortal(app: App, config: Config, plt: MockPlatform):
let renderer = mockRenderer();
let componentFactoryResolver: any = null;
let gestureCtrl = new GestureController(app);
let serializer = new UrlSerializer(null);
let serializer = new UrlSerializer(app, null);
let location = mockLocation();
let deepLinker = new DeepLinker(app, serializer, location, null, null);