Skip to content

Commit 92f9762

Browse files
committed
Merge pull request NativeScript#109 from NativeScript/feature/tests
Structural directives tests + TestApp
2 parents c433502 + 42bbb11 commit 92f9762

File tree

3 files changed

+197
-81
lines changed

3 files changed

+197
-81
lines changed

tests/app/tests/renderer-tests.ts

Lines changed: 93 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,27 @@
11
//make sure you import mocha-config before angular2/core
22
import {assert} from "./test-config";
3-
import {bootstrap} from "../nativescript-angular/application";
43
import {
5-
Type,
64
Component,
7-
ComponentRef,
8-
DynamicComponentLoader,
9-
ViewChild,
105
ElementRef,
11-
provide
126
} from "angular2/core";
13-
import {View} from "ui/core/view";
14-
import * as background from "ui/styling/background";
15-
import {StackLayout} from "ui/layouts/stack-layout";
16-
import {GridLayout} from "ui/layouts/grid-layout";
17-
import {LayoutBase} from "ui/layouts/layout-base";
187
import {ProxyViewContainer} from "ui/proxy-view-container";
19-
import {topmost} from 'ui/frame';
20-
import {APP_ROOT_VIEW} from "../nativescript-angular/platform-providers";
218
import {Red} from "color/known-colors";
22-
23-
@Component({
24-
selector: 'my-app',
25-
template: `<StackLayout #loadSite></StackLayout>`
26-
})
27-
export class App {
28-
@ViewChild("loadSite") public loadSiteRef: ElementRef;
29-
30-
constructor(public loader: DynamicComponentLoader,
31-
public elementRef: ElementRef) {
32-
}
33-
}
9+
import {dumpView} from "./test-utils";
10+
import {TestApp} from "./test-app";
3411

3512
@Component({
3613
template: `<StackLayout><Label text="Layout"></Label></StackLayout>`
3714
})
3815
export class LayoutWithLabel {
39-
constructor(public elementRef: ElementRef){}
16+
constructor(public elementRef: ElementRef) { }
4017
}
4118

4219
@Component({
4320
selector: "label-cmp",
4421
template: `<Label text="Layout"></Label>`
4522
})
4623
export class LabelCmp {
47-
constructor(public elementRef: ElementRef){
24+
constructor(public elementRef: ElementRef) {
4825
}
4926
}
5027

@@ -53,15 +30,15 @@ export class LabelCmp {
5330
template: `<GridLayout><label-cmp></label-cmp></GridLayout>`
5431
})
5532
export class LabelContainer {
56-
constructor(public elementRef: ElementRef){}
33+
constructor(public elementRef: ElementRef) { }
5734
}
5835

5936
@Component({
6037
selector: "projectable-cmp",
6138
template: `<StackLayout><ng-content></ng-content></StackLayout>`
6239
})
6340
export class ProjectableCmp {
64-
constructor(public elementRef: ElementRef){
41+
constructor(public elementRef: ElementRef) {
6542
}
6643
}
6744
@Component({
@@ -71,7 +48,7 @@ export class ProjectableCmp {
7148
</GridLayout>`
7249
})
7350
export class ProjectionContainer {
74-
constructor(public elementRef: ElementRef){}
51+
constructor(public elementRef: ElementRef) { }
7552
}
7653

7754
@Component({
@@ -82,90 +59,125 @@ export class ProjectionContainer {
8259
template: `<Label text="Styled!"></Label>`
8360
})
8461
export class StyledLabelCmp {
85-
constructor(public elementRef: ElementRef){
62+
constructor(public elementRef: ElementRef) {
8663
}
8764
}
8865

89-
describe('Renderer E2E', () => {
90-
let appComponent: App = null;
91-
let _pendingDispose: ComponentRef[] = [];
66+
@Component({
67+
selector: "ng-if-label",
68+
template: `<Label *ngIf="show" text="iffed"></Label>`
69+
})
70+
export class NgIfLabel {
71+
public show: boolean = false;
72+
constructor(public elementRef: ElementRef) {
73+
}
74+
}
9275

93-
function loadComponent(type: Type): Promise<ComponentRef> {
94-
return appComponent.loader.loadIntoLocation(type, appComponent.elementRef, "loadSite").then((componentRef) => {
95-
_pendingDispose.push(componentRef);
96-
return componentRef;
97-
});
76+
@Component({
77+
selector: "ng-for-label",
78+
template: `<Label *ngFor="#item of items" [text]="item"></Label>`
79+
})
80+
export class NgForLabel {
81+
public items: Array<string> = ["one", "two", "three"];
82+
constructor(public elementRef: ElementRef) {
9883
}
84+
}
9985

100-
afterEach(() => {
101-
while (_pendingDispose.length > 0) {
102-
const componentRef = _pendingDispose.pop()
103-
componentRef.dispose();
104-
}
105-
});
86+
87+
describe('Renderer E2E', () => {
88+
let testApp: TestApp = null;
10689

10790
before(() => {
108-
//bootstrap the app in a custom location
109-
const page = topmost().currentPage;
110-
const rootLayout = <LayoutBase>page.content;
111-
const viewRoot = new StackLayout();
112-
rootLayout.addChild(viewRoot);
113-
GridLayout.setRow(rootLayout, 50);
114-
const rootViewProvider = provide(APP_ROOT_VIEW, {useFactory: () => viewRoot});
115-
return bootstrap(App, [rootViewProvider]).then((componentRef) => {
116-
appComponent = componentRef.instance;
117-
});
91+
return TestApp.create().then((app) => {
92+
testApp = app;
93+
})
94+
});
95+
96+
after(() => {
97+
testApp.dispose();
98+
});
99+
100+
afterEach(() => {
101+
testApp.disposeComponenets();
118102
});
119103

120104
it("component with a layout", () => {
121-
return loadComponent(LayoutWithLabel).then((componentRef) => {
105+
return testApp.loadComponent(LayoutWithLabel).then((componentRef) => {
122106
const componentRoot = componentRef.instance.elementRef.nativeElement;
123107
assert.equal("(ProxyViewContainer (StackLayout (Label)))", dumpView(componentRoot));
124108
});
125109
});
126110

127111
it("component without a layout", () => {
128-
return loadComponent(LabelContainer).then((componentRef) => {
112+
return testApp.loadComponent(LabelContainer).then((componentRef) => {
129113
const componentRoot = componentRef.instance.elementRef.nativeElement;
130114
assert.equal("(ProxyViewContainer (GridLayout (ProxyViewContainer (Label))))", dumpView(componentRoot));
131115
});
132116
});
133117

134118
it("projects content into components", () => {
135-
return loadComponent(ProjectionContainer).then((componentRef) => {
119+
return testApp.loadComponent(ProjectionContainer).then((componentRef) => {
136120
const componentRoot = componentRef.instance.elementRef.nativeElement;
137121
assert.equal("(ProxyViewContainer (GridLayout (ProxyViewContainer (StackLayout (Button)))))", dumpView(componentRoot));
138122
});
139123
});
140124

141125
it("applies component styles", () => {
142-
return loadComponent(StyledLabelCmp).then((componentRef) => {
126+
return testApp.loadComponent(StyledLabelCmp).then((componentRef) => {
143127
const componentRoot = componentRef.instance.elementRef.nativeElement;
144128
const label = (<ProxyViewContainer>componentRoot).getChildAt(0);
145129
assert.equal(Red, label.style.color.hex);
146130
});
147131
});
148132

149-
});
133+
describe("Structural directives", () => {
134+
it("ngIf hides component when false", () => {
135+
return testApp.loadComponent(NgIfLabel).then((componentRef) => {
136+
const componentRoot = componentRef.instance.elementRef.nativeElement;
137+
assert.equal("(ProxyViewContainer (template))", dumpView(componentRoot));
138+
});
139+
});
150140

151-
function dumpView(view: View): string {
152-
let nodeName = (<any>view).nodeName
153-
if (!nodeName) {
154-
nodeName = (<any>view.constructor).name + '!';
155-
}
156-
let output = ["(", nodeName, " "];
157-
(<any>view)._eachChildView((child) => {
158-
const childDump = dumpView(child);
159-
output.push(childDump);
160-
output.push(", ");
161-
return true;
162-
});
163-
if (output[output.length - 1] == ", ") {
164-
output.pop();
165-
}
166-
if (output[output.length - 1] == " ") {
167-
output.pop();
168-
}
169-
output.push(")");
170-
return output.join("");
171-
}
141+
it("ngIf show component when true", () => {
142+
return testApp.loadComponent(NgIfLabel).then((componentRef) => {
143+
const component = <NgIfLabel>componentRef.instance;
144+
const componentRoot = component.elementRef.nativeElement;
145+
146+
component.show = true;
147+
testApp.appRef.tick();
148+
assert.equal("(ProxyViewContainer (template), (Label))", dumpView(componentRoot));
149+
});
150+
})
151+
152+
it("ngFor creates element for each item", () => {
153+
return testApp.loadComponent(NgForLabel).then((componentRef) => {
154+
const componentRoot = componentRef.instance.elementRef.nativeElement;
155+
assert.equal("(ProxyViewContainer (template), (Label[text=one]), (Label[text=two]), (Label[text=three]))", dumpView(componentRoot, true));
156+
});
157+
});
158+
159+
it("ngFor updates when item is removed", () => {
160+
return testApp.loadComponent(NgForLabel).then((componentRef) => {
161+
const component = <NgForLabel>componentRef.instance;
162+
const componentRoot = component.elementRef.nativeElement;
163+
164+
component.items.splice(1, 1);
165+
testApp.appRef.tick();
166+
167+
assert.equal("(ProxyViewContainer (template), (Label[text=one]), (Label[text=three]))", dumpView(componentRoot, true));
168+
});
169+
});
170+
171+
it("ngFor updates when item is inserted", () => {
172+
return testApp.loadComponent(NgForLabel).then((componentRef) => {
173+
const component = <NgForLabel>componentRef.instance;
174+
const componentRoot = component.elementRef.nativeElement;
175+
176+
component.items.splice(1, 0, "new");
177+
testApp.appRef.tick();
178+
179+
assert.equal("(ProxyViewContainer (template), (Label[text=one]), (Label[text=new]), (Label[text=two]), (Label[text=three]))", dumpView(componentRoot, true));
180+
});
181+
});
182+
})
183+
})

tests/app/tests/test-app.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//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
12+
} from "angular2/core";
13+
14+
import {View} from "ui/core/view";
15+
import {StackLayout} from "ui/layouts/stack-layout";
16+
import {GridLayout} from "ui/layouts/grid-layout";
17+
import {LayoutBase} from "ui/layouts/layout-base";
18+
import {topmost} from 'ui/frame';
19+
import {APP_ROOT_VIEW} from "../nativescript-angular/platform-providers";
20+
21+
@Component({
22+
selector: 'my-app',
23+
template: `<StackLayout #loadSite></StackLayout>`
24+
})
25+
export class TestApp {
26+
@ViewChild("loadSite") public loadSiteRef: ElementRef;
27+
private _pageRoot: LayoutBase;
28+
private _appRoot: StackLayout;
29+
private _pendingDispose: ComponentRef[] = [];
30+
31+
constructor(public loader: DynamicComponentLoader,
32+
public elementRef: ElementRef,
33+
public appRef: ApplicationRef) {
34+
}
35+
36+
public loadComponent(type: Type): Promise<ComponentRef> {
37+
return this.loader.loadIntoLocation(type, this.elementRef, "loadSite").then((componentRef) => {
38+
this._pendingDispose.push(componentRef);
39+
this.appRef.tick();
40+
return componentRef;
41+
});
42+
}
43+
44+
public disposeComponenets() {
45+
while (this._pendingDispose.length > 0) {
46+
const componentRef = this._pendingDispose.pop()
47+
componentRef.dispose();
48+
}
49+
}
50+
51+
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+
});
64+
}
65+
66+
public dispose() {
67+
if (!this._appRoot) {
68+
throw new Error("Test app already disposed or not initalized.");
69+
}
70+
this.disposeComponenets();
71+
this._pageRoot.removeChild(this._appRoot);
72+
this._appRoot = null;
73+
this._pageRoot = null;
74+
}
75+
}

tests/app/tests/test-utils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {View} from "ui/core/view";
2+
import {TextBase} from "ui/text-base";
3+
4+
function getChildren(view: View): Array<View> {
5+
var children: Array<View> = [];
6+
(<any>view)._eachChildView((child) => {
7+
children.push(child);
8+
return true;
9+
});
10+
return children;
11+
}
12+
13+
export function dumpView(view: View, verbose: boolean = false): string {
14+
let nodeName = (<any>view).nodeName
15+
let output = ["(", nodeName];
16+
if (verbose) {
17+
if (view instanceof TextBase) {
18+
output.push("[text=", view.text, "]")
19+
}
20+
}
21+
22+
let children = getChildren(view).map((c) => dumpView(c, verbose)).join(", ");
23+
if (children) {
24+
output.push(" ", children);
25+
}
26+
27+
output.push(")");
28+
return output.join("");
29+
}

0 commit comments

Comments
 (0)