Skip to content

Commit 8858f07

Browse files
committed
Tests for router
1 parent 358104a commit 8858f07

File tree

3 files changed

+285
-33
lines changed

3 files changed

+285
-33
lines changed

tests/app/tests/router.ts

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
//make sure you import mocha-config before angular2/core
2+
import {assert} from "./test-config";
3+
import {
4+
Type,
5+
Component,
6+
ComponentRef,
7+
DynamicComponentLoader,
8+
ViewChild,
9+
ElementRef,
10+
provide,
11+
ApplicationRef,
12+
ChangeDetectorRef
13+
} from "angular2/core";
14+
15+
import {ProxyViewContainer} from "ui/proxy-view-container";
16+
import {dumpView} from "./test-utils";
17+
import {bootstrapTestApp, destroyTestApp} from "./test-app";
18+
19+
import {ROUTER_DIRECTIVES, Router, OnActivate, OnDeactivate, CanReuse, OnReuse,
20+
LocationStrategy, RouteParams, ComponentInstruction, RouteConfig, Location } from 'angular2/router';
21+
import {topmost, BackstackEntry} from "ui/frame";
22+
import {Page} from "ui/page";
23+
import {NS_ROUTER_DIRECTIVES, NS_ROUTER_PROVIDERS} from "../nativescript-angular/router/ns-router";
24+
25+
@Component({
26+
template: `<StackLayout><Label text="Layout"></Label></StackLayout>`
27+
})
28+
export class LayoutWithLabel {
29+
constructor(public elementRef: ElementRef) { }
30+
}
31+
32+
33+
const hooksLog = [];
34+
class CompBase implements OnActivate, OnDeactivate {
35+
protected name: string = "";
36+
37+
routerOnActivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
38+
this.log("activate", nextInstruction, prevInstruction);
39+
}
40+
41+
routerOnDeactivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
42+
this.log("deactivate", nextInstruction, prevInstruction);
43+
}
44+
45+
private log(method: string, nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction) {
46+
hooksLog.push(this.name + "." + method + " " + nextInstruction.urlPath + " " + (prevInstruction ? prevInstruction.urlPath : null));
47+
}
48+
}
49+
50+
@Component({
51+
selector: "first-comp",
52+
template: `<Label text="First"></Label>`
53+
})
54+
export class FirstComponent extends CompBase {
55+
protected name = "first";
56+
}
57+
58+
@Component({
59+
selector: "second-comp",
60+
template: `<Label text="Second"></Label>`
61+
})
62+
export class SecondComponent extends CompBase {
63+
protected name = "second";
64+
}
65+
66+
@Component({
67+
selector: "outlet-app",
68+
directives: [ROUTER_DIRECTIVES],
69+
template: `<router-outlet></router-outlet>`
70+
71+
})
72+
@RouteConfig([
73+
{ path: '/first', name: 'First', component: FirstComponent, useAsDefault: true },
74+
{ path: '/second', name: 'Second', component: SecondComponent }
75+
])
76+
export class SimpleOutletCompnenet {
77+
constructor(
78+
public elementRef: ElementRef,
79+
public router: Router,
80+
public location: LocationStrategy) {
81+
}
82+
}
83+
84+
@Component({
85+
selector: "page-outlet-app",
86+
directives: [ROUTER_DIRECTIVES, NS_ROUTER_DIRECTIVES],
87+
template: `<page-router-outlet></page-router-outlet>`
88+
89+
})
90+
@RouteConfig([
91+
{ path: '/first', name: 'First', component: FirstComponent, useAsDefault: true },
92+
{ path: '/second', name: 'Second', component: SecondComponent }
93+
])
94+
export class PageOutletCompnenet {
95+
constructor(
96+
public elementRef: ElementRef,
97+
public router: Router,
98+
public location: LocationStrategy) {
99+
}
100+
}
101+
102+
describe('router-outlet', () => {
103+
let testApp: SimpleOutletCompnenet = null;
104+
105+
beforeEach((done) => {
106+
hooksLog.length = 0;
107+
return bootstrapTestApp(SimpleOutletCompnenet, [NS_ROUTER_PROVIDERS]).then((app: SimpleOutletCompnenet) => {
108+
testApp = app;
109+
setTimeout(done, 0);
110+
});
111+
});
112+
113+
afterEach(() => {
114+
destroyTestApp(testApp)
115+
});
116+
117+
118+
it("loads default path", () => {
119+
assert.equal("(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=First])))", dumpView(testApp.elementRef.nativeElement, true));
120+
});
121+
122+
it("navigates to other component", () => {
123+
return testApp.router.navigateByUrl("/second").then(() => {
124+
assert.equal("(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=Second])))", dumpView(testApp.elementRef.nativeElement, true));
125+
})
126+
});
127+
128+
it("navigates to other component and then comes back", () => {
129+
return testApp.router.navigateByUrl("/second").then(() => {
130+
return testApp.router.navigateByUrl("/first");
131+
}).then(() => {
132+
assert.equal("(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=First])))", dumpView(testApp.elementRef.nativeElement, true));
133+
})
134+
});
135+
136+
it("hooks are fired when navigating", () => {
137+
return testApp.router.navigateByUrl("/second").then(() => {
138+
return testApp.router.navigateByUrl("/first");
139+
}).then(() => {
140+
var expected = [
141+
"first.activate first null",
142+
"first.deactivate second first",
143+
"second.activate second first",
144+
"second.deactivate first second",
145+
"first.activate first second"];
146+
147+
assert.equal(hooksLog.join("|"), expected.join("|"));
148+
})
149+
});
150+
});
151+
152+
describe('page-router-outlet', () => {
153+
let testApp: PageOutletCompnenet = null;
154+
var initialBackstackLength: number;
155+
var initalPage: Page;
156+
157+
before(() => {
158+
initialBackstackLength = topmost().backStack.length;
159+
initalPage = topmost().currentPage;
160+
})
161+
162+
beforeEach((done) => {
163+
hooksLog.length = 0;
164+
165+
bootstrapTestApp(PageOutletCompnenet, [NS_ROUTER_PROVIDERS]).then((app: PageOutletCompnenet) => {
166+
testApp = app;
167+
setTimeout(done, 0);
168+
});
169+
});
170+
171+
afterEach(() => {
172+
destroyTestApp(testApp)
173+
174+
// Ensure navigation to inital page
175+
const backStack = topmost().backStack;
176+
if (backStack.length > initialBackstackLength) {
177+
return goBackToEntry(backStack[initialBackstackLength]);
178+
}
179+
else {
180+
return true;
181+
}
182+
});
183+
184+
function goBackToEntry(entry: BackstackEntry): Promise<any> {
185+
var navPromise = getNavigatedPromise(entry.resolvedPage);
186+
topmost().goBack(entry);
187+
return navPromise;
188+
}
189+
190+
function getNavigatedPromise(page: Page): Promise<any> {
191+
return new Promise((resolve, reject) => {
192+
var callback = () => {
193+
page.off("navigatedTo", callback);
194+
setTimeout(resolve, 0);
195+
}
196+
page.on("navigatedTo", callback)
197+
})
198+
}
199+
200+
it("loads default path", () => {
201+
// App-Root app-componenet first-componenet
202+
// | | |
203+
var expected = "(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=First])))";
204+
assert.equal(expected, dumpView(testApp.elementRef.nativeElement, true));
205+
});
206+
207+
it("navigates to other component", () => {
208+
return testApp.router.navigateByUrl("/second")
209+
.then(() => {
210+
assert.equal("(ProxyViewContainer (Label[text=Second]))", dumpView(topmost().currentPage.content, true));
211+
})
212+
});
213+
214+
it("navigates to other component and then comes back", () => {
215+
return testApp.router.navigateByUrl("/second")
216+
.then(() => {
217+
var navPromise = getNavigatedPromise(initalPage);
218+
testApp.location.back();
219+
return navPromise;
220+
}).then(() => {
221+
assert.equal(topmost().currentPage, initalPage);
222+
assert.equal("(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=First])))", dumpView(testApp.elementRef.nativeElement, true));
223+
})
224+
});
225+
226+
227+
it("hooks are fired when navigating", () => {
228+
return testApp.router.navigateByUrl("/second")
229+
.then(() => {
230+
var navPromise = getNavigatedPromise(initalPage);
231+
testApp.location.back();
232+
return navPromise;
233+
}).then(() => {
234+
var expected = [
235+
"first.activate first null",
236+
"first.deactivate second first",
237+
"second.activate second first",
238+
"second.deactivate first second",
239+
"first.activate first second"];
240+
241+
assert.equal(hooksLog.join("|"), expected.join("|"));
242+
})
243+
});
244+
});

tests/app/tests/test-app.ts

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
//make sure you import mocha-config before angular2/core
2-
import {bootstrap} from "../nativescript-angular/application";
3-
import {
4-
Type,
5-
Component,
6-
ComponentRef,
7-
DynamicComponentLoader,
8-
ViewChild,
9-
ElementRef,
10-
provide,
11-
ApplicationRef
2+
import {bootstrap, ProviderArray} from "../nativescript-angular/application";
3+
import {Type, Component, ComponentRef, DynamicComponentLoader,
4+
ViewChild, ElementRef, provide, ApplicationRef
125
} from "angular2/core";
136

147
import {View} from "ui/core/view";
15-
import {StackLayout} from "ui/layouts/stack-layout";
168
import {GridLayout} from "ui/layouts/grid-layout";
179
import {LayoutBase} from "ui/layouts/layout-base";
1810
import {topmost} from 'ui/frame';
@@ -24,8 +16,6 @@ import {APP_ROOT_VIEW} from "../nativescript-angular/platform-providers";
2416
})
2517
export class TestApp {
2618
@ViewChild("loadSite") public loadSiteRef: ElementRef;
27-
private _pageRoot: LayoutBase;
28-
private _appRoot: StackLayout;
2919
private _pendingDispose: ComponentRef[] = [];
3020

3121
constructor(public loader: DynamicComponentLoader,
@@ -49,27 +39,45 @@ export class TestApp {
4939
}
5040

5141
public static create(): Promise<TestApp> {
52-
const page = topmost().currentPage;
53-
const rootLayout = <LayoutBase>page.content;
54-
const viewRoot = new StackLayout();
55-
rootLayout.addChild(viewRoot);
56-
GridLayout.setRow(rootLayout, 50);
57-
const rootViewProvider = provide(APP_ROOT_VIEW, { useFactory: () => viewRoot });
58-
return bootstrap(TestApp, [rootViewProvider]).then((componentRef) => {
59-
const testApp = <TestApp>componentRef.instance;
60-
testApp._pageRoot = rootLayout;
61-
testApp._appRoot = viewRoot;
62-
return testApp;
63-
});
42+
return bootstrapTestApp(TestApp);
6443
}
6544

6645
public dispose() {
67-
if (!this._appRoot) {
68-
throw new Error("Test app already disposed or not initalized.");
69-
}
7046
this.disposeComponenets();
71-
this._pageRoot.removeChild(this._appRoot);
72-
this._appRoot = null;
73-
this._pageRoot = null;
47+
destroyTestApp(this);
7448
}
75-
}
49+
}
50+
51+
var runningApps = new Map<any, { hostView: LayoutBase, appRoot: GridLayout, appRef: ApplicationRef }>();
52+
53+
export function bootstrapTestApp(appComponentType: any, providers: ProviderArray = []): Promise<any> {
54+
const page = topmost().currentPage;
55+
const rootLayout = <LayoutBase>page.content;
56+
const viewRoot = new GridLayout();
57+
rootLayout.addChild(viewRoot);
58+
GridLayout.setRow(rootLayout, 50);
59+
60+
const rootViewProvider = provide(APP_ROOT_VIEW, { useValue: viewRoot });
61+
return bootstrap(appComponentType, providers.concat(rootViewProvider)).then((componentRef) => {
62+
componentRef.injector.get(ApplicationRef)
63+
const testApp = componentRef.instance;
64+
65+
runningApps.set(testApp, {
66+
hostView: rootLayout,
67+
appRoot: viewRoot,
68+
appRef: componentRef.injector.get(ApplicationRef) });
69+
70+
return testApp;
71+
});
72+
}
73+
74+
export function destroyTestApp(app: any) {
75+
if (!runningApps.has(app)) {
76+
throw new Error("Unable to cleanup app: " + app);
77+
}
78+
79+
var entry = runningApps.get(app);
80+
entry.hostView.removeChild(entry.appRoot);
81+
entry.appRef.dispose();
82+
runningApps.delete(app);
83+
}

tests/app/tests/test-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function getChildren(view: View): Array<View> {
1111
}
1212

1313
export function dumpView(view: View, verbose: boolean = false): string {
14-
let nodeName = (<any>view).nodeName
14+
let nodeName = (<any>view).nodeName || view;
1515
let output = ["(", nodeName];
1616
if (verbose) {
1717
if (view instanceof TextBase) {

0 commit comments

Comments
 (0)