Skip to content
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,4 @@ script:
- cd ./tests
- npm install
- tns platform add android
- tns run android --justlaunch
- tns test android --emulator
- tns test android --emulator --justlaunch
2 changes: 1 addition & 1 deletion ng-sample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"nativescript": {
"id": "org.nativescript.ngsample",
"tns-android": {
"version": "1.6.2"
"version": "1.6.3"
}
}
}
44 changes: 18 additions & 26 deletions src/nativescript-angular/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,59 +34,51 @@ import {NS_DIRECTIVES} from './directives/ns-directives';
import {bootstrap as angularBootstrap} from 'angular2/bootstrap';

import {Page} from 'ui/page';
import {topmost} from 'ui/frame';
import {TextView} from 'ui/text-view';
import application = require('application');

export type ProviderArray = Array<Type | Provider | any[]>;

import {defaultPageProvider} from "./platform-providers";

let _platform = null;

export function bootstrap(appComponentType: any,
customProviders: ProviderArray = null) : Promise<ComponentRef> {
NativeScriptDomAdapter.makeCurrent();

let nativeScriptProviders: ProviderArray = [
NativeScriptRootRenderer,
provide(RootRenderer, {useClass: NativeScriptRootRenderer}),
NativeScriptRenderer,
provide(Renderer, {useClass: NativeScriptRenderer}),
provide(XHR, {useClass: FileSystemXHR}),
provide(ExceptionHandler, {useFactory: () => new ExceptionHandler(DOM, true), deps: []}),
let platformProviders: ProviderArray = [
PLATFORM_COMMON_PROVIDERS,
];

let defaultAppProviders: ProviderArray = [
APPLICATION_COMMON_PROVIDERS,
FORM_PROVIDERS,
provide(PLATFORM_PIPES, {useValue: COMMON_PIPES, multi: true}),
provide(PLATFORM_DIRECTIVES, {useValue: COMMON_DIRECTIVES, multi: true}),
provide(PLATFORM_DIRECTIVES, {useValue: NS_DIRECTIVES, multi: true}),
provide(ExceptionHandler, {useFactory: () => new ExceptionHandler(DOM, true), deps: []}),

APPLICATION_COMMON_PROVIDERS,
defaultPageProvider,
NativeScriptRootRenderer,
provide(RootRenderer, {useClass: NativeScriptRootRenderer}),
NativeScriptRenderer,
provide(Renderer, {useClass: NativeScriptRenderer}),
COMPILER_PROVIDERS,
PLATFORM_COMMON_PROVIDERS,
FORM_PROVIDERS,
];
provide(XHR, {useClass: FileSystemXHR}),
]

var appProviders = [];
var appProviders = [defaultAppProviders];
if (isPresent(customProviders)) {
appProviders.push(customProviders);
}

const pageProvider = provide(Page, {useFactory: getDefaultPage});
appProviders.push(pageProvider);

if (!_platform) {
_platform = platform(nativeScriptProviders);
_platform = platform(platformProviders);
}
return _platform.application(appProviders).bootstrap(appComponentType);
}

function getDefaultPage() {
const frame = topmost();
if (frame) {
return frame.currentPage;
} else {
return null;
}
}

export function nativeScriptBootstrap(appComponentType: any, customProviders?: ProviderArray, appOptions?: any) {
if (appOptions && appOptions.cssFile) {
application.cssFile = appOptions.cssFile;
Expand Down
18 changes: 18 additions & 0 deletions src/nativescript-angular/platform-providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {topmost} from 'ui/frame';
import {Page} from 'ui/page';
import {provide, OpaqueToken} from 'angular2/src/core/di';

export const APP_ROOT_VIEW = new OpaqueToken('App Root View');

export const defaultPageProvider = provide(Page, {useFactory: getDefaultPage});

export function getDefaultPage(): Page {
console.log('getDefaultPage');
console.trace();
const frame = topmost();
if (frame) {
return frame.currentPage;
} else {
return null;
}
}
35 changes: 24 additions & 11 deletions src/nativescript-angular/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {Inject, Injectable} from 'angular2/src/core/di';
import {Inject, Injectable, Optional} from 'angular2/src/core/di';
import {
Renderer,
RootRenderer,
RenderComponentType,
RenderDebugInfo
} from 'angular2/src/core/render/api';
import {APP_ROOT_VIEW} from "./platform-providers";
import {isBlank} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {COMPONENT_VARIABLE, CONTENT_ATTR} from 'angular2/src/platform/dom/dom_renderer';
Expand All @@ -16,14 +17,25 @@ import * as util from "./view-util";
export { rendererTraceCategory } from "./view-util";

@Injectable()
export class NativeScriptRootRenderer extends RootRenderer {
export class NativeScriptRootRenderer implements RootRenderer {
private _rootView: View = null;
constructor(@Optional() @Inject(APP_ROOT_VIEW) rootView: View) {
console.log('root view: ' + rootView);
this._rootView = rootView;
}

private _registeredComponents: Map<string, NativeScriptRenderer> = new Map<string, NativeScriptRenderer>();
private _page: Page;
public get page(): Page {
if (!this._page) {
this._page = <Page>(<any>topmost()).currentPage;

public get rootView(): View {
if (!this._rootView) {
const page = topmost().currentPage;
this._rootView = page.content;
}
return this._page;
return this._rootView;
}

public get page(): Page {
return <Page>this.rootView.page;
}

renderComponent(componentProto: RenderComponentType): Renderer {
Expand Down Expand Up @@ -60,10 +72,11 @@ export class NativeScriptRenderer extends Renderer {
}

selectRootElement(selector: string): util.NgView {
console.log('selectRootElement: ' + selector);
util.traceLog('ROOT');
const page = <util.NgView><any>this.rootRenderer.page;
page.nodeName = 'Page';
return page;
const rootView = <util.NgView><any>this.rootRenderer.rootView;
rootView.nodeName = 'ROOT';
return rootView;
}

createViewRoot(hostElement: util.NgView): util.NgView {
Expand Down Expand Up @@ -189,4 +202,4 @@ export class NativeScriptRenderer extends Renderer {
public listenGlobal(target: string, eventName: string, callback: Function): Function {
throw new Error('Not implemented.');
}
}
}
8 changes: 2 additions & 6 deletions tests/app/tests/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
//stash it here before Angular runs it over...
const realAssert = global.assert;
import "reflect-metadata";
//make sure you import mocha-config before angular2/core
import {assert} from "./test-config";
import {bootstrap} from "../nativescript-angular/application";
import {Component} from "angular2/core";
global.assert = realAssert;
import * as chai from "chai"
declare var assert: typeof chai.assert;

@Component({
template: "<Button text='OHAI'></Button>"
Expand Down
171 changes: 171 additions & 0 deletions tests/app/tests/renderer-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//make sure you import mocha-config before angular2/core
import {assert} from "./test-config";
import {bootstrap} from "../nativescript-angular/application";
import {
Type,
Component,
ComponentRef,
DynamicComponentLoader,
ViewChild,
ElementRef,
provide
} from "angular2/core";
import {View} from "ui/core/view";
import * as background from "ui/styling/background";
import {StackLayout} from "ui/layouts/stack-layout";
import {GridLayout} from "ui/layouts/grid-layout";
import {LayoutBase} from "ui/layouts/layout-base";
import {ProxyViewContainer} from "ui/proxy-view-container";
import {topmost} from 'ui/frame';
import {APP_ROOT_VIEW} from "../nativescript-angular/platform-providers";
import {Red} from "color/known-colors";

@Component({
selector: 'my-app',
template: `<StackLayout #loadSite></StackLayout>`
})
export class App {
@ViewChild("loadSite") public loadSiteRef: ElementRef;

constructor(public loader: DynamicComponentLoader,
public elementRef: ElementRef) {
}
}

@Component({
template: `<StackLayout><Label text="Layout"></Label></StackLayout>`
})
export class LayoutWithLabel {
constructor(public elementRef: ElementRef){}
}

@Component({
selector: "label-cmp",
template: `<Label text="Layout"></Label>`
})
export class LabelCmp {
constructor(public elementRef: ElementRef){
}
}

@Component({
directives: [LabelCmp],
template: `<GridLayout><label-cmp></label-cmp></GridLayout>`
})
export class LabelContainer {
constructor(public elementRef: ElementRef){}
}

@Component({
selector: "projectable-cmp",
template: `<StackLayout><ng-content></ng-content></StackLayout>`
})
export class ProjectableCmp {
constructor(public elementRef: ElementRef){
}
}
@Component({
directives: [ProjectableCmp],
template: `<GridLayout>
<projectable-cmp><Button text="projected"></Button></projectable-cmp>
</GridLayout>`
})
export class ProjectionContainer {
constructor(public elementRef: ElementRef){}
}

@Component({
selector: "styled-label-cmp",
styles: [
"Label { color: red; }",
],
template: `<Label text="Styled!"></Label>`
})
export class StyledLabelCmp {
constructor(public elementRef: ElementRef){
}
}

describe('bootstrap', () => {
let appComponent: App = null;
let _pendingDispose: ComponentRef[] = [];

function loadComponent(type: Type): Promise<ComponentRef> {
return appComponent.loader.loadIntoLocation(type, appComponent.elementRef, "loadSite").then((componentRef) => {
_pendingDispose.push(componentRef);
return componentRef;
});
}

afterEach(() => {
while (_pendingDispose.length > 0) {
const componentRef = _pendingDispose.pop()
componentRef.dispose();
}
});

before(() => {
//bootstrap the app in a custom location
const page = topmost().currentPage;
const rootLayout = <LayoutBase>page.content;
const viewRoot = new StackLayout();
rootLayout.addChild(viewRoot);
GridLayout.setRow(rootLayout, 50);
const rootViewProvider = provide(APP_ROOT_VIEW, {useFactory: () => viewRoot});
return bootstrap(App, [rootViewProvider]).then((componentRef) => {
appComponent = componentRef.instance;
});
});

it("component with a layout", () => {
return loadComponent(LayoutWithLabel).then((componentRef) => {
const componentRoot = componentRef.instance.elementRef.nativeElement;
assert.equal("(ProxyViewContainer (StackLayout (Label)))", dumpView(componentRoot));
});
});

it("component without a layout", () => {
return loadComponent(LabelContainer).then((componentRef) => {
const componentRoot = componentRef.instance.elementRef.nativeElement;
assert.equal("(ProxyViewContainer (GridLayout (ProxyViewContainer (Label))))", dumpView(componentRoot));
});
});

it("projects content into components", () => {
return loadComponent(ProjectionContainer).then((componentRef) => {
const componentRoot = componentRef.instance.elementRef.nativeElement;
assert.equal("(ProxyViewContainer (GridLayout (ProxyViewContainer (StackLayout (Button)))))", dumpView(componentRoot));
});
});

it("applies component styles", () => {
return loadComponent(StyledLabelCmp).then((componentRef) => {
const componentRoot = componentRef.instance.elementRef.nativeElement;
const label = (<ProxyViewContainer>componentRoot).getChildAt(0);
assert.equal(Red, label.style.color.hex);
});
});

});

function dumpView(view: View): string {
let nodeName = (<any>view).nodeName
if (!nodeName) {
nodeName = (<any>view.constructor).name + '!';
}
let output = ["(", nodeName, " "];
(<any>view)._eachChildView((child) => {
const childDump = dumpView(child);
output.push(childDump);
output.push(", ");
return true;
});
if (output[output.length - 1] == ", ") {
output.pop();
}
if (output[output.length - 1] == " ") {
output.pop();
}
output.push(")");
return output.join("");
}
11 changes: 11 additions & 0 deletions tests/app/tests/test-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
global.mocha.setup({
timeout: 20000,
});

const realAssert = global.assert;
import "reflect-metadata";
export * from "angular2/core";
global.assert = realAssert;

import * as chai from "chai"
export var assert: typeof chai.assert = global.assert;
1 change: 1 addition & 0 deletions tests/hooks/after-prepare/nativescript-unit-test-runner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("nativescript-unit-test-runner/lib/after-prepare.js");
Loading