From 3e131f2561e631c98656335098db68f76e4e116b Mon Sep 17 00:00:00 2001 From: vakrilov Date: Mon, 18 Sep 2017 11:20:02 +0300 Subject: [PATCH 01/14] chore(update): Update deps to angular 4.4.1 --- nativescript-angular/package.json | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index 4165ad184..1cafb5883 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -43,27 +43,27 @@ "reflect-metadata": "^0.1.8" }, "peerDependencies": { - "@angular/common": "~4.2.5", - "@angular/compiler": "~4.2.5", - "@angular/core": "~4.2.5", - "@angular/forms": "~4.2.5", - "@angular/http": "~4.2.5", - "@angular/platform-browser": "~4.2.5", - "@angular/router": "~4.2.5", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/router": "~4.4.1", "rxjs": "^5.0.1", "tns-core-modules": "^3.1.0 || >3.3.0-", "zone.js": "^0.8.4" }, "devDependencies": { - "@angular/animations": "~4.2.5", - "@angular/common": "~4.2.5", - "@angular/compiler": "~4.2.5", - "@angular/compiler-cli": "~4.2.5", - "@angular/core": "~4.2.5", - "@angular/forms": "~4.2.5", - "@angular/http": "~4.2.5", - "@angular/platform-browser": "~4.2.5", - "@angular/router": "~4.2.5", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/compiler-cli": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/router": "~4.4.1", "codelyzer": "^3.1.2", "rxjs": "^5.4.2", "tns-core-modules": "next", From c640b7c52c3c25cee15a8aca37dc66417eb4fe40 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Mon, 18 Sep 2017 11:37:09 +0300 Subject: [PATCH 02/14] chore(update): Replace OpaqueTokens with InjectionTokens. --- nativescript-angular/platform-common.ts | 19 +++++++++---------- nativescript-angular/platform-providers.ts | 18 ++++++------------ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 374d9276c..932dfbded 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -18,18 +18,17 @@ import { EventEmitter, Provider, Sanitizer, - OpaqueToken + InjectionToken } from "@angular/core"; -// Work around a TS bug requiring an import of OpaqueToken without using it -if ((global).___TS_UNUSED) { - (() => { - return OpaqueToken; - })(); -} - import { rendererLog, rendererError } from "./trace"; -import { PAGE_FACTORY, PageFactory, defaultPageFactoryProvider, setRootPage } from "./platform-providers"; +import { + PAGE_FACTORY, + PageFactory, + defaultPageFactoryProvider, + setRootPage, + PageFactoryOptions +} from "./platform-providers"; import { start, setCssFileName } from "tns-core-modules/application"; import { topmost, NavigationEntry } from "tns-core-modules/ui/frame"; @@ -82,7 +81,7 @@ export class NativeScriptPlatformRef extends PlatformRef { bootstrapModule( moduleType: Type, compilerOptions: CompilerOptions | CompilerOptions[] = [] - ): Promise> { + ): Promise> { this._bootstrapper = () => this.platform.bootstrapModule(moduleType, compilerOptions); this.bootstrapApp(); diff --git a/nativescript-angular/platform-providers.ts b/nativescript-angular/platform-providers.ts index 466fa54c9..86d47e0a2 100644 --- a/nativescript-angular/platform-providers.ts +++ b/nativescript-angular/platform-providers.ts @@ -1,19 +1,13 @@ +import { InjectionToken } from "@angular/core"; + import { topmost, Frame } from "tns-core-modules/ui/frame"; +import { View } from "tns-core-modules/ui/core/view"; import { Page } from "tns-core-modules/ui/page"; -import { OpaqueToken } from "@angular/core"; import { device, Device } from "tns-core-modules/platform"; -import * as platform from "tns-core-modules/platform"; - -export const APP_ROOT_VIEW = new OpaqueToken("App Root View"); -export const DEVICE = new OpaqueToken("platfrom device"); -export const PAGE_FACTORY = new OpaqueToken("page factory"); -// Work around a TS bug requiring an import of platform.Device without using it -if ((global).___TS_UNUSED) { - (() => { - return platform; - })(); -} +export const APP_ROOT_VIEW = new InjectionToken("App Root View"); +export const DEVICE = new InjectionToken("platform device"); +export const PAGE_FACTORY = new InjectionToken("page factory"); let _rootPageRef: WeakRef; From 3beea35488a88fbaa8bfe6f1c1b39ca9ee614e30 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Mon, 18 Sep 2017 11:41:16 +0300 Subject: [PATCH 03/14] chore(update): Stop passing any arguments to the constructor for ErrorHandler --- nativescript-angular/nativescript.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativescript-angular/nativescript.module.ts b/nativescript-angular/nativescript.module.ts index e5b34007e..305890ab4 100644 --- a/nativescript-angular/nativescript.module.ts +++ b/nativescript-angular/nativescript.module.ts @@ -21,7 +21,7 @@ import { NativeScriptRendererFactory } from "./renderer"; import { DetachedLoader } from "./common/detached-loader"; export function errorHandlerFactory() { - return new ErrorHandler(true); + return new ErrorHandler(); } @NgModule({ From 9c298cc06fe00ca3df1599a2644a27fb256a477e Mon Sep 17 00:00:00 2001 From: vakrilov Date: Mon, 18 Sep 2017 15:04:03 +0300 Subject: [PATCH 04/14] chore(update): Update apps deps --- e2e/renderer/package.json | 18 +++++++++--------- e2e/router/package.json | 16 ++++++++-------- ng-sample/package.json | 18 +++++++++--------- tests/package.json | 18 +++++++++--------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/e2e/renderer/package.json b/e2e/renderer/package.json index f9e33fe7e..eea1df2e7 100644 --- a/e2e/renderer/package.json +++ b/e2e/renderer/package.json @@ -6,18 +6,18 @@ "nativescript": { "id": "org.nativescript.renderer", "tns-android": { - "version": "next" + "version": "3.2.0-2017-9-12-1" } }, "dependencies": { - "@angular/animations": "~4.2.0", - "@angular/common": "~4.2.0", - "@angular/compiler": "~4.2.0", - "@angular/core": "~4.2.0", - "@angular/forms": "~4.2.0", - "@angular/http": "~4.2.0", - "@angular/platform-browser": "~4.2.0", - "@angular/router": "~4.2.0", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/router": "~4.4.1", "nativescript-angular": "file:../../nativescript-angular", "nativescript-intl": "^3.0.0", "reflect-metadata": "~0.1.8", diff --git a/e2e/router/package.json b/e2e/router/package.json index 922c26304..3323d2898 100644 --- a/e2e/router/package.json +++ b/e2e/router/package.json @@ -13,14 +13,14 @@ } }, "dependencies": { - "@angular/animations": "~4.2.0", - "@angular/common": "~4.2.0", - "@angular/compiler": "~4.2.0", - "@angular/core": "~4.2.0", - "@angular/forms": "~4.2.0", - "@angular/http": "~4.2.0", - "@angular/platform-browser": "~4.2.0", - "@angular/router": "~4.2.0", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/router": "~4.4.1", "nativescript-angular": "file:../../nativescript-angular", "nativescript-intl": "^3.0.0", "reflect-metadata": "~0.1.8", diff --git a/ng-sample/package.json b/ng-sample/package.json index 1d07d71b5..6009d22fd 100644 --- a/ng-sample/package.json +++ b/ng-sample/package.json @@ -32,15 +32,15 @@ }, "homepage": "https://github.com/NativeScript/template-hello-world", "dependencies": { - "@angular/animations": "~4.2.4", - "@angular/common": "~4.2.4", - "@angular/compiler": "~4.2.4", - "@angular/core": "~4.2.4", - "@angular/forms": "~4.2.4", - "@angular/http": "~4.2.4", - "@angular/platform-browser": "~4.2.4", - "@angular/platform-browser-dynamic": "~4.2.4", - "@angular/router": "~4.2.4", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/platform-browser-dynamic": "~4.4.1", + "@angular/router": "~4.4.1", "nativescript-angular": "file:../nativescript-angular", "rxjs": "^5.3.0", "tns-core-modules": "next", diff --git a/tests/package.json b/tests/package.json index 89db53ad8..9cfb0bb66 100644 --- a/tests/package.json +++ b/tests/package.json @@ -26,15 +26,15 @@ ], "homepage": "http://nativescript.org", "dependencies": { - "@angular/animations": "~4.2.4", - "@angular/common": "~4.2.4", - "@angular/compiler": "~4.2.4", - "@angular/core": "~4.2.4", - "@angular/forms": "~4.2.4", - "@angular/http": "~4.2.4", - "@angular/platform-browser": "~4.2.4", - "@angular/platform-browser-dynamic": "~4.2.4", - "@angular/router": "~4.2.4", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/platform-browser-dynamic": "~4.4.1", + "@angular/router": "~4.4.1", "nativescript-angular": "../nativescript-angular", "nativescript-unit-test-runner": "^0.3.4", "rxjs": "^5.2.0", From 9c8da72588572f993f00fa1b17ea0cb722e86ac5 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Wed, 20 Sep 2017 16:16:32 +0300 Subject: [PATCH 05/14] feat(http): Support for HttpClinet with "nativescript-angular/http-client" module. --- .../http-client/http-client.module.ts | 26 ++++ nativescript-angular/http-client/index.ts | 1 + .../http-client/ns-http-backend.ts | 88 +++++++++++++ nativescript-angular/http.ts | 35 ----- nativescript-angular/http/http.module.ts | 34 +++++ nativescript-angular/http/index.ts | 1 + nativescript-angular/http/ns-http.ts | 112 ++++++++-------- nativescript-angular/platform-common.ts | 8 ++ ng-sample/app/app.ts | 10 +- ng-sample/app/examples/http-client/data.json | 8 ++ .../examples/http-client/http-client-test.ts | 122 ++++++++++++++++++ 11 files changed, 353 insertions(+), 92 deletions(-) create mode 100644 nativescript-angular/http-client/http-client.module.ts create mode 100644 nativescript-angular/http-client/index.ts create mode 100644 nativescript-angular/http-client/ns-http-backend.ts delete mode 100644 nativescript-angular/http.ts create mode 100644 nativescript-angular/http/http.module.ts create mode 100644 nativescript-angular/http/index.ts create mode 100644 ng-sample/app/examples/http-client/data.json create mode 100644 ng-sample/app/examples/http-client/http-client-test.ts diff --git a/nativescript-angular/http-client/http-client.module.ts b/nativescript-angular/http-client/http-client.module.ts new file mode 100644 index 000000000..ee2703402 --- /dev/null +++ b/nativescript-angular/http-client/http-client.module.ts @@ -0,0 +1,26 @@ +import { NgModule, Optional, Inject, Injectable } from "@angular/core"; + +// IMPORTant: Importing "@angular/common/http" for the first time overwrites the +// global.__extends function. +const cachedExtends = global.__extends; +import { HttpClientModule, HttpBackend } from "@angular/common/http"; +global.__extends = cachedExtends; + +import { NSFileSystem } from "../file-system/ns-file-system"; +import { NsHttpBackEnd } from "./ns-http-backend"; + +@NgModule({ + providers: [ + NSFileSystem, + NsHttpBackEnd, + { provide: HttpBackend, useExisting: NsHttpBackEnd }, + ], + imports: [ + HttpClientModule, + ], + exports: [ + HttpClientModule, + ] +}) +export class NativeScriptHttpClientModule { +} diff --git a/nativescript-angular/http-client/index.ts b/nativescript-angular/http-client/index.ts new file mode 100644 index 000000000..b57798f8f --- /dev/null +++ b/nativescript-angular/http-client/index.ts @@ -0,0 +1 @@ +export * from "./http-client.module"; diff --git a/nativescript-angular/http-client/ns-http-backend.ts b/nativescript-angular/http-client/ns-http-backend.ts new file mode 100644 index 000000000..472f944fe --- /dev/null +++ b/nativescript-angular/http-client/ns-http-backend.ts @@ -0,0 +1,88 @@ +import { Injectable } from "@angular/core"; +import { + HttpHandler, HttpRequest, + HttpEvent, HttpEventType, + XhrFactory, HttpResponse, + HttpErrorResponse, HttpXhrBackend +} from "@angular/common/http"; +import { Observable } from "rxjs/Observable"; +import { Observer } from "rxjs/Observer"; + +import { NSFileSystem } from "../file-system/ns-file-system"; +import { path } from "tns-core-modules/file-system"; + +@Injectable() +export class NsHttpBackEnd extends HttpXhrBackend { + constructor(xhrFactory: XhrFactory, private nsFileSystem: NSFileSystem) { + super(xhrFactory); + } + + handle(req: HttpRequest): Observable> { + let result: Observable>; + + if (isLocalRequest(req.url)) { + result = this.handleLocalRequest(req.url); + } else { + result = super.handle(req); + } + + return result; + } + + private getAbsolutePath(url: string): string { + url = url.replace("~", "").replace("/", ""); + url = path.join(this.nsFileSystem.currentApp().path, url); + return url; + } + + private handleLocalRequest(url: string): Observable> { + url = this.getAbsolutePath(url); + + // request from local app resources + return new Observable((observer: Observer>) => { + if (this.nsFileSystem.fileExists(url)) { + const localFile = this.nsFileSystem.fileFromPath(url); + localFile.readText() + .then((data) => { + try { + const json = JSON.parse(data); + observer.next(createSuccessResponse(url, json, 200)); + observer.complete(); + } catch (error) { + // Even though the response status was 2xx, this is still an error. + // The parse error contains the text of the body that failed to parse. + const errorResult = { error, text: data }; + observer.error(createErrorResponse(url, errorResult, 200)); + } + }, (err: Object) => { + observer.error(createErrorResponse(url, err, 400)); + + }); + } else { + observer.error(createErrorResponse(url, "Not Found", 404)); + } + }); + } +} + +function isLocalRequest(url: string): boolean { + return url.indexOf("~") === 0 || url.indexOf("/") === 0; +} + +function createSuccessResponse(url: string, body: any, status: number): HttpEvent { + return new HttpResponse({ + url, + body, + status, + statusText: "OK" + }); +} + +function createErrorResponse(url: string, body: any, status: number): HttpErrorResponse { + return new HttpErrorResponse({ + url, + error: body, + status, + statusText: "ERROR" + }); +} diff --git a/nativescript-angular/http.ts b/nativescript-angular/http.ts deleted file mode 100644 index 5f6583a02..000000000 --- a/nativescript-angular/http.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - Http, XHRBackend, RequestOptions -} from "@angular/http"; -import { NSXSRFStrategy, NSHttp } from "./http/ns-http"; -import { NSFileSystem } from "./file-system/ns-file-system"; - -import { NgModule } from "@angular/core"; -import { HttpModule, XSRFStrategy } from "@angular/http"; - -export { NSHttp } from "./http/ns-http"; - -export function nsHttpFactory(backend, options, nsFileSystem) { - return new NSHttp(backend, options, nsFileSystem); -} - -export function nsXSRFStrategyFactory() { - return new NSXSRFStrategy(); -} - -@NgModule({ - providers: [ - {provide: XSRFStrategy, useFactory: nsXSRFStrategyFactory}, - NSFileSystem, - {provide: Http, useFactory: nsHttpFactory, - deps: [XHRBackend, RequestOptions, NSFileSystem]} - ], - imports: [ - HttpModule, - ], - exports: [ - HttpModule, - ] -}) -export class NativeScriptHttpModule { -} diff --git a/nativescript-angular/http/http.module.ts b/nativescript-angular/http/http.module.ts new file mode 100644 index 000000000..b6d35172c --- /dev/null +++ b/nativescript-angular/http/http.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from "@angular/core"; +import { Http, XHRBackend, RequestOptions, HttpModule, XSRFStrategy } from "@angular/http"; + +import { NSXSRFStrategy, NSHttp } from "./ns-http"; +import { NSFileSystem } from "../file-system/ns-file-system"; + +export { NSHttp } from "./ns-http"; + +export function nsHttpFactory(backend, options, nsFileSystem) { + return new NSHttp(backend, options, nsFileSystem); +} + +export function nsXSRFStrategyFactory() { + return new NSXSRFStrategy(); +} + +@NgModule({ + providers: [ + { provide: XSRFStrategy, useFactory: nsXSRFStrategyFactory }, + NSFileSystem, + { + provide: Http, useFactory: nsHttpFactory, + deps: [XHRBackend, RequestOptions, NSFileSystem] + } + ], + imports: [ + HttpModule, + ], + exports: [ + HttpModule, + ] +}) +export class NativeScriptHttpModule { +} diff --git a/nativescript-angular/http/index.ts b/nativescript-angular/http/index.ts new file mode 100644 index 000000000..720657322 --- /dev/null +++ b/nativescript-angular/http/index.ts @@ -0,0 +1 @@ +export * from "./http.module"; diff --git a/nativescript-angular/http/ns-http.ts b/nativescript-angular/http/ns-http.ts index d8d6453b0..f33d0639d 100644 --- a/nativescript-angular/http/ns-http.ts +++ b/nativescript-angular/http/ns-http.ts @@ -14,78 +14,78 @@ import "rxjs/add/observable/fromPromise"; import { NSFileSystem } from "../file-system/ns-file-system"; export class NSXSRFStrategy { - public configureRequest(_req: any) { - // noop - } + public configureRequest(_req: any) { + // noop + } } @Injectable() export class NSHttp extends Http { - constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private nsFileSystem: NSFileSystem) { - super(backend, defaultOptions); - } + constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private nsFileSystem: NSFileSystem) { + super(backend, defaultOptions); + } - /** - * Performs a request with `request` http method. - */ - request(req: string | Request, options?: RequestOptionsArgs): Observable { - const urlString = typeof req === "string" ? req : req.url; - if (isLocalRequest(urlString)) { - return this._requestLocalUrl(urlString); - } else { - return super.request(req, options); + /** + * Performs a request with `request` http method. + */ + request(req: string | Request, options?: RequestOptionsArgs): Observable { + const urlString = typeof req === "string" ? req : req.url; + if (isLocalRequest(urlString)) { + return this._requestLocalUrl(urlString); + } else { + return super.request(req, options); + } } - } - /** - * Performs a request with `get` http method. - */ - get(url: string, options?: RequestOptionsArgs): Observable { - if (isLocalRequest(url)) { - return this._requestLocalUrl(url); - } else { - return super.get(url, options); + /** + * Performs a request with `get` http method. + */ + get(url: string, options?: RequestOptionsArgs): Observable { + if (isLocalRequest(url)) { + return this._requestLocalUrl(url); + } else { + return super.get(url, options); + } } - } - /** - * Uses a local file if `~/` resource is requested. - * @param url - */ - private _requestLocalUrl(url: string): Observable { - // normalize url - url = normalizeLocalUrl(url); - // request from local app resources - return Observable.fromPromise(new Promise((resolve, reject) => { - let app = this.nsFileSystem.currentApp(); - let localFile = app.getFile(url); - if (localFile) { - localFile.readText().then((data) => { - resolve(responseOptions(data, 200, url)); - }, (err: Object) => { - reject(responseOptions(err, 400, url)); - }); - } else { - reject(responseOptions("Not Found", 404, url)); - } - })); - } + /** + * Uses a local file if `~/` resource is requested. + * @param url + */ + private _requestLocalUrl(url: string): Observable { + url = normalizeLocalUrl(url); + + // request from local app resources + return Observable.fromPromise(new Promise((resolve, reject) => { + let app = this.nsFileSystem.currentApp(); + let localFile = app.getFile(url); + if (localFile) { + localFile.readText().then((data) => { + resolve(responseOptions(data, 200, url)); + }, (err: Object) => { + reject(responseOptions(err, 400, url)); + }); + } else { + reject(responseOptions("Not Found", 404, url)); + } + })); + } } function isLocalRequest(url: string): boolean { - return url.indexOf("~") === 0 || url.indexOf("/") === 0; + return url.indexOf("~") === 0 || url.indexOf("/") === 0; } function normalizeLocalUrl(url: string): string { - return url.replace("~", "").replace("/", ""); + return url.replace("~", "").replace("/", ""); } function responseOptions(body: string | Object, status: number, url: string): Response { - return new Response(new ResponseOptions({ - body: body, - status: status, - statusText: "OK", - type: status === 200 ? ResponseType.Default : ResponseType.Error, - url: url - })); + return new Response(new ResponseOptions({ + body: body, + status: status, + statusText: "OK", + type: status === 200 ? ResponseType.Default : ResponseType.Error, + url: url + })); } diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 932dfbded..bdae52672 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -20,6 +20,7 @@ import { Sanitizer, InjectionToken } from "@angular/core"; +import { DOCUMENT } from "@angular/common"; import { rendererLog, rendererError } from "./trace"; import { @@ -56,9 +57,16 @@ export class NativeScriptSanitizer extends Sanitizer { } } +export class NativeScriptDocument { + createElement(tag: string) { + throw new Error("NativeScriptDocument is not DOM Document. There is no createElement() method."); + } +} + export const COMMON_PROVIDERS = [ defaultPageFactoryProvider, { provide: Sanitizer, useClass: NativeScriptSanitizer }, + { provide: DOCUMENT, useClass: NativeScriptDocument }, ]; export class NativeScriptPlatformRef extends PlatformRef { diff --git a/ng-sample/app/app.ts b/ng-sample/app/app.ts index 581f039fe..5e860f71c 100644 --- a/ng-sample/app/app.ts +++ b/ng-sample/app/app.ts @@ -3,10 +3,13 @@ import { platformNativeScriptDynamic } from "nativescript-angular/platform"; import { NativeScriptAnimationsModule } from "nativescript-angular/animations"; import { onAfterLivesync, onBeforeLivesync } from "nativescript-angular/platform-common"; import { NgModule } from "@angular/core"; +import { DOCUMENT } from '@angular/common'; import { Router } from "@angular/router"; import { NativeScriptRouterModule } from "nativescript-angular/router"; import { NativeScriptFormsModule } from "nativescript-angular/forms"; import { NativeScriptHttpModule } from "nativescript-angular/http"; +import { NativeScriptHttpClientModule } from "nativescript-angular/http-client"; + import { rendererTraceCategory, routerTraceCategory, @@ -34,6 +37,7 @@ import { ListTemplateSelectorTest } from "./examples/list/template-selector"; import { ListTestAsync, ListTestFilterAsync } from "./examples/list/list-test-async"; import { ImageTest } from "./examples/image/image-test"; import { HttpTest } from "./examples/http/http-test"; +import { HttpClientTest } from "./examples/http-client/http-client-test"; import { ActionBarTest } from "./examples/action-bar/action-bar-test"; import { ModalTest } from "./examples/modal/modal-test"; import { PlatfromDirectivesTest } from "./examples/platform-directives/platform-directives-test"; @@ -53,6 +57,7 @@ import { AnimationNgClassTest } from "./examples/animation/animation-ngclass-tes import { AnimationStatesTest } from "./examples/animation/animation-states-test"; import { AnimationStatesMultiTest } from "./examples/animation/animation-states-multi-test"; + @NgModule({ declarations: [ ], @@ -60,12 +65,14 @@ import { AnimationStatesMultiTest } from "./examples/animation/animation-states- NativeScriptModule, NativeScriptFormsModule, NativeScriptHttpModule, + NativeScriptHttpClientModule, NativeScriptRouterModule, ], exports: [ NativeScriptModule, NativeScriptFormsModule, NativeScriptHttpModule, + NativeScriptHttpClientModule, NativeScriptRouterModule, ], providers: [] @@ -130,6 +137,7 @@ const customPageFactoryProvider = { // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ImageTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ModalTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(HttpTest)); +platformNativeScriptDynamic().bootstrapModule(makeExampleModule(HttpClientTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PlatfromDirectivesTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ActionBarTest)); @@ -137,7 +145,7 @@ const customPageFactoryProvider = { // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(RouterOutletAppComponent)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PageRouterOutletAppComponent)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PageRouterOutletNestedAppComponent)); -platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ClearHistoryAppComponent)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ClearHistoryAppComponent)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(LoginAppComponent)); // animations diff --git a/ng-sample/app/examples/http-client/data.json b/ng-sample/app/examples/http-client/data.json new file mode 100644 index 000000000..52d4169d4 --- /dev/null +++ b/ng-sample/app/examples/http-client/data.json @@ -0,0 +1,8 @@ +{ + "results": [ + { + "title": "Test", + "description": "Testing Http local and remote." + } + ] +} diff --git a/ng-sample/app/examples/http-client/http-client-test.ts b/ng-sample/app/examples/http-client/http-client-test.ts new file mode 100644 index 000000000..9d4766992 --- /dev/null +++ b/ng-sample/app/examples/http-client/http-client-test.ts @@ -0,0 +1,122 @@ +import { Component, Inject, Injectable } from "@angular/core"; +import { + HttpClient, HTTP_INTERCEPTORS, HttpEventType, HttpErrorResponse, + HttpEvent, HttpInterceptor, HttpHandler, HttpRequest +} from "@angular/common/http"; +import { Observable } from "rxjs/observable"; +import "rxjs/add/operator/do"; + +@Injectable() +export class CustomInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + console.log(`[CustomInterceptor] intercept url: ${req.url}`); + + return next.handle(req) + .do(event => { + console.log(`[CustomInterceptor] handled type: ${HttpEventType[event.type]} url: ${req.url}`); + }); + } +} + +interface DataResults { + results: Array; +} + +interface LocalData { + title: string; + description: string; +} + +interface RemoteData { + name: { first: string }; + email: string; +} + +@Component({ + selector: "http-client-test", + template: ` + + + + + + + + + + + + `, + styles: [` + #title { margin-top:20; } + Label { margin: 5 20; } + `], +}) +export class HttpClientTest { + static providers = [ + { + provide: HTTP_INTERCEPTORS, + useClass: CustomInterceptor, + multi: true, + } + ]; + + public title: string; + public description: string; + public error: string; + + constructor(private http: HttpClient) { + } + + public loadLocal() { + this.http.get>("~/examples/http/data.json") + .subscribe((response) => { + let user = response.results[0]; + this.onSuccess(user.title, user.description); + }, (error) => { + this.onError(error); + }); + } + + public loadRemote() { + this.http.get>(`https://randomuser.me/api/?results=1&nat=us`) + .subscribe((response) => { + const user = response.results[0]; + this.onSuccess(user.email, user.name.first); + }, (error) => { + this.onError(error); + }); + } + + public loadNonexistentLocal() { + this.http.get>("~/non/existent/app/folder/data.json") + .subscribe((response) => { + this.onSuccess("strange?!", ""); + }, (error) => { + this.onError(error); + }); + } + + public loadNonexistentRemote() { + this.http.get>("https://google.com/non/existent/url/data.json") + .subscribe((response) => { + this.onSuccess("strange?!", ""); + }, (error) => { + this.onError(error); + }); + } + + private onSuccess(title: string, description: string) { + this.title = title; + this.description = description; + this.error = ""; + } + + private onError(error: HttpErrorResponse) { + console.log("onError " + error); + console.dir(error); + this.title = ""; + this.description = ""; + this.error = error.message; + } +} From 58dd8fb1753f46d17b4e70f3ccdf35033bc7b06c Mon Sep 17 00:00:00 2001 From: vakrilov Date: Wed, 20 Sep 2017 18:32:15 +0300 Subject: [PATCH 06/14] test(http): Add tests for NsHttpBackEnd --- .../http-client/http-client.module.ts | 2 + tests/app/tests/http-client-ns-backend.ts | 80 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 tests/app/tests/http-client-ns-backend.ts diff --git a/nativescript-angular/http-client/http-client.module.ts b/nativescript-angular/http-client/http-client.module.ts index ee2703402..40f1ffe78 100644 --- a/nativescript-angular/http-client/http-client.module.ts +++ b/nativescript-angular/http-client/http-client.module.ts @@ -9,6 +9,8 @@ global.__extends = cachedExtends; import { NSFileSystem } from "../file-system/ns-file-system"; import { NsHttpBackEnd } from "./ns-http-backend"; +export { NsHttpBackEnd } from "./ns-http-backend"; + @NgModule({ providers: [ NSFileSystem, diff --git a/tests/app/tests/http-client-ns-backend.ts b/tests/app/tests/http-client-ns-backend.ts new file mode 100644 index 000000000..e5baf8d2c --- /dev/null +++ b/tests/app/tests/http-client-ns-backend.ts @@ -0,0 +1,80 @@ +// make sure you import mocha-config before @angular/core +import { assert } from "./test-config"; +import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system"; +import { NsHttpBackEnd } from "nativescript-angular/http-client"; + +import { XhrFactory, HttpRequest, HttpResponse, HttpErrorResponse } from "@angular/common/http"; +import { File } from "tns-core-modules/file-system"; + +class NSFileSystemMock implements NSFileSystem { + public currentApp(): any { + return { path: "/app/dir" }; + } + + public fileFromPath(path: string): any { + if (path === "/app/dir/data.json") { + return { + readText: () => { return Promise.resolve(` { "result": "success" } `); } + }; + } + throw new Error("Opening non-existing file"); + } + + public fileExists(path: string): boolean { + return path === "/app/dir/data.json"; + } +} +class XhrFactoryMock implements XhrFactory { + build(): XMLHttpRequest { + throw new Error("Hi, from XhrFactoryMock!"); + } +} + +describe("NsHttpBackEnd ", () => { + let backend: NsHttpBackEnd; + + before(() => { + backend = new NsHttpBackEnd(new XhrFactoryMock(), new NSFileSystemMock()); + }); + + it("should work with local files prefixed with '~'", (done) => { + const req = new HttpRequest("GET", "~/data.json"); + let nextCalled = false; + backend.handle(req).subscribe( + (response: HttpResponse<{ result: string }>) => { + assert.equal(response.body.result, "success"); + nextCalled = true; + }, (error) => { + done(error); + }, () => { + assert.isTrue(nextCalled, "next callback should be called with result."); + done(); + }); + }); + + it("should return 404 for non-existing local files prefixed with '~'", (done) => { + const req = new HttpRequest("GET", "~/non/existing/file.json"); + backend.handle(req).subscribe( + (response) => { + assert.fail("next callback should not be called for non existing file."); + }, (error: HttpErrorResponse) => { + assert.equal(error.status, 404); + done(); + }, () => { + assert.fail("next callback should not be called for non existing file."); + }); + }); + + it("should fallback to XHR backend when requesting remote files", (done) => { + const req = new HttpRequest("GET", "https://nativescript.org/"); + backend.handle(req).subscribe( + (response) => { + assert.fail("next callback should not be called for non existing file."); + }, (error: Error) => { + assert.equal(error.message, "Hi, from XhrFactoryMock!"); + done(); + }, () => { + assert.fail("next callback should not be called for non existing file."); + }); + }); +}); From 74511b05626154593a069f3ba07f0bee6dc3e148 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Thu, 21 Sep 2017 15:52:12 +0300 Subject: [PATCH 07/14] chore(cleanup): Remove unused imports --- nativescript-angular/http-client/http-client.module.ts | 2 +- nativescript-angular/http-client/ns-http-backend.ts | 3 +-- nativescript-angular/package.json | 2 +- nativescript-angular/platform-common.ts | 9 +++++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/nativescript-angular/http-client/http-client.module.ts b/nativescript-angular/http-client/http-client.module.ts index 40f1ffe78..54fe858e4 100644 --- a/nativescript-angular/http-client/http-client.module.ts +++ b/nativescript-angular/http-client/http-client.module.ts @@ -1,4 +1,4 @@ -import { NgModule, Optional, Inject, Injectable } from "@angular/core"; +import { NgModule } from "@angular/core"; // IMPORTant: Importing "@angular/common/http" for the first time overwrites the // global.__extends function. diff --git a/nativescript-angular/http-client/ns-http-backend.ts b/nativescript-angular/http-client/ns-http-backend.ts index 472f944fe..9be4e91ea 100644 --- a/nativescript-angular/http-client/ns-http-backend.ts +++ b/nativescript-angular/http-client/ns-http-backend.ts @@ -1,7 +1,6 @@ import { Injectable } from "@angular/core"; import { - HttpHandler, HttpRequest, - HttpEvent, HttpEventType, + HttpRequest, HttpEvent, XhrFactory, HttpResponse, HttpErrorResponse, HttpXhrBackend } from "@angular/common/http"; diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index 1cafb5883..c56aacd12 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -68,7 +68,7 @@ "rxjs": "^5.4.2", "tns-core-modules": "next", "tslint": "^5.5.0", - "typescript": "^2.4.1", + "typescript": "^2.5.1", "zone.js": "^0.8.12" } } diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index bdae52672..a5fbb67e1 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -27,8 +27,7 @@ import { PAGE_FACTORY, PageFactory, defaultPageFactoryProvider, - setRootPage, - PageFactoryOptions + setRootPage } from "./platform-providers"; import { start, setCssFileName } from "tns-core-modules/application"; @@ -43,6 +42,12 @@ export const onAfterLivesync = new EventEmitter>(); let lastBootstrappedModule: WeakRef>; type BootstrapperAction = () => Promise>; +// Work around a TS bug requiring an import of OpaqueToken without using it +if ((global).___TS_UNUSED) { + (() => { + return InjectionToken; + })(); +} export interface AppOptions { bootInExistingPage?: boolean; cssFile?: string; From bfd0d9731458b5a66310211aa5fc47254c7170b9 Mon Sep 17 00:00:00 2001 From: Alexander Vakrilov Date: Tue, 26 Sep 2017 13:20:35 +0300 Subject: [PATCH 08/14] fix: Fixed runt --- e2e/renderer/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/renderer/package.json b/e2e/renderer/package.json index eea1df2e7..94403d63c 100644 --- a/e2e/renderer/package.json +++ b/e2e/renderer/package.json @@ -6,7 +6,7 @@ "nativescript": { "id": "org.nativescript.renderer", "tns-android": { - "version": "3.2.0-2017-9-12-1" + "version": "next" } }, "dependencies": { From fa6381be852c914e48dba33a2144ffe3f9e04306 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Wed, 27 Sep 2017 17:07:50 +0300 Subject: [PATCH 09/14] Update test to be compatible with next version of nativescript-dev-appium --- e2e/router/.gitignore | 4 +- e2e/router/.vscode/launch.json | 41 +++ e2e/router/.vscode/settings.json | 19 + .../e2e/config/appium.capabilities.json | 68 ++-- e2e/router/e2e/helpers/appium-elements.ts | 41 --- e2e/router/e2e/router.e2e-spec.ts | 341 ++++++++---------- e2e/router/e2e/setup.ts | 12 +- e2e/router/package.json | 5 +- 8 files changed, 262 insertions(+), 269 deletions(-) create mode 100644 e2e/router/.vscode/launch.json create mode 100644 e2e/router/.vscode/settings.json delete mode 100644 e2e/router/e2e/helpers/appium-elements.ts diff --git a/e2e/router/.gitignore b/e2e/router/.gitignore index d3df84cc8..b244937ad 100644 --- a/e2e/router/.gitignore +++ b/e2e/router/.gitignore @@ -4,5 +4,7 @@ hooks app/**/*.js e2e/**/*.js +e2e/**/*.map +.vscode test-results.xml - +e2e/reports \ No newline at end of file diff --git a/e2e/router/.vscode/launch.json b/e2e/router/.vscode/launch.json new file mode 100644 index 000000000..4961b50d3 --- /dev/null +++ b/e2e/router/.vscode/launch.json @@ -0,0 +1,41 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "android24", + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceRoot}/e2e", + "--runType", + "android24", + "--reuseDevice" + ], + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "node", + "request": "launch", + "name": "sim.iPhone7.iOS110", + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + "args": [ + "-u", + "tdd", + "--timeout", + "999999", + "--colors", + "${workspaceRoot}/e2e", + "--runType", + "sim.iPhone7.iOS110", + "--reuseDevice" + ], + "internalConsoleOptions": "openOnSessionStart" + } + ] + } \ No newline at end of file diff --git a/e2e/router/.vscode/settings.json b/e2e/router/.vscode/settings.json new file mode 100644 index 000000000..699fb4408 --- /dev/null +++ b/e2e/router/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "typescript.check.tscVersion": false, + "window.zoomLevel": 0, + "workbench.iconTheme": "vscode-icons", + "vsicons.dontShowNewVersionMessage": true, + "workbench.colorTheme": "Default Light+", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/*.js": true, + "**/*.map": true, + "node_modules": true, + "hooks": true, + "platforms": true + } +} \ No newline at end of file diff --git a/e2e/router/e2e/config/appium.capabilities.json b/e2e/router/e2e/config/appium.capabilities.json index 676f5e070..4f1b4407d 100644 --- a/e2e/router/e2e/config/appium.capabilities.json +++ b/e2e/router/e2e/config/appium.capabilities.json @@ -1,84 +1,100 @@ { "nexus5": { "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "6.0", "deviceName": "device", "udid": "077e4a47003b7698", - "-lt": 60000, - "automationName": "Appium", "appActivity": "com.tns.NativeScriptActivity", "app": "" }, "android19": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "4.4", "deviceName": "Emulator-Api19-Default", "avd": "Emulator-Api19-Default", - "-lt": 60000, - "automationName": "Appium", + "lt": 60000, "appActivity": "com.tns.NativeScriptActivity", "newCommandTimeout": 720, - "noReset": true, + "noReset": false, "fullReset": false, "app": "" }, "android21": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "5.0", "deviceName": "Emulator-Api21-Default", "avd": "Emulator-Api21-Default", - "-lt": 60000, - "automationName": "Appium", + "lt": 60000, "appActivity": "com.tns.NativeScriptActivity", "newCommandTimeout": 720, - "noReset": true, + "noReset": false, "fullReset": false, "app": "" }, "android23": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "6.0", "deviceName": "Emulator-Api23-Default", "avd": "Emulator-Api23-Default", - "-lt": 60000, - "automationName": "Appium", + "lt": 60000, "appActivity": "com.tns.NativeScriptActivity", "newCommandTimeout": 720, - "noReset": true, + "noReset": false, "fullReset": false, "app": "" }, "android24": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "7.0", "deviceName": "Emulator-Api24-Default", "avd": "Emulator-Api24-Default", - "-lt": 60000, + "lt": 60000, + "appActivity": "com.tns.NativeScriptActivity", + "newCommandTimeout": 720, + "noReset": false, + "fullReset": false, + "app": "" + }, + "android25": { + "platformName": "Android", + "platformVersion": "7.1", + "deviceName": "Emulator-Api25-Google", + "avd": "Emulator-Api25-Google", + "lt": 60000, + "appActivity": "com.tns.NativeScriptActivity", + "newCommandTimeout": 720, + "noReset": false, + "fullReset": false, + "app": "" + }, + "android26": { + "platformName": "Android", + "platformVersion": "8.0", + "deviceName": "Emulator-Api26-Google", + "avd": "Emulator-Api26-Google", + "lt": 60000, "automationName": "UIAutomator2", "appActivity": "com.tns.NativeScriptActivity", "newCommandTimeout": 720, - "noReset": true, + "noReset": false, "fullReset": false, "app": "" }, "sim.iPhone7.iOS100": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "iOS", "platformVersion": "10.0", "deviceName": "iPhone 7 100", - "noReset": true, + "noReset": false, + "fullReset": false, + "app": "" + }, + "sim.iPhone7.iOS110": { + "platformName": "iOS", + "platformVersion": "11.0", + "deviceName": "iPhone 7 110", + "noReset": false, "fullReset": false, "app": "" } -} +} \ No newline at end of file diff --git a/e2e/router/e2e/helpers/appium-elements.ts b/e2e/router/e2e/helpers/appium-elements.ts deleted file mode 100644 index 26a020a4d..000000000 --- a/e2e/router/e2e/helpers/appium-elements.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { AppiumDriver } from "nativescript-dev-appium"; - -import { UIElement } from "nativescript-dev-appium/ui-element"; - -export class ExtendedUIElement extends UIElement { - refetch(): Promise { - return Promise.resolve(this); - } -} - -const refetchable = () => - (target: any, propertyKey: string, descriptor: PropertyDescriptor): any => { - const originalMethod = descriptor.value; - const patchRefetch = async (args, fetchMethod) => { - const result = await fetchMethod() as ExtendedUIElement; - result.refetch = () => patchRefetch(args, fetchMethod); - - return result; - } - - descriptor.value = async function (...args: any[]): Promise { - const fetchMethod = () => originalMethod.apply(this, args); - const result = await patchRefetch(args, fetchMethod); - - return result; - } - - return descriptor; - }; - -export class DriverWrapper { - constructor(private driver: AppiumDriver) { - } - - @refetchable() - async findElementByText(...args: any[]): Promise { - const result = await (this.driver).findElementByText(...args); - - return result; - } -} diff --git a/e2e/router/e2e/router.e2e-spec.ts b/e2e/router/e2e/router.e2e-spec.ts index c6ae36cb6..82dcda30c 100644 --- a/e2e/router/e2e/router.e2e-spec.ts +++ b/e2e/router/e2e/router.e2e-spec.ts @@ -1,453 +1,398 @@ import { AppiumDriver, + UIElement, createDriver, SearchOptions, } from "nativescript-dev-appium"; -import { DriverWrapper, ExtendedUIElement } from "./helpers/appium-elements"; - describe("Simple navigate and back", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1); - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1); + await assureNestedMasterComponent(driver); }); it("should navigate back to First", async () => { - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Navigate inside nested outlet", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(1)/detail(1) and back", async () => { - const detailBtn = await driverWrapper.findElementByText("DETAIL 1", SearchOptions.exact); + const detailBtn = await driver.findElementByText("DETAIL 1", SearchOptions.exact); detailBtn.click(); - await assureSecondComponent(driverWrapper, 1) - await assureNestedDetailComponent(driverWrapper, 1); + await assureSecondComponent(driver, 1) + await assureNestedDetailComponent(driver, 1); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await goBack(driver); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(1)/detail(2) and back", async () => { - const detailBtn = await driverWrapper.findElementByText("DETAIL 2", SearchOptions.exact); + const detailBtn = await driver.findElementByText("DETAIL 2", SearchOptions.exact); detailBtn.click(); - await assureSecondComponent(driverWrapper, 1) - await assureNestedDetailComponent(driverWrapper, 2); + await assureSecondComponent(driver, 1) + await assureNestedDetailComponent(driver, 2); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await goBack(driver); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate back to First", async () => { - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Navigate to same component with different param", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(2)/master", async () => { const navigationButton = - await driverWrapper.findElementByText("GO TO NEXT SECOND", SearchOptions.exact); + await driver.findElementByText("GO TO NEXT SECOND", SearchOptions.exact); navigationButton.click(); - await assureSecondComponent(driverWrapper, 2) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 2) + await assureNestedMasterComponent(driver); }); it("should navigate back to Second(1)/master", async () => { - await goBack(driverWrapper); + await goBack(driver); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate back to First", async () => { - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Nested navigation + page navigation", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(1)/detail(1)", async () => { - const detailBtn = await driverWrapper.findElementByText("DETAIL 1", SearchOptions.exact); + const detailBtn = await driver.findElementByText("DETAIL 1", SearchOptions.exact); detailBtn.click(); - await assureSecondComponent(driverWrapper, 1) - await assureNestedDetailComponent(driverWrapper, 1); + await assureSecondComponent(driver, 1) + await assureNestedDetailComponent(driver, 1); }); it("should navigate to Second(2)/master", async () => { const navigationButton = - await driverWrapper.findElementByText("GO TO NEXT SECOND", SearchOptions.exact); + await driver.findElementByText("GO TO NEXT SECOND", SearchOptions.exact); navigationButton.click(); - await assureSecondComponent(driverWrapper, 2) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 2) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(2)/detail(2)", async () => { - const detailBtn = await driverWrapper.findElementByText("DETAIL 2", SearchOptions.exact); + const detailBtn = await driver.findElementByText("DETAIL 2", SearchOptions.exact); detailBtn.click(); - await assureSecondComponent(driverWrapper, 2) - await assureNestedDetailComponent(driverWrapper, 2); + await assureSecondComponent(driver, 2) + await assureNestedDetailComponent(driver, 2); }); it("should navigate to First", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); + await findAndClick(driver, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate the whole stack", async () => { - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 2) - await assureNestedDetailComponent(driverWrapper, 2); + await goBack(driver); + await assureSecondComponent(driver, 2) + await assureNestedDetailComponent(driver, 2); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 2) - await assureNestedMasterComponent(driverWrapper); + await goBack(driver); + await assureSecondComponent(driver, 2) + await assureNestedMasterComponent(driver); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 1) - await assureNestedDetailComponent(driverWrapper, 1); + await goBack(driver); + await assureSecondComponent(driver, 1) + await assureNestedDetailComponent(driver, 1); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await goBack(driver); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Shouldn't be able to navigate back on startup", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("shouldn't be able to go back", async () => { - await goBack(driverWrapper); - await driverWrapper.findElementByText("canGoBack() - false", SearchOptions.exact); + await goBack(driver); + await driver.findElementByText("canGoBack() - false", SearchOptions.exact); }); }); describe("Shouldn't be able to navigate back after cleared history", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO FIRST(CLEAR)"); + await findAndClick(driver, "GO TO FIRST(CLEAR)"); - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("shouldn't be able to go back", async () => { - await goBack(driverWrapper); - await driverWrapper.findElementByText("canGoBack() - false", SearchOptions.exact); + await goBack(driver); + await driver.findElementByText("canGoBack() - false", SearchOptions.exact); }); }); describe("Navigate to componentless route", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to ComponentlessSecond(100)/detail(200)", async () => { const navigationButton = - await driverWrapper.findElementByText("GO TO C-LESS SECOND", SearchOptions.exact); + await driver.findElementByText("GO TO C-LESS SECOND", SearchOptions.exact); navigationButton.click(); - await assureSecondComponent(driverWrapper, 100) - await assureNestedDetailComponent(driverWrapper, 200); + await assureSecondComponent(driver, 100) + await assureNestedDetailComponent(driver, 200); }); it("should navigate to First", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); + await findAndClick(driver, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate the whole stack", async () => { - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 100) - await assureNestedDetailComponent(driverWrapper, 200); + await goBack(driver); + await assureSecondComponent(driver, 100) + await assureNestedDetailComponent(driver, 200); - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Navigate to lazy module", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to lazy/home", async () => { - await findAndClick(driverWrapper, "GO TO LAZY HOME"); - - await assureLazyComponent(driverWrapper); + await findAndClick(driver, "GO TO LAZY HOME"); + await assureLazyComponent(driver); }); it("should navigate to First", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await findAndClick(driver, "GO TO FIRST"); + await assureFirstComponent(driver); }); it("should navigate back to lazy/home", async () => { - await goBack(driverWrapper); - await assureLazyComponent(driverWrapper); + await goBack(driver); + await assureLazyComponent(driver); }); it("should navigate to First again", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await findAndClick(driver, "GO TO FIRST"); + await assureFirstComponent(driver); }); it("should navigate the whole stack", async () => { - await goBack(driverWrapper); - await assureLazyComponent(driverWrapper); + await goBack(driver); + await assureLazyComponent(driver); - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Navigate to componentless lazy module route", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to nest/more (componentless lazy route)", async () => { - await findAndClick(driverWrapper, "GO TO C-LESS LAZY"); + await findAndClick(driver, "GO TO C-LESS LAZY"); - await assureComponentlessLazyComponent(driverWrapper); + await assureComponentlessLazyComponent(driver); }); it("should navigate to lazy/home", async () => { - await findAndClick(driverWrapper, "GO TO LAZY HOME"); + await findAndClick(driver, "GO TO LAZY HOME"); - await assureLazyComponent(driverWrapper); + await assureLazyComponent(driver); }); - + it("should navigate to First", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); + await findAndClick(driver, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate the whole stack", async () => { - await goBack(driverWrapper); - await assureLazyComponent(driverWrapper); + await goBack(driver); + await assureLazyComponent(driver); - await goBack(driverWrapper); - await assureComponentlessLazyComponent(driverWrapper); + await goBack(driver); + await assureComponentlessLazyComponent(driver); - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); -async function assureFirstComponent(driverWrapper: DriverWrapper) { - await driverWrapper.findElementByText("FirstComponent", SearchOptions.exact); +async function assureFirstComponent(driver: AppiumDriver) { + await driver.findElementByText("FirstComponent", SearchOptions.exact); } -async function assureLazyComponent(driverWrapper: DriverWrapper) { - await driverWrapper.findElementByText("LazyComponent", SearchOptions.exact); +async function assureLazyComponent(driver: AppiumDriver) { + await driver.findElementByText("LazyComponent", SearchOptions.exact); } -async function assureComponentlessLazyComponent(driverWrapper: DriverWrapper) { - await driverWrapper.findElementByText("Lazy Componentless Route", SearchOptions.exact); +async function assureComponentlessLazyComponent(driver: AppiumDriver) { + await driver.findElementByText("Lazy Componentless Route", SearchOptions.exact); } -async function assureSecondComponent(driverWrapper: DriverWrapper, param: number) { - await driverWrapper.findElementByText("SecondComponent", SearchOptions.exact); - await driverWrapper.findElementByText(`param: ${param}`, SearchOptions.exact); +async function assureSecondComponent(driver: AppiumDriver, param: number) { + await driver.findElementByText("SecondComponent", SearchOptions.exact); + await driver.findElementByText(`param: ${param}`, SearchOptions.exact); } -async function assureNestedMasterComponent(driverWrapper: DriverWrapper) { - await driverWrapper.findElementByText("NestedMaster", SearchOptions.exact); +async function assureNestedMasterComponent(driver: AppiumDriver) { + await driver.findElementByText("NestedMaster", SearchOptions.exact); } -async function assureNestedDetailComponent(driverWrapper: DriverWrapper, param: number) { - await driverWrapper.findElementByText("NestedDetail", SearchOptions.exact); - await driverWrapper.findElementByText(`nested-param: ${param}`, SearchOptions.exact); +async function assureNestedDetailComponent(driver: AppiumDriver, param: number) { + await driver.findElementByText("NestedDetail", SearchOptions.exact); + await driver.findElementByText(`nested-param: ${param}`, SearchOptions.exact); } -async function goBack(driverWrapper: DriverWrapper) { - const backButton = await driverWrapper.findElementByText("BACK", SearchOptions.exact); +async function goBack(driver: AppiumDriver) { + const backButton = await driver.findElementByText("BACK", SearchOptions.exact); await backButton.click(); + //await driver.navBack(); } -async function findAndClick(driverWrapper: DriverWrapper, text: string) { +async function findAndClick(driver: AppiumDriver, text: string) { const navigationButton = - await driverWrapper.findElementByText(text, SearchOptions.exact); + await driver.findElementByText(text, SearchOptions.exact); navigationButton.click(); } \ No newline at end of file diff --git a/e2e/router/e2e/setup.ts b/e2e/router/e2e/setup.ts index 8b26e66e9..0c0add14a 100644 --- a/e2e/router/e2e/setup.ts +++ b/e2e/router/e2e/setup.ts @@ -1,9 +1,19 @@ -import { startServer, stopServer } from "nativescript-dev-appium"; +import { startServer, stopServer , createDriver , AppiumDriver } from "nativescript-dev-appium"; + +let driver: AppiumDriver; before("start server", async () => { await startServer(); + driver = await createDriver(); }); after("stop server", async () => { + await driver.quit(); await stopServer(); }); + +afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logScreenshot(this.currentTest.title); + } +}); \ No newline at end of file diff --git a/e2e/router/package.json b/e2e/router/package.json index 3323d2898..e99f48117 100644 --- a/e2e/router/package.json +++ b/e2e/router/package.json @@ -42,12 +42,13 @@ "mocha": "~3.5.0", "mocha-junit-reporter": "^1.13.0", "mocha-multi": "^0.11.0", - "nativescript-dev-appium": "2.1.0-2017-8-17", + "nativescript-dev-appium": "next", "nativescript-dev-typescript": "~0.4.0", "tslib": "^1.7.1", "typescript": "~2.2.1" }, "scripts": { - "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts" + "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts", + "compile-tests-w": "tsc -p e2e --watch" } } From 4a81f08a9a6ddc36f42adea0f2d7ab8038aa2670 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Wed, 27 Sep 2017 17:15:22 +0300 Subject: [PATCH 10/14] Upade vs settings and .gitignore --- e2e/router/.gitignore | 12 ++++++------ e2e/router/.vscode/settings.json | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/e2e/router/.gitignore b/e2e/router/.gitignore index b244937ad..8e824499e 100644 --- a/e2e/router/.gitignore +++ b/e2e/router/.gitignore @@ -1,10 +1,10 @@ +.vscode + platforms node_modules hooks -app/**/*.js -e2e/**/*.js -e2e/**/*.map -.vscode -test-results.xml -e2e/reports \ No newline at end of file +/**/*.js +/**/*.map +e2e/reports +test-results.xml \ No newline at end of file diff --git a/e2e/router/.vscode/settings.json b/e2e/router/.vscode/settings.json index 699fb4408..8508de074 100644 --- a/e2e/router/.vscode/settings.json +++ b/e2e/router/.vscode/settings.json @@ -5,13 +5,13 @@ "vsicons.dontShowNewVersionMessage": true, "workbench.colorTheme": "Default Light+", "files.exclude": { - "**/.git": true, + "**/**/*.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, - "**/*.js": true, - "**/*.map": true, + "**/**/*.js": true, + "**/**/*.map": true, "node_modules": true, "hooks": true, "platforms": true From 3f11eeb0b84ba56f05b0c90a2325432c19a0572e Mon Sep 17 00:00:00 2001 From: vakrilov Date: Mon, 2 Oct 2017 18:21:20 +0300 Subject: [PATCH 11/14] refactor: Minor changes based on PR review --- nativescript-angular/http-client/http-client.module.ts | 2 -- nativescript-angular/http-client/index.ts | 1 + ng-sample/app/examples/http-client/http-client-test.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/nativescript-angular/http-client/http-client.module.ts b/nativescript-angular/http-client/http-client.module.ts index 54fe858e4..ea6623d5b 100644 --- a/nativescript-angular/http-client/http-client.module.ts +++ b/nativescript-angular/http-client/http-client.module.ts @@ -9,8 +9,6 @@ global.__extends = cachedExtends; import { NSFileSystem } from "../file-system/ns-file-system"; import { NsHttpBackEnd } from "./ns-http-backend"; -export { NsHttpBackEnd } from "./ns-http-backend"; - @NgModule({ providers: [ NSFileSystem, diff --git a/nativescript-angular/http-client/index.ts b/nativescript-angular/http-client/index.ts index b57798f8f..da2e04db0 100644 --- a/nativescript-angular/http-client/index.ts +++ b/nativescript-angular/http-client/index.ts @@ -1 +1,2 @@ export * from "./http-client.module"; +export * from "./ns-http-backend"; diff --git a/ng-sample/app/examples/http-client/http-client-test.ts b/ng-sample/app/examples/http-client/http-client-test.ts index 9d4766992..7af6fb9d6 100644 --- a/ng-sample/app/examples/http-client/http-client-test.ts +++ b/ng-sample/app/examples/http-client/http-client-test.ts @@ -3,7 +3,7 @@ import { HttpClient, HTTP_INTERCEPTORS, HttpEventType, HttpErrorResponse, HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from "@angular/common/http"; -import { Observable } from "rxjs/observable"; +import { Observable } from "rxjs/Observable"; import "rxjs/add/operator/do"; @Injectable() From 1d1877e9d009f4157b5e99f8ff8002349cda855a Mon Sep 17 00:00:00 2001 From: vakrilov Date: Tue, 3 Oct 2017 15:35:02 +0300 Subject: [PATCH 12/14] refactor: Extract common http methods --- .../http-client/http-utils.ts | 52 ++++++++++++++++++ .../http-client/ns-http-backend.ts | 55 +++++-------------- nativescript-angular/http/ns-http.ts | 45 +++++---------- tests/app/tests/http.ts | 53 +++++++++++++----- tests/app/tests/mocks/ns-file-system.mock.ts | 36 ------------ 5 files changed, 118 insertions(+), 123 deletions(-) create mode 100644 nativescript-angular/http-client/http-utils.ts delete mode 100644 tests/app/tests/mocks/ns-file-system.mock.ts diff --git a/nativescript-angular/http-client/http-utils.ts b/nativescript-angular/http-client/http-utils.ts new file mode 100644 index 000000000..27b799383 --- /dev/null +++ b/nativescript-angular/http-client/http-utils.ts @@ -0,0 +1,52 @@ +import { NSFileSystem } from "../file-system/ns-file-system"; + +import { Observable } from "rxjs/Observable"; +import { Observer } from "rxjs/Observer"; +import { path } from "tns-core-modules/file-system/file-system"; + +export type httpResponseFactory = (url: string, body: any, status: number) => T; +export type httpErrorFactory = (url: string, body: any, status: number) => any; + +export function isLocalRequest(url: string): boolean { + return url.indexOf("~") === 0 || url.indexOf("/") === 0; +} + +export function getAbsolutePath(url: string, nsFileSystem: NSFileSystem): string { + url = url.replace("~", "").replace("/", ""); + url = path.join(nsFileSystem.currentApp().path, url); + return url; +} + +export function handleLocalRequest( + url: string, + nsFileSystem: NSFileSystem, + successResponse: httpResponseFactory, + errorResponse: httpErrorFactory): Observable { + + url = getAbsolutePath(url, nsFileSystem); + + // request from local app resources + return new Observable((observer: Observer) => { + if (nsFileSystem.fileExists(url)) { + const localFile = nsFileSystem.fileFromPath(url); + localFile.readText() + .then((data) => { + try { + const json = JSON.parse(data); + observer.next(successResponse(url, json, 200)); + observer.complete(); + } catch (error) { + // Even though the response status was 2xx, this is still an error. + // The parse error contains the text of the body that failed to parse. + const errorResult = { error, text: data }; + observer.error(errorResponse(url, errorResult, 200)); + } + }, (err: Object) => { + observer.error(errorResponse(url, err, 400)); + + }); + } else { + observer.error(errorResponse(url, "Not Found", 404)); + } + }); +} diff --git a/nativescript-angular/http-client/ns-http-backend.ts b/nativescript-angular/http-client/ns-http-backend.ts index 9be4e91ea..061fb8647 100644 --- a/nativescript-angular/http-client/ns-http-backend.ts +++ b/nativescript-angular/http-client/ns-http-backend.ts @@ -5,10 +5,9 @@ import { HttpErrorResponse, HttpXhrBackend } from "@angular/common/http"; import { Observable } from "rxjs/Observable"; -import { Observer } from "rxjs/Observer"; import { NSFileSystem } from "../file-system/ns-file-system"; -import { path } from "tns-core-modules/file-system"; +import { isLocalRequest, handleLocalRequest } from "./http-utils"; @Injectable() export class NsHttpBackEnd extends HttpXhrBackend { @@ -28,47 +27,20 @@ export class NsHttpBackEnd extends HttpXhrBackend { return result; } - private getAbsolutePath(url: string): string { - url = url.replace("~", "").replace("/", ""); - url = path.join(this.nsFileSystem.currentApp().path, url); - return url; - } - private handleLocalRequest(url: string): Observable> { - url = this.getAbsolutePath(url); - - // request from local app resources - return new Observable((observer: Observer>) => { - if (this.nsFileSystem.fileExists(url)) { - const localFile = this.nsFileSystem.fileFromPath(url); - localFile.readText() - .then((data) => { - try { - const json = JSON.parse(data); - observer.next(createSuccessResponse(url, json, 200)); - observer.complete(); - } catch (error) { - // Even though the response status was 2xx, this is still an error. - // The parse error contains the text of the body that failed to parse. - const errorResult = { error, text: data }; - observer.error(createErrorResponse(url, errorResult, 200)); - } - }, (err: Object) => { - observer.error(createErrorResponse(url, err, 400)); - - }); - } else { - observer.error(createErrorResponse(url, "Not Found", 404)); - } - }); + return handleLocalRequest( + url, + this.nsFileSystem, + createSuccessResponse, + createErrorResponse + ); } } -function isLocalRequest(url: string): boolean { - return url.indexOf("~") === 0 || url.indexOf("/") === 0; -} - -function createSuccessResponse(url: string, body: any, status: number): HttpEvent { +function createSuccessResponse( + url: string, + body: any, + status: number): HttpEvent { return new HttpResponse({ url, body, @@ -77,7 +49,10 @@ function createSuccessResponse(url: string, body: any, status: number): HttpEven }); } -function createErrorResponse(url: string, body: any, status: number): HttpErrorResponse { +function createErrorResponse( + url: string, + body: any, + status: number): HttpErrorResponse { return new HttpErrorResponse({ url, error: body, diff --git a/nativescript-angular/http/ns-http.ts b/nativescript-angular/http/ns-http.ts index f33d0639d..6b4c8fe98 100644 --- a/nativescript-angular/http/ns-http.ts +++ b/nativescript-angular/http/ns-http.ts @@ -11,6 +11,9 @@ import { } from "@angular/http"; import { Observable } from "rxjs/Observable"; import "rxjs/add/observable/fromPromise"; + +import { isLocalRequest, handleLocalRequest } from "../http-client/http-utils"; + import { NSFileSystem } from "../file-system/ns-file-system"; export class NSXSRFStrategy { @@ -31,7 +34,7 @@ export class NSHttp extends Http { request(req: string | Request, options?: RequestOptionsArgs): Observable { const urlString = typeof req === "string" ? req : req.url; if (isLocalRequest(urlString)) { - return this._requestLocalUrl(urlString); + return this.handleLocalRequest(urlString); } else { return super.request(req, options); } @@ -42,45 +45,23 @@ export class NSHttp extends Http { */ get(url: string, options?: RequestOptionsArgs): Observable { if (isLocalRequest(url)) { - return this._requestLocalUrl(url); + return this.handleLocalRequest(url); } else { return super.get(url, options); } } - /** - * Uses a local file if `~/` resource is requested. - * @param url - */ - private _requestLocalUrl(url: string): Observable { - url = normalizeLocalUrl(url); - - // request from local app resources - return Observable.fromPromise(new Promise((resolve, reject) => { - let app = this.nsFileSystem.currentApp(); - let localFile = app.getFile(url); - if (localFile) { - localFile.readText().then((data) => { - resolve(responseOptions(data, 200, url)); - }, (err: Object) => { - reject(responseOptions(err, 400, url)); - }); - } else { - reject(responseOptions("Not Found", 404, url)); - } - })); + private handleLocalRequest(url: string): Observable { + return handleLocalRequest( + url, + this.nsFileSystem, + createResponse, + createResponse + ); } } -function isLocalRequest(url: string): boolean { - return url.indexOf("~") === 0 || url.indexOf("/") === 0; -} - -function normalizeLocalUrl(url: string): string { - return url.replace("~", "").replace("/", ""); -} - -function responseOptions(body: string | Object, status: number, url: string): Response { +function createResponse(url: string, body: string | Object, status: number): Response { return new Response(new ResponseOptions({ body: body, status: status, diff --git a/tests/app/tests/http.ts b/tests/app/tests/http.ts index e3dd55794..e02af2ad0 100644 --- a/tests/app/tests/http.ts +++ b/tests/app/tests/http.ts @@ -1,16 +1,39 @@ // make sure you import mocha-config before @angular/core -import {assert} from "./test-config"; +import { assert } from "./test-config"; import { async, inject, } from "@angular/core/testing"; -import {ReflectiveInjector} from "@angular/core"; -import {Request, BaseRequestOptions, ConnectionBackend, Http, Response, ResponseOptions} from "@angular/http"; +import { ReflectiveInjector, Injectable } from "@angular/core"; +import { Request, BaseRequestOptions, ConnectionBackend, Http, Response, ResponseOptions } from "@angular/http"; import "rxjs/add/operator/map"; -import {MockBackend} from "@angular/http/testing"; -import {NSHttp} from "nativescript-angular/http/ns-http"; -import {NSFileSystem} from "nativescript-angular/file-system/ns-file-system"; -import {NSFileSystemMock, FileResponses} from "./mocks/ns-file-system.mock"; +import { MockBackend } from "@angular/http/testing"; +import { NSHttp } from "nativescript-angular/http/ns-http"; +import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system"; + +const AWESOME_TEAM: string = '[{"name":"Alex"}, {"name":"Rosen"}, {"name":"Panayot"}]'; + +// Filesystem mock +@Injectable() +export class NSFileSystemMock implements NSFileSystem { + public currentApp(): any { + return { path: "/app/dir" }; + } + + public fileFromPath(path: string): any { + if (path === "/app/dir/test.json") { + return { + readText: () => { return Promise.resolve(AWESOME_TEAM); } + }; + } + throw new Error("Opening non-existing file"); + } + + public fileExists(path: string): boolean { + return path === "/app/dir/test.json"; + } + +} describe("Http", () => { let http: Http; @@ -22,15 +45,15 @@ describe("Http", () => { MockBackend, { provide: NSFileSystem, useClass: NSFileSystemMock }, { - provide: Http, - useFactory: function ( - connectionBackend: ConnectionBackend, - defaultOptions: BaseRequestOptions, - nsFileSystem: NSFileSystem) { + provide: Http, + useFactory: function ( + connectionBackend: ConnectionBackend, + defaultOptions: BaseRequestOptions, + nsFileSystem: NSFileSystem) { // HACK: cast backend to any to work around an angular typings problem return new NSHttp(connectionBackend, defaultOptions, nsFileSystem); - }, - deps: [MockBackend, BaseRequestOptions, NSFileSystem] + }, + deps: [MockBackend, BaseRequestOptions, NSFileSystem] } ]); @@ -78,6 +101,6 @@ describe("Http", () => { assert.strictEqual(3, response.length); assert.strictEqual("Rosen", response[1].name); }); - connection.mockRespond(new Response(new ResponseOptions({ body: FileResponses.AWESOME_TEAM }))); + connection.mockRespond(new Response(new ResponseOptions({ body: AWESOME_TEAM }))); }); }); diff --git a/tests/app/tests/mocks/ns-file-system.mock.ts b/tests/app/tests/mocks/ns-file-system.mock.ts deleted file mode 100644 index 0f841aff5..000000000 --- a/tests/app/tests/mocks/ns-file-system.mock.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Injectable} from "@angular/core"; -import {ResponseType, Response, ResponseOptions} from "@angular/http"; - -export class FileResponses { - public static AWESOME_TEAM: string = '[{"name":"Alex"}, {"name":"Rosen"}, {"name":"Panayot"}]'; -} - -// Folder mock -class Folder { - public getFile(url: string): any { - let data; - switch (url) { - case "test.json": - data = FileResponses.AWESOME_TEAM; - break; - default: - throw (new Error("Unsupported file for the testing mock - ns-file-system-mock")); - } - return { - readText: () => { - return new Promise((resolve) => { - resolve(data); - }); - } - }; - } -} - -// Filesystem mock -@Injectable() -export class NSFileSystemMock { - public currentApp(): Folder { - return new Folder(); - } -} - From 1ad18ccef5418458012e7b23097e01dbc8bab723 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Wed, 4 Oct 2017 00:55:43 +0300 Subject: [PATCH 13/14] chore: Removing .vscode folder form rotuer tests --- e2e/router/.vscode/launch.json | 41 -------------------------------- e2e/router/.vscode/settings.json | 19 --------------- 2 files changed, 60 deletions(-) delete mode 100644 e2e/router/.vscode/launch.json delete mode 100644 e2e/router/.vscode/settings.json diff --git a/e2e/router/.vscode/launch.json b/e2e/router/.vscode/launch.json deleted file mode 100644 index 4961b50d3..000000000 --- a/e2e/router/.vscode/launch.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "android24", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "args": [ - "-u", - "tdd", - "--timeout", - "999999", - "--colors", - "${workspaceRoot}/e2e", - "--runType", - "android24", - "--reuseDevice" - ], - "internalConsoleOptions": "openOnSessionStart" - }, - { - "type": "node", - "request": "launch", - "name": "sim.iPhone7.iOS110", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "args": [ - "-u", - "tdd", - "--timeout", - "999999", - "--colors", - "${workspaceRoot}/e2e", - "--runType", - "sim.iPhone7.iOS110", - "--reuseDevice" - ], - "internalConsoleOptions": "openOnSessionStart" - } - ] - } \ No newline at end of file diff --git a/e2e/router/.vscode/settings.json b/e2e/router/.vscode/settings.json deleted file mode 100644 index 8508de074..000000000 --- a/e2e/router/.vscode/settings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "typescript.check.tscVersion": false, - "window.zoomLevel": 0, - "workbench.iconTheme": "vscode-icons", - "vsicons.dontShowNewVersionMessage": true, - "workbench.colorTheme": "Default Light+", - "files.exclude": { - "**/**/*.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/**/*.js": true, - "**/**/*.map": true, - "node_modules": true, - "hooks": true, - "platforms": true - } -} \ No newline at end of file From f8dfb2a23e07a8d223cb8df0c9db777415aa62ce Mon Sep 17 00:00:00 2001 From: vakrilov Date: Wed, 4 Oct 2017 01:09:52 +0300 Subject: [PATCH 14/14] refactor: Renamed some methods for clarity --- nativescript-angular/http-client/http-utils.ts | 2 +- nativescript-angular/http-client/ns-http-backend.ts | 8 ++++---- nativescript-angular/http/ns-http.ts | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/nativescript-angular/http-client/http-utils.ts b/nativescript-angular/http-client/http-utils.ts index 27b799383..ae0ecc6f8 100644 --- a/nativescript-angular/http-client/http-utils.ts +++ b/nativescript-angular/http-client/http-utils.ts @@ -17,7 +17,7 @@ export function getAbsolutePath(url: string, nsFileSystem: NSFileSystem): string return url; } -export function handleLocalRequest( +export function processLocalFileRequest( url: string, nsFileSystem: NSFileSystem, successResponse: httpResponseFactory, diff --git a/nativescript-angular/http-client/ns-http-backend.ts b/nativescript-angular/http-client/ns-http-backend.ts index 061fb8647..6cf2f2bf4 100644 --- a/nativescript-angular/http-client/ns-http-backend.ts +++ b/nativescript-angular/http-client/ns-http-backend.ts @@ -7,7 +7,7 @@ import { import { Observable } from "rxjs/Observable"; import { NSFileSystem } from "../file-system/ns-file-system"; -import { isLocalRequest, handleLocalRequest } from "./http-utils"; +import { isLocalRequest, processLocalFileRequest } from "./http-utils"; @Injectable() export class NsHttpBackEnd extends HttpXhrBackend { @@ -19,7 +19,7 @@ export class NsHttpBackEnd extends HttpXhrBackend { let result: Observable>; if (isLocalRequest(req.url)) { - result = this.handleLocalRequest(req.url); + result = this.handleLocalFileRequest(req.url); } else { result = super.handle(req); } @@ -27,8 +27,8 @@ export class NsHttpBackEnd extends HttpXhrBackend { return result; } - private handleLocalRequest(url: string): Observable> { - return handleLocalRequest( + private handleLocalFileRequest(url: string): Observable> { + return processLocalFileRequest( url, this.nsFileSystem, createSuccessResponse, diff --git a/nativescript-angular/http/ns-http.ts b/nativescript-angular/http/ns-http.ts index 6b4c8fe98..e0f04b661 100644 --- a/nativescript-angular/http/ns-http.ts +++ b/nativescript-angular/http/ns-http.ts @@ -12,7 +12,7 @@ import { import { Observable } from "rxjs/Observable"; import "rxjs/add/observable/fromPromise"; -import { isLocalRequest, handleLocalRequest } from "../http-client/http-utils"; +import { isLocalRequest, processLocalFileRequest } from "../http-client/http-utils"; import { NSFileSystem } from "../file-system/ns-file-system"; @@ -34,7 +34,7 @@ export class NSHttp extends Http { request(req: string | Request, options?: RequestOptionsArgs): Observable { const urlString = typeof req === "string" ? req : req.url; if (isLocalRequest(urlString)) { - return this.handleLocalRequest(urlString); + return this.requestLocalFile(urlString); } else { return super.request(req, options); } @@ -45,14 +45,14 @@ export class NSHttp extends Http { */ get(url: string, options?: RequestOptionsArgs): Observable { if (isLocalRequest(url)) { - return this.handleLocalRequest(url); + return this.requestLocalFile(url); } else { return super.get(url, options); } } - private handleLocalRequest(url: string): Observable { - return handleLocalRequest( + private requestLocalFile(url: string): Observable { + return processLocalFileRequest( url, this.nsFileSystem, createResponse,