From 3c3c10ea9671a56e545953e685f392b4951cf713 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Fri, 8 Feb 2019 18:16:59 +0200 Subject: [PATCH 01/31] chore: bump package versino tp 7.3.0 --- nativescript-angular/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index ea4257c6d..f622d58c2 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -1,6 +1,6 @@ { "name": "nativescript-angular", - "version": "7.2.0", + "version": "7.3.0", "description": "An Angular renderer that lets you build mobile apps with NativeScript.", "homepage": "https://www.nativescript.org/", "bugs": "https://github.com/NativeScript/nativescript-angular/issues", From a21454f1e1b9f823dfa71c8f6a8fc9f1ff2174c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Anton=20Bach=20Sj=C3=B8gren?= Date: Tue, 12 Feb 2019 14:19:28 +0100 Subject: [PATCH 02/31] chor(NativeScriptPlatformRef): Destroy lastModuleRef on exitEvent --- nativescript-angular/platform-common.ts | 10 ++++++++++ nativescript-angular/router/page-router-outlet.ts | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 2f7e958cd..4eb5d7900 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -33,6 +33,7 @@ import { on, launchEvent, LaunchEventData, + exitEvent, } from "tns-core-modules/application"; import { TextView } from "tns-core-modules/ui/text-view"; @@ -255,7 +256,16 @@ export class NativeScriptPlatformRef extends PlatformRef { args.root = rootContent; } ); + const exitCallback = profile( + "nativescript-angular/platform-common.exitCallback",() => { + const lastModuleRef = lastBootstrappedModule ? lastBootstrappedModule.get() : null; + if (lastModuleRef) { + lastModuleRef.destroy(); + } + } + ); on(launchEvent, launchCallback); + on(exitEvent, exitCallback); applicationRun(); } diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index 381ff52bd..6ad425c65 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -352,12 +352,19 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire // Add it to the new page page.content = componentView; - page.on(Page.navigatedFromEvent, (global).Zone.current.wrap((args: NavigatedData) => { + const navigatedFromCallback = (global).Zone.current.wrap((args: NavigatedData) => { if (args.isBackNavigation) { this.locationStrategy._beginBackPageNavigation(this.frame); this.locationStrategy.back(null, this.frame); } - })); + }); + page.on(Page.navigatedFromEvent, navigatedFromCallback); + componentRef.onDestroy(() => { + if (page) { + page.off(Page.navigatedFromEvent, navigatedFromCallback); + page = null; + } + }); const navOptions = this.locationStrategy._beginPageNavigation(this.frame); @@ -374,7 +381,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire } this.frame.navigate({ - create: () => { return page; }, + create() { + return page; + }, clearHistory: navOptions.clearHistory, animated: navOptions.animated, transition: navOptions.transition From a4ee021fb0f3b74cf2ee3b0b9ec5ca06e8b1ec61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Anton=20Bach=20Sj=C3=B8gren?= Date: Tue, 12 Feb 2019 14:34:11 +0100 Subject: [PATCH 03/31] fix(tslint): missing whitespace --- nativescript-angular/platform-common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 4eb5d7900..12958d1ae 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -257,7 +257,7 @@ export class NativeScriptPlatformRef extends PlatformRef { } ); const exitCallback = profile( - "nativescript-angular/platform-common.exitCallback",() => { + "nativescript-angular/platform-common.exitCallback", () => { const lastModuleRef = lastBootstrappedModule ? lastBootstrappedModule.get() : null; if (lastModuleRef) { lastModuleRef.destroy(); From 0f6a975b8aa1d4e90474c76efe7bb7002dedc63b Mon Sep 17 00:00:00 2001 From: Alexander Vakrilov Date: Tue, 19 Feb 2019 11:51:29 +0200 Subject: [PATCH 04/31] fix(router): routing services should be provided in forRoot only (#1729) * fix(router): routing services should be provided in forRoot only * refactor: remove logs * refactor: use single return statment in forRoot/Child --- nativescript-angular/router/router.module.ts | 45 +++++++----- tests/app/tests/router-module-tests.ts | 74 ++++++++++++++++++++ tests/package.json | 2 +- 3 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 tests/app/tests/router-module-tests.ts diff --git a/nativescript-angular/router/router.module.ts b/nativescript-angular/router/router.module.ts index 385b4fe1f..95610d6aa 100644 --- a/nativescript-angular/router/router.module.ts +++ b/nativescript-angular/router/router.module.ts @@ -19,32 +19,39 @@ export { NSEmptyOutletComponent } from "./ns-empty-outlet.component"; export type LocationState = LocationState; +const ROUTER_DIRECTIVES = [NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent]; + +const NS_ROUTER_PROVIDERS = [ + { + provide: NSLocationStrategy, + useFactory: provideLocationStrategy, + deps: [[NSLocationStrategy, new Optional(), new SkipSelf()], FrameService], + }, + { provide: LocationStrategy, useExisting: NSLocationStrategy }, + NativescriptPlatformLocation, + { provide: PlatformLocation, useClass: NativescriptPlatformLocation }, + RouterExtensions, + NSRouteReuseStrategy, + { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy }, +]; + @NgModule({ - declarations: [NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent], - providers: [ - { - provide: NSLocationStrategy, - useFactory: provideLocationStrategy, - deps: [[NSLocationStrategy, new Optional(), new SkipSelf()], FrameService], - }, - { provide: LocationStrategy, useExisting: NSLocationStrategy }, - NativescriptPlatformLocation, - { provide: PlatformLocation, useClass: NativescriptPlatformLocation }, - RouterExtensions, - NSRouteReuseStrategy, - { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy }, - ], + declarations: ROUTER_DIRECTIVES, + entryComponents: [NSEmptyOutletComponent], imports: [RouterModule, NativeScriptCommonModule], - exports: [RouterModule, NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent], + exports: [RouterModule, ...ROUTER_DIRECTIVES], schemas: [NO_ERRORS_SCHEMA], }) export class NativeScriptRouterModule { - static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders { - return RouterModule.forRoot(routes, config); + static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders { + return { + ngModule: NativeScriptRouterModule, + providers: [...RouterModule.forRoot(routes, config).providers, ...NS_ROUTER_PROVIDERS] + }; } - static forChild(routes: Routes): ModuleWithProviders { - return RouterModule.forChild(routes); + static forChild(routes: Routes): ModuleWithProviders { + return { ngModule: NativeScriptRouterModule, providers: RouterModule.forChild(routes).providers }; } } diff --git a/tests/app/tests/router-module-tests.ts b/tests/app/tests/router-module-tests.ts new file mode 100644 index 000000000..7bce3f7ef --- /dev/null +++ b/tests/app/tests/router-module-tests.ts @@ -0,0 +1,74 @@ +// make sure you import mocha-config before @angular/core +import { Component, ViewChild } from "@angular/core"; +import { nsTestBedAfterEach, nsTestBedBeforeEach, nsTestBedRender } from "nativescript-angular/testing"; +import { NativeScriptRouterModule, RouterExtensions } from "nativescript-angular/router"; +import { NSRouterLink } from "nativescript-angular/router/ns-router-link"; +import { NSLocationStrategy } from "nativescript-angular/router/ns-location-strategy"; +import { assert } from "~/tests/test-config"; +import { ActivatedRoute, Router, RouteReuseStrategy } from "@angular/router"; +import { LocationStrategy, PlatformLocation } from "@angular/common"; +import { NSRouteReuseStrategy } from "nativescript-angular/router/ns-route-reuse-strategy"; + +@Component({ + template: `` +}) +class RouterTestComponent { + @ViewChild(NSRouterLink) + nsRouterLink: NSRouterLink; +} + +describe("NativeScriptRouterModule.forRoot", () => { + beforeEach(nsTestBedBeforeEach( + [RouterTestComponent], + [], + [NativeScriptRouterModule.forRoot([])], + [])); + + afterEach(nsTestBedAfterEach()); + + it("should provide nativescript routing services", () => { + return nsTestBedRender(RouterTestComponent).then((fixture) => { + const injector = fixture.componentRef.injector + + assert.instanceOf(injector.get(LocationStrategy, null), NSLocationStrategy); + assert.instanceOf(injector.get(RouterExtensions, null), RouterExtensions); + assert.instanceOf(injector.get(RouteReuseStrategy, null), NSRouteReuseStrategy); + }); + }); + + it("should provide nativescript routing directives", () => { + return nsTestBedRender(RouterTestComponent).then((fixture) => { + const linkDirective = fixture.componentRef.instance.nsRouterLink; + assert.instanceOf(linkDirective, NSRouterLink); + }); + }); +}); + +describe("NativeScriptRouterModule.forChild", () => { + beforeEach(nsTestBedBeforeEach( + [RouterTestComponent], + [ + { provide: Router, useValue: {} }, + { provide: RouterExtensions, useValue: {} }, + { provide: ActivatedRoute, useValue: {} }, + ], + [NativeScriptRouterModule.forChild([])], + [])); + afterEach(nsTestBedAfterEach()); + + it("should not provide nativescript routing services", () => { + return nsTestBedRender(RouterTestComponent).then((fixture) => { + const injector = fixture.componentRef.injector + assert.isNull(injector.get(LocationStrategy, null)); + assert.isNull(injector.get(RouteReuseStrategy, null)); + }); + }); + + it("should provide nativescript routing directives", () => { + return nsTestBedRender(RouterTestComponent).then((fixture) => { + const linkDirective = fixture.componentRef.instance.nsRouterLink; + assert.instanceOf(linkDirective, NSRouterLink); + }); + }); +}); + diff --git a/tests/package.json b/tests/package.json index 29bede509..7375973e3 100644 --- a/tests/package.json +++ b/tests/package.json @@ -35,7 +35,7 @@ "@angular/platform-browser": "~7.2.0", "@angular/platform-browser-dynamic": "~7.2.0", "@angular/router": "~7.2.0", - "nativescript-angular": "../nativescript-angular", + "nativescript-angular": "file:../nativescript-angular/nativescript-angular-7.3.0.tgz", "nativescript-unit-test-runner": "^0.3.4", "rxjs": "~6.3.3", "tns-core-modules": "next", From 14e787fb160d2a6f8e8ebbbaff88e745eee8ac1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Anton=20Bach=20Sj=C3=B8gren?= Date: Thu, 21 Feb 2019 14:26:00 +0100 Subject: [PATCH 05/31] fix: remove rootContent on exit --- nativescript-angular/platform-common.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 12958d1ae..2ef914c98 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -262,6 +262,10 @@ export class NativeScriptPlatformRef extends PlatformRef { if (lastModuleRef) { lastModuleRef.destroy(); } + + if (!autoCreateFrame) { + rootContent = null; + } } ); on(launchEvent, launchCallback); From ea66985a9cc94a69fc40d7d8731b4a2f1262b8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Anton=20Bach=20Sj=C3=B8gren?= Date: Fri, 22 Feb 2019 11:40:38 +0100 Subject: [PATCH 06/31] fix: page might be null'ed before clearHistory's navigatedToEvent --- nativescript-angular/router/page-router-outlet.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index 6ad425c65..cc9d07cc2 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -374,10 +374,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire if (this.outlet) { this.routeReuseStrategy.clearCache(this.outlet.outletKeys[0]); } - page.off(Page.navigatedToEvent, clearCallback); }); - page.on(Page.navigatedToEvent, clearCallback); + page.once(Page.navigatedToEvent, clearCallback); } this.frame.navigate({ From ed6954a40c38c8c300ccbb59412b65fe1eafe55c Mon Sep 17 00:00:00 2001 From: vakrilov Date: Thu, 28 Feb 2019 15:08:54 +0200 Subject: [PATCH 07/31] refactor: minor changes after review --- nativescript-angular/router/ns-platform-location.ts | 12 ++++++------ nativescript-angular/router/router.module.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nativescript-angular/router/ns-platform-location.ts b/nativescript-angular/router/ns-platform-location.ts index 311faa5b8..ebd86f067 100644 --- a/nativescript-angular/router/ns-platform-location.ts +++ b/nativescript-angular/router/ns-platform-location.ts @@ -6,7 +6,7 @@ import { routerLog, isLogEnabled } from "../trace"; @Injectable() export class NativescriptPlatformLocation extends PlatformLocation { - constructor(private locationStartegy: NSLocationStrategy) { + constructor(private locationStrategy: NSLocationStrategy) { super(); if (isLogEnabled()) { routerLog("NativescriptPlatformLocation.constructor()"); @@ -18,7 +18,7 @@ export class NativescriptPlatformLocation extends PlatformLocation { } onPopState(fn: LocationChangeListener): void { - this.locationStartegy.onPopState(fn); + this.locationStrategy.onPopState(fn); } onHashChange(_fn: LocationChangeListener): void { @@ -31,18 +31,18 @@ export class NativescriptPlatformLocation extends PlatformLocation { return ""; } get pathname(): string { - return this.locationStartegy.path(); + return this.locationStrategy.path(); } set pathname(_newPath: string) { throw new Error("NativescriptPlatformLocation set pathname - not implemented"); } pushState(state: any, title: string, url: string): void { - this.locationStartegy.pushState(state, title, url, null); + this.locationStrategy.pushState(state, title, url, null); } replaceState(state: any, title: string, url: string): void { - this.locationStartegy.replaceState(state, title, url, null); + this.locationStrategy.replaceState(state, title, url, null); } forward(): void { @@ -50,6 +50,6 @@ export class NativescriptPlatformLocation extends PlatformLocation { } back(): void { - this.locationStartegy.back(); + this.locationStrategy.back(); } } diff --git a/nativescript-angular/router/router.module.ts b/nativescript-angular/router/router.module.ts index 95610d6aa..5246eeea0 100644 --- a/nativescript-angular/router/router.module.ts +++ b/nativescript-angular/router/router.module.ts @@ -29,7 +29,7 @@ const NS_ROUTER_PROVIDERS = [ }, { provide: LocationStrategy, useExisting: NSLocationStrategy }, NativescriptPlatformLocation, - { provide: PlatformLocation, useClass: NativescriptPlatformLocation }, + { provide: PlatformLocation, useExisting: NativescriptPlatformLocation }, RouterExtensions, NSRouteReuseStrategy, { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy }, From 5e13263389ae0c97cd4f92693a11ba38884fc5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Anton=20Bach=20Sj=C3=B8gren?= Date: Fri, 8 Mar 2019 15:39:51 +0100 Subject: [PATCH 08/31] fix: on destroy remove the lastBootstrappedModule If exit event was triggered twice we would try to destroy an already destroy module ref. --- nativescript-angular/platform-common.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 2ef914c98..8c1c342b6 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -260,6 +260,8 @@ export class NativeScriptPlatformRef extends PlatformRef { "nativescript-angular/platform-common.exitCallback", () => { const lastModuleRef = lastBootstrappedModule ? lastBootstrappedModule.get() : null; if (lastModuleRef) { + lastBootstrappedModule = null; + lastModuleRef.destroy(); } From 9fc172c2c2ebddc91d59f6d2578bff813eafa4fb Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 11 Mar 2019 09:33:18 +0200 Subject: [PATCH 09/31] chore(tsconfig): remove path resolution for short imports (`ui/page`) The usage of short imports, e.g, `import * as Page from "ui/page"` instead of `import * as Page from "tns-core-modules/ui/page"` is *deprecated* since {N} 5.2. --- nativescript-angular/tsconfig.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/nativescript-angular/tsconfig.json b/nativescript-angular/tsconfig.json index e05ddfed9..e8a09f283 100644 --- a/nativescript-angular/tsconfig.json +++ b/nativescript-angular/tsconfig.json @@ -16,14 +16,7 @@ "dom", "es6", "es2015.iterable" - ], - "baseUrl": ".", - "paths": { - "*": [ - "./node_modules/tns-core-modules/*", - "./node_modules/*" - ] - } + ] }, "angularCompilerOptions": { "genDir": ".", From 3dffbd512f31856c48dc9f9dcea754f0bca7a03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Anton=20Bach=20Sj=C3=B8gren?= Date: Mon, 11 Mar 2019 13:41:23 +0100 Subject: [PATCH 10/31] fix: the exit event is triggered on restart Don't destroy the angular module on restart --- nativescript-angular/platform-common.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 8c1c342b6..d6a4c557f 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -34,6 +34,7 @@ import { launchEvent, LaunchEventData, exitEvent, + ApplicationEventData, } from "tns-core-modules/application"; import { TextView } from "tns-core-modules/ui/text-view"; @@ -257,9 +258,16 @@ export class NativeScriptPlatformRef extends PlatformRef { } ); const exitCallback = profile( - "nativescript-angular/platform-common.exitCallback", () => { + "nativescript-angular/platform-common.exitCallback", (args: ApplicationEventData) => { + const androidActivity = args.android; + if (androidActivity && !androidActivity.isFinishing()) { + // Exit event was triggered as a part of a restart of the app. + return; + } + const lastModuleRef = lastBootstrappedModule ? lastBootstrappedModule.get() : null; if (lastModuleRef) { + // Make sure the module is only destroyed once lastBootstrappedModule = null; lastModuleRef.destroy(); From 9ba7fca7af7dceb2eb3fbe3eb217d4f49d29f672 Mon Sep 17 00:00:00 2001 From: tsonevn Date: Tue, 12 Mar 2019 09:58:54 +0200 Subject: [PATCH 11/31] NS Angular api ref build script --- build-docs.sh | 9 +++++ nativescript-angular/package.json | 7 +++- nativescript-angular/tsconfig.typedoc.json | 44 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100755 build-docs.sh create mode 100644 nativescript-angular/tsconfig.typedoc.json diff --git a/build-docs.sh b/build-docs.sh new file mode 100755 index 000000000..a0f39ec85 --- /dev/null +++ b/build-docs.sh @@ -0,0 +1,9 @@ +set -e + +ENV="${ENV:-dev}" +DIST_DIR="nativescript-angular/bin/dist" +APIREF_DIR="$DIST_DIR/ng-api-reference" +rm -rf "$APIREF_DIR" +cd "nativescript-angular" +npm install +npm run typedoc diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index f622d58c2..062189aaa 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -34,7 +34,8 @@ "tsc-w": "tsc -p tsconfig.json -w", "ngc": "ngc -p tsconfig.json", "prepare": "npm run ngc", - "version": "rm -rf package-lock.json && conventional-changelog -p angular -i ../CHANGELOG.md -s && git add ../CHANGELOG.md" + "version": "rm -rf package-lock.json && conventional-changelog -p angular -i ../CHANGELOG.md -s && git add ../CHANGELOG.md", + "typedoc": "typedoc --tsconfig \"./tsconfig.typedoc.json\" --out ./bin/dist/ng-api-reference --includeDeclarations --name \"NativeScript Angular\" --theme ./node_modules/nativescript-typedoc-theme --excludeExternals --externalPattern \"**/+(tns-core-modules|module|declarations).d.ts\"" }, "bin": { "update-app-ng-deps": "./bin/update-app-ng-deps" @@ -73,6 +74,8 @@ "tns-core-modules": "next", "tslint": "^5.5.0", "typescript": "~3.1.1", - "zone.js": "^0.8.4" + "zone.js": "^0.8.4", + "nativescript-typedoc-theme": "git://github.com/NativeScript/nativescript-typedoc-theme.git#master", + "typedoc": "^0.13.0" } } diff --git a/nativescript-angular/tsconfig.typedoc.json b/nativescript-angular/tsconfig.typedoc.json new file mode 100644 index 000000000..4faa0da11 --- /dev/null +++ b/nativescript-angular/tsconfig.typedoc.json @@ -0,0 +1,44 @@ +{"compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noImplicitUseStrict": true, + "noEmitHelpers": true, + "declaration": true, + "removeComments": false, + "noEmitOnError": true, + "noImplicitAny": false, + "lib": [ + "dom", + "es6", + "es2015.iterable" + ], + "baseUrl": ".", + "paths": { + "*": [ + "./node_modules/tns-core-modules/*", + "./node_modules/*" + ] + } + }, + "angularCompilerOptions": { + "genDir": ".", + "skipMetadataEmit": false, + "skipTemplateCodegen": true, + "strictMetadataEmit": true + }, +"exclude": [ + "./node_modules", + "tns-core-modules/references.d.ts", + "tns-core-modules/node_modules", + "tns-core-modules/ui/frame/transition-definitions.android.d.ts", + "./zone-js", + "./index.ts", + "./bin", + "./index.d.ts", + "./testing" + ] +} \ No newline at end of file From b1c4340aa6859f32c9f69f0f15e5c122274535e4 Mon Sep 17 00:00:00 2001 From: tsonevn Date: Tue, 12 Mar 2019 11:55:16 +0200 Subject: [PATCH 12/31] exclude files from api ref build --- nativescript-angular/tsconfig.typedoc.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/nativescript-angular/tsconfig.typedoc.json b/nativescript-angular/tsconfig.typedoc.json index 4faa0da11..3074c04b8 100644 --- a/nativescript-angular/tsconfig.typedoc.json +++ b/nativescript-angular/tsconfig.typedoc.json @@ -39,6 +39,23 @@ "./index.ts", "./bin", "./index.d.ts", - "./testing" + "./testing", + "./animations/index.ts", + "./animations/utils.ts", + "./app-host-view.ts", + "./common/utils.ts", + "./dom-adapter.ts", + "./file-system", + "./forms/index.ts", + "./forms/value-accessors/index.ts", + "./lang-facade.ts", + "./polyfills", + "./router/private-imports", + "./schema-registry.ts", + "./http-client/index.ts", + "./http/index.ts", + "./router/index.ts", + "" + ] } \ No newline at end of file From 9cfa127d46427a0286d5b0840b51627289675203 Mon Sep 17 00:00:00 2001 From: Marcus Williams Date: Fri, 1 Feb 2019 19:35:52 +0000 Subject: [PATCH 13/31] =?UTF-8?q?feat(modal):=20add=20=E2=80=98ios=20prese?= =?UTF-8?q?ntationStyle=E2=80=99=20option=20to=20ModalDialogParams=20feat(?= =?UTF-8?q?modal):=20expose=20current/future=20core=20options=20via=20Moda?= =?UTF-8?q?lDialogParams?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nativescript-angular/directives/dialogs.ts | 67 +++++++++------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts index 7c8b45739..3c700777d 100644 --- a/nativescript-angular/directives/dialogs.ts +++ b/nativescript-angular/directives/dialogs.ts @@ -7,6 +7,7 @@ import { ReflectiveInjector, Type, ViewContainerRef, + ElementRef, } from "@angular/core"; import { NSLocationStrategy } from "../router/ns-location-strategy"; @@ -17,15 +18,15 @@ import { AppHostView } from "../app-host-view"; import { DetachedLoader } from "../common/detached-loader"; import { PageFactory, PAGE_FACTORY } from "../platform-providers"; import { once } from "../common/utils"; -import { topmost, Frame } from "tns-core-modules/ui/frame"; +import { topmost, Frame, ShowModalOptions } from "tns-core-modules/ui/frame"; -export interface ModalDialogOptions { +export type BaseShowModalOptions = Pick>; + +export interface ModalDialogOptions extends BaseShowModalOptions { context?: any; - fullscreen?: boolean; - animated?: boolean; - stretched?: boolean; viewContainerRef?: ViewContainerRef; moduleRef?: NgModuleRef; + sourceView?: ElementRef; } export class ModalDialogParams { @@ -35,13 +36,10 @@ export class ModalDialogParams { } } -interface ShowDialogOptions { +interface ShowDialogOptions extends BaseShowModalOptions { containerRef: ViewContainerRef; context: any; doneCallback; - fullscreen: boolean; - animated: boolean; - stretched: boolean; pageFactory: PageFactory; parentView: ViewBase; resolver: ComponentFactoryResolver; @@ -54,16 +52,19 @@ export class ModalDialogService { } public showModal(type: Type, - { viewContainerRef, moduleRef, context, fullscreen, animated, stretched }: ModalDialogOptions + options: ModalDialogOptions ): Promise { - if (!viewContainerRef) { + if (!options.viewContainerRef) { throw new Error( "No viewContainerRef: " + "Make sure you pass viewContainerRef in ModalDialogOptions." ); } - let parentView = viewContainerRef.element.nativeElement; + let parentView = options.viewContainerRef.element.nativeElement; + if (options.sourceView) { + parentView = options.sourceView.nativeElement; + } if (parentView instanceof AppHostView && parentView.ngAppRoot) { parentView = parentView.ngAppRoot; } @@ -75,11 +76,11 @@ export class ModalDialogService { parentView = parentView._ngDialogRoot; } - const pageFactory: PageFactory = viewContainerRef.injector.get(PAGE_FACTORY); + const pageFactory: PageFactory = options.viewContainerRef.injector.get(PAGE_FACTORY); // resolve from particular module (moduleRef) // or from same module as parentView (viewContainerRef) - const componentContainer = moduleRef || viewContainerRef; + const componentContainer = options.moduleRef || options.viewContainerRef; const resolver = componentContainer.injector.get(ComponentFactoryResolver); let frame = parentView; @@ -93,16 +94,14 @@ export class ModalDialogService { setTimeout(() => { try { this._showDialog({ - containerRef: viewContainerRef, - context, + ...options, + containerRef: options.viewContainerRef, + context: options.context, doneCallback: resolve, - fullscreen, - animated, - stretched, pageFactory, parentView, resolver, - type, + type }); } catch (err) { reject(err); @@ -111,23 +110,12 @@ export class ModalDialogService { }); } - private _showDialog({ - containerRef, - context, - doneCallback, - fullscreen, - animated, - stretched, - pageFactory, - parentView, - resolver, - type, - }: ShowDialogOptions): void { + private _showDialog(options: ShowDialogOptions): void { let componentView: View; let detachedLoaderRef: ComponentRef; const closeCallback = once((...args) => { - doneCallback.apply(undefined, args); + options.doneCallback.apply(undefined, args); if (componentView) { componentView.closeModal(); this.location._closeModalNavigation(); @@ -136,15 +124,15 @@ export class ModalDialogService { } }); - const modalParams = new ModalDialogParams(context, closeCallback); + const modalParams = new ModalDialogParams(options.context, closeCallback); const providers = ReflectiveInjector.resolve([ { provide: ModalDialogParams, useValue: modalParams }, ]); - const childInjector = ReflectiveInjector.fromResolvedProviders(providers, containerRef.parentInjector); - const detachedFactory = resolver.resolveComponentFactory(DetachedLoader); - detachedLoaderRef = containerRef.createComponent(detachedFactory, -1, childInjector, null); - detachedLoaderRef.instance.loadComponent(type).then((compRef) => { + const childInjector = ReflectiveInjector.fromResolvedProviders(providers, options.containerRef.parentInjector); + const detachedFactory = options.resolver.resolveComponentFactory(DetachedLoader); + detachedLoaderRef = options.containerRef.createComponent(detachedFactory, -1, childInjector, null); + detachedLoaderRef.instance.loadComponent(options.type).then((compRef) => { const detachedProxy = compRef.location.nativeElement; if (detachedProxy.getChildrenCount() > 1) { @@ -157,9 +145,10 @@ export class ModalDialogService { (componentView.parent).removeChild(componentView); } + // TODO: remove cast after https://github.com/NativeScript/NativeScript/pull/5734 // is in a published version of tns-core-modules. - (parentView).showModal(componentView, context, closeCallback, fullscreen, animated, stretched); + (options.parentView).showModal(componentView, { ...options, closeCallback }); }); } } From a4282ffb8dd7f332f465cacbe8c36182489e61fe Mon Sep 17 00:00:00 2001 From: VladimirAmiorkov Date: Mon, 25 Mar 2019 10:03:47 +0200 Subject: [PATCH 14/31] chore: remove object cast as it is not needed --- nativescript-angular/directives/dialogs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts index 3c700777d..fbdecb34d 100644 --- a/nativescript-angular/directives/dialogs.ts +++ b/nativescript-angular/directives/dialogs.ts @@ -148,7 +148,7 @@ export class ModalDialogService { // TODO: remove cast after https://github.com/NativeScript/NativeScript/pull/5734 // is in a published version of tns-core-modules. - (options.parentView).showModal(componentView, { ...options, closeCallback }); + options.parentView.showModal(componentView, { ...options, closeCallback }); }); } } From c9dc82681bb16631fc9c41f2ea4515fa2b82c44b Mon Sep 17 00:00:00 2001 From: VladimirAmiorkov Date: Mon, 25 Mar 2019 10:17:38 +0200 Subject: [PATCH 15/31] chore: add example that shows "popover" mode for modal service --- .../navigation/basic.navigation.component.ts | 38 +++++++++++++------ e2e/modal-navigation-ng/package.json | 1 + e2e/modal-navigation-ng/references.d.ts | 1 + 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 e2e/modal-navigation-ng/references.d.ts diff --git a/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts b/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts index 54c818612..64e2f6070 100644 --- a/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts +++ b/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewContainerRef, Input } from "@angular/core"; +import { Component, ViewContainerRef, Input, ViewChild } from "@angular/core"; import { Router, NavigationEnd } from "@angular/router"; import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs"; import { ModalComponent } from "../modal/modal.component"; @@ -16,12 +16,14 @@ import { ModalViewComponent } from "~/modal-shared/modal-view.component"; - + + ` }) export class BasicsNavigationComponent { + @ViewChild("popoverButtonComp") popoverButtonComp; @Input() col: number; constructor( private modal: ModalDialogService, @@ -29,7 +31,7 @@ export class BasicsNavigationComponent { private vcf: ViewContainerRef, private viewContainerRefService: ViewContainerRefService) { } - + onModalNoFrame() { const options: ModalDialogOptions = { context: { @@ -74,14 +76,28 @@ export class BasicsNavigationComponent { onRootModalTap(): void { const options: ModalDialogOptions = { - viewContainerRef: this.viewContainerRefService.root, - context: {}, - fullscreen: true + viewContainerRef: this.viewContainerRefService.root, + context: {}, + fullscreen: true }; - + this.modal.showModal(ModalViewComponent, options) - .then((result: string) => { - console.log(result); - }); - } + .then((result: string) => { + console.log(result); + }); + } + + onPopoverModal() { + const options: ModalDialogOptions = { + viewContainerRef: this.viewContainerRefService.root, + context: {}, + ios: { + presentationStyle: UIModalPresentationStyle.Popover + }, + sourceView: this.popoverButtonComp + }; + + this.modal.showModal(ModalViewComponent, options) + .then((result: string) => { console.log(result);}); + } } diff --git a/e2e/modal-navigation-ng/package.json b/e2e/modal-navigation-ng/package.json index 444d4b4ee..fc6209dc9 100644 --- a/e2e/modal-navigation-ng/package.json +++ b/e2e/modal-navigation-ng/package.json @@ -46,6 +46,7 @@ "nativescript-dev-appium": "next", "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", + "tns-platform-declarations": "next", "typescript": "~3.1.1" }, "scripts": { diff --git a/e2e/modal-navigation-ng/references.d.ts b/e2e/modal-navigation-ng/references.d.ts new file mode 100644 index 000000000..b945d69c5 --- /dev/null +++ b/e2e/modal-navigation-ng/references.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file From 0e90cc382ac5a67f61a23ac4eef936eb007d0f66 Mon Sep 17 00:00:00 2001 From: VladimirAmiorkov Date: Tue, 26 Mar 2019 11:10:44 +0200 Subject: [PATCH 16/31] chore: remove old todo comment --- nativescript-angular/directives/dialogs.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts index fbdecb34d..17ad11289 100644 --- a/nativescript-angular/directives/dialogs.ts +++ b/nativescript-angular/directives/dialogs.ts @@ -145,9 +145,6 @@ export class ModalDialogService { (componentView.parent).removeChild(componentView); } - - // TODO: remove cast after https://github.com/NativeScript/NativeScript/pull/5734 - // is in a published version of tns-core-modules. options.parentView.showModal(componentView, { ...options, closeCallback }); }); } From 2ce80074937b9b73e999fdf08fe0d9c2cd7370e1 Mon Sep 17 00:00:00 2001 From: VladimirAmiorkov Date: Wed, 27 Mar 2019 15:44:01 +0200 Subject: [PATCH 17/31] chore(list-view): change the import to be from the base module chore(list-view): rename the new options API and change its type --- nativescript-angular/directives/dialogs.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts index 17ad11289..f5905f059 100644 --- a/nativescript-angular/directives/dialogs.ts +++ b/nativescript-angular/directives/dialogs.ts @@ -18,7 +18,8 @@ import { AppHostView } from "../app-host-view"; import { DetachedLoader } from "../common/detached-loader"; import { PageFactory, PAGE_FACTORY } from "../platform-providers"; import { once } from "../common/utils"; -import { topmost, Frame, ShowModalOptions } from "tns-core-modules/ui/frame"; +import { topmost, Frame } from "tns-core-modules/ui/frame"; +import { ShowModalOptions } from "tns-core-modules/ui/core/view"; export type BaseShowModalOptions = Pick>; @@ -26,7 +27,7 @@ export interface ModalDialogOptions extends BaseShowModalOptions { context?: any; viewContainerRef?: ViewContainerRef; moduleRef?: NgModuleRef; - sourceView?: ElementRef; + target?: View; } export class ModalDialogParams { @@ -62,9 +63,10 @@ export class ModalDialogService { } let parentView = options.viewContainerRef.element.nativeElement; - if (options.sourceView) { - parentView = options.sourceView.nativeElement; + if (options.target) { + parentView = options.target; } + if (parentView instanceof AppHostView && parentView.ngAppRoot) { parentView = parentView.ngAppRoot; } From cb7da864ead226c777d85c6652862f329e7d3893 Mon Sep 17 00:00:00 2001 From: VladimirAmiorkov Date: Wed, 27 Mar 2019 16:00:53 +0200 Subject: [PATCH 18/31] chore: update "pop over modal" example with new API --- .../app/navigation/basic.navigation.component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts b/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts index 64e2f6070..ad0cd0031 100644 --- a/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts +++ b/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewContainerRef, Input, ViewChild } from "@angular/core"; +import { Component, ViewContainerRef, Input, ViewChild, ElementRef } from "@angular/core"; import { Router, NavigationEnd } from "@angular/router"; import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs"; import { ModalComponent } from "../modal/modal.component"; @@ -16,14 +16,14 @@ import { ModalViewComponent } from "~/modal-shared/modal-view.component"; - - + + ` }) export class BasicsNavigationComponent { - @ViewChild("popoverButtonComp") popoverButtonComp; + @ViewChild("popoverButtonComp") popoverButtonComp: ElementRef; @Input() col: number; constructor( private modal: ModalDialogService, @@ -94,7 +94,7 @@ export class BasicsNavigationComponent { ios: { presentationStyle: UIModalPresentationStyle.Popover }, - sourceView: this.popoverButtonComp + target: this.popoverButtonComp.nativeElement }; this.modal.showModal(ModalViewComponent, options) From 3feada8485161dfef3ef301f88c2718b0fe88cd4 Mon Sep 17 00:00:00 2001 From: VladimirAmiorkov Date: Wed, 27 Mar 2019 16:04:24 +0200 Subject: [PATCH 19/31] chore: fix tslint error --- nativescript-angular/directives/dialogs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts index f5905f059..51363c032 100644 --- a/nativescript-angular/directives/dialogs.ts +++ b/nativescript-angular/directives/dialogs.ts @@ -6,8 +6,7 @@ import { NgModuleRef, ReflectiveInjector, Type, - ViewContainerRef, - ElementRef, + ViewContainerRef } from "@angular/core"; import { NSLocationStrategy } from "../router/ns-location-strategy"; From bfb5f4a5ccb55374d63cec8df285d717fd3d69ee Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Mon, 22 Apr 2019 17:26:51 +0300 Subject: [PATCH 20/31] docs: update contribution change log --- CONTRIBUTING.md | 113 ++++++++++++++---- merge-guidance-schema.png | Bin 0 -> 64596 bytes release-contribution-guide-schema-webpack.png | Bin 0 -> 97021 bytes 3 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 merge-guidance-schema.png create mode 100644 release-contribution-guide-schema-webpack.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 13889f11d..41aa62d06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,36 +81,109 @@ If you want to contribute, but you are not sure where to start - look for [issue ## Publishing new versions -Instructions how to publish a new version for Maintainers. -1. Execute `npm install` to install dependencies and prepare the package for publishing: -```bash -cd nativescript-angular/nativescript-angular -npm install +## Releasing new versions +Instructions how to release a new version for **NativeScript Core Team Members**. + +![](./release-contribution-guide-schema-webpack.png?raw=true) + +1. Checkout release branch +``` +cd nativescript-angular/nativescript-angular && git checkout release && git pull +``` +#### If we prepare major or minor release, merge master in release branch else **skip this step**. ``` +git merge --ff-only origin/master +``` +*** Note: If there are commits in release branch which are not merged in master branch '-ff-merge' command will fail. +In this case the commits should be merge firstly from release in master branch as explained in section 'Merge changes from release into master' and then repeat step 1. -2. Add the following to your `.npmrc`: +2. Execute `npm i` to install dependencies: +``` +cd nativescript-angular && npm i +``` +3. Execute [`npm version`](https://docs.npmjs.com/cli/version) to bump the version: +``` +npm --no-git-tag-version version [patch|minor|major] -m "release: cut the %s release" +``` +or ``` -tag-version-prefix="" -message="release: cut the %s release" +npm --no-git-tag-version version [version] --allow-same-version -m "release: cut the %s release" ``` +NOTE: Check the changelog!!! -3. Create new branch for the release: -```bash -git checkout -b username/release-version +7. Create release-branch with change log +``` +git checkout -b release-[version] ``` -4. Execute [`npm version`](https://docs.npmjs.com/cli/version) to bump the version in the `package.json` file, tag the release and update the CHANGELOG.md: -```bash -npm version [patch|minor|major] +7. Add changes +``` +git add changed-files +git commit -m "release: cut the %s release" +git push +``` +8. Create git tag version with format 0.22.3 ``` +git tag version +git push --tags +``` +9. Create a pull request from git in web or try to use script below. Be careful to base your branch on the correct "release" branch +``` +curl -d '{"title": "release: cut the [version] release","body": "docs: update changelog","head": "[BRANCH]","base": "release"}' -X POST https://api.github.com/repos/NativeScript/nativescript-dev-webpack/pulls -H "Authorization: token ${GIT_TOKEN}" +``` +10. Merge PR into release branch. -5. Push all the changes to your branch and create a pull request: -```bash -git push --set-upstream origin username/release-version --tags +11. If all checks has passed publish package. Usually the night builds will be triggered and the package will be ready to be released on the next day. +12. Don't forget to tag the release branch +``` +git tag [version] +git push tags +``` +Tips to remove tags: +``` +git push --delete origin [version] +git tag -d [version] ``` -6. Publish the package to `npm` after the pull request is merged: -```bash -npm publish +## Merge changes from release into master + +![](./merge-guidance-schema.png) + +### Here are steps described in the diagram above. + +1. Make sure you are in release branch: +``` +git checkout release +git pull +``` +2. Create PR to merge changes back in master and preserve history: +``` +git checkout -b merge-release-in-master-branch +git push --set-upstream origin merge-release-in-master-branch +git merge origin/master +``` +3. Resolve conflicts. Choose to keep the version of master branch. If it is needed to revert versions of modules, see at the bottom. + +4. Add conflicts: +``` +git add resolved files +``` +5. Commit changes with default merge message: +``` +git commit +git push +``` + +6. Create pull request. Replace replace env merge-release-in-master-branch with its value +``` +curl -d '{"title": "chore: merge release in master","body": "chore: merge release in master","head": "merge-release-in-master","base": "master"}' -X POST https://api.github.com/repos/NativeScript/NativeScript/pulls -H "Authorization: token ${GIT_TOKEN}" +``` + +**If needed, revert version of modules and platform declarations to take the one from master:** +``` +git checkout origin/master nativescript-angular/package.json +git commit --amend +git push --force-with-lease ``` +This will require to repeat steps from 1 to 4, since we need to keep the branches with the same history diff --git a/merge-guidance-schema.png b/merge-guidance-schema.png new file mode 100644 index 0000000000000000000000000000000000000000..dd882471e0fadac0b06e3daac0a1be018fb7d0f5 GIT binary patch literal 64596 zcmeEt19PQa7iHM7ZCjmmY}-0FDr zd=K&NNCg)h;g+N#8h;j8z8EWGeHHj+Ene0>GDC3p9zX~QrsN}Vr1#aj_X=W zFQmo{>?Ao`db0UZ&lk++Aa4c2+Fb6mvd?eVYfhgS&y!Z)k1$@zJ}7Hhs6WI>9;TllB8Lk;d&(D6-h1+wb;*`VL|OF`#TyH#wSS0^%ilY@$#kH8nA^GT+_ z=w=*#=C59hDm5Xn+mih(y7@%@_?^z3>5p%zW=JO;Di)LXepxkoj1g`bqpY|n32qH;X1m_^1O2K!rZ#Qxcz!)3L@2=DdboWIp5RB z1& zn7DT?tl1SeQ>|nOF8|uiD`gM!X?sRNR{(sW(k2i%XCm+zr;M5;Il#3#Ra%*sfH{W8 zpJMd7Vg*8m?=SBqB=Knkc;AgscH>F)+M^CQr_q;luXkv-YGo_ZTc*>=pR|Om z{PpDXREU0`;+muU;`G>jTf~%x|F+gi7l2*Wn!n6sq%Ka3=tbs;N`~Hp^pgjGCz=zb2+LNet5n*-)=Jxp20|p^0u>T};N?<=1uJe5 zX_3LJB161MZVLN+C>Qi!Y2^} zhQ3QIRLKRL3^Ey84K~9m>&)X%KQr_f8GcZ^dtZCCjvW#0x6-J?>3-tIq#2ns7Z$7* z3{x4!P0_`pwbdg&5XQPiZovxhDAArRjM1*T_e7SIi&*Szpbna&z4Au5BgVdrnIfwh z_QyjsC}ow}iE9dkR9&C=vwf#S<2K0RHTUD*R%Y8KEvwsBG?RxtWdKhWS6;)QR4$XL zi%85-(=DON!7IX3nSgPtwE#tc_g;u3ryi2xmDvhW)d2RkiQ#aZDqR|4@A^vb2EGn| zLYghI0xV{C!6a+9y2vF1D$JD_-B9sEk?ZICeGP{_82#R!4VNL-eyQj{cS(1b_V8Q# zpal^`_}s1nJte8)Txg0cj#@3fq^LKJ8wI=^a}jpx)e8esN&tFjn;@dyp;D_~d|HYE z$_5OI!quUj;1n{Z8s9b=0Gh_U>&J(K|F`#2yS*mc$kt@&YR}V@oQG;TcPsk2bJ}rli~n5|M|4 zLc=+8GH89^G~5nKrzzBgyrB$dY4+>mc9xTL;8d_;ybejTNAzhWA_r3KZOV37y*7251Np#^cOt( zAfBV}RQE}+J`UnTo~v%DnSqSu`-m|@KWj&l70srL#1AMNi-i}Jxa7|Gjr2pQde6y= zBZ+X$$_paTSp5YBxljqu4|H6tqnN;}k2!|!2$7Rvyr%0y(AyK*c7DHs1Q|eBPMQw{ zNR#(NW1xr!=+ZcM3u#h^X>mRi`W4xS zr(!Ifh&NPl3(AJ)-f_q`4fPtn1mJlf4E?e9T8j0}3#P0dd<*XH!@Av;!_S^DGV@%_ z>aO1FXAX;xg=D7CsZF3AD0$_C{M9MYyjADD(M)QficgR`@evmG@P#QOl+pg@$8?W2 z1S5*{ELo;5{QmNfx$0_sT&Sx4zlG#1MkHdYIAAAyTRnKzy>(Mo50x2we6l`WM^QD? zxL&h{Uzs|iMo8SQ6g*6eQmqBOW_;FFuAd$ptGM-?`De4tuNjnHgBy9U9)l6`);@tw z4Z$9OfcSwVgawpcfzL7_T$M#Ie)0)If`Q~bZsfZlnazNK0|SGL$cLdmo6T^EUy6jG z0)yj({1R89IGk4T>_7QK~83e_G@ zE|Qx+yp>6dg?t=M(Ek3m70=CqTyP~u%u#Cb{Pbf8$YwLYW+Q8b8b5t#mTzdlsszQ=K>jNGbknIscJBvwc?N<*NYXWW{y@J5}u_U;UlXxaAXDjd; z@eA}KCK1QW9dI!1_TnfEr1FF$W|43r!yrj%DV%Cc)Y7?hw;F+fl;zG8@o zFvBthG1}`-7}f-wU_LBXar8dk`NPS2a`UdjB(#AERKHd! zV`QnQE9;BpIcknbv*G{?{aK3z1o#Bl+ig9&xzUj8jmb#(#UDKOA`!&e46lZ~i^tAD zrN!+H(;Am2tow8^=DNXtNHLH6EpZ2J(QiaT8^y%yue0C_@E2NDS}zB}l&KO6(y@*g zmqTOCp^6*at4ez!5S^UooSK)Oq}9T^l?1gdTPz9`O4{@-*CQmxKk@!v;{9u>U`Xy( zCzL}~+QGy~oEXWD{kRrkiTttH-W;YKr5%sM$=#98j^?rr&JpbbYWq$l1c{l#@Og4Y zj!>0$+KfN`B;KtAIrKqNluSDislydpqHbqIrK71Kq39a+R6}nv6!6;Iu2s>*QB80> znjn((h<*C5Y>;O_O;IC|X#CeCuCRXc_ftF_u1J;E>k+iZx+1D>79!_tzx6YH%Vk>f z(ZTgL?irX=$|JZV(~BdwGRpcCIBF)cYW^DHI@5QB@V(ak^q|iLCPw%CId=E{2LE{= zvaD5N|W&bFrd)%oI+)$Ah07^dQD4;D9naznAR-D|T1m_aNx^eVHt!)5V;nL!SBo0gv=OV;jN$-aI+tYR?B+Ser$345l9oVZ*IH zjfM}CQq|tC@J*Hj)v1yj8g=J!R*$hH&IRH03W1tKE8m|WJ#SBKrpY3wz1&3FuG--0 z{atmQ$!ggE*CrWtHufJ=1AN3{wQ=VmO$AcEs!6q68|#sPS9rVK&GvkO?uk@#FX{Yf zvk}gEv&|&g5+t??*2#(WL@KgZ+eD!Em-(sFmS#^G9I#dVhQ3HSCMN0Oelz)}0A_MSnN zK+$CVOyFbFdx^tFOv{bS!DJ$u{DXifwW^9JQGfsKa<}t6hTK>(^Dt_K#(3;7o5W|L z$lImx^9cn$i@W5MQmq`5w?t1a+3D;|!}EPqaSg#UWbT7%s?*MJZ!At@^VRsA)2cyy z?>rK^AlK*pAyjj8+H1|#a_yOcGZMF8^r=`DFM&o^I-5cB7UJXS)u!0URj0qf>yro1 zW-@#Ca3aU7YQt1L?~AZPYf;9hjK&il{bqA0X{gtg_jp>da%%$ph-P}Nk$6?bSE@L& z-fg9#SZWdbr_xO4qWepTt~;y*{~Mx*GH;2ac+BHD+4e^N(Zu+sO>Z2Fh4N6+yuJ#} z25LoIhQEF(PRI!Yh~isQ5hawRZ=IxSIAz22{e?PhcS0|dR-ao*wbCK>*IVBalPa}> zH3A@xiJLB#1ITNIzeAM0~W!pt@h&*y7I@9+l~XyBV5`lYT)IZyY4`{k$79I{Nc>O3{mg>(K})D zhJx0N;IhaiQf0c=(yR}}@SjwHfxIfr(u+~J#v{8|$FooM3PKL7RC47IRmqf5P&^(f zi>>1ALyF?VR0tO?Wh%37eIxodkO)32@S}GI^UJ>qJ;Dw~i}F4d4^>8!8l(+eKQ!XW zM+D;#GQ}bnRPkf0RD*7cwf5mqhZ3a@J<2gh>oicg^H?PWYxwUimj!8lNG(+Mr&o31 zm*@h1BgJu*T5x22t5DUbE?H6oS#rX#lwcCAhVAu^$nCR=aatUqnGs!BnNZ)Z7-V}5 z1VW{i(AGF;h%4@nd8|6$pN!ue;j8w$?@CGycKBP8ngx_(4^7S~&r{a(zR8-Rg-lr2 zCi@I8c?Y?)Ff<<@th~*+=j*EdTZ!Q>!AJozyTDant?o{BaXBJ@PB!Kj>Tt%mv?BAbOFkOjvQ zMWQVM!pgOFKO3>ADTL=(^RUCK5+zO1RJy0fDIyk~*}?Y4UU77{G79aAM2TY5pm=k& z1uRgAZaFJjit9z2%>`qRGMT9X$5ZphF?AZL6H7kFW?foZ3u|oWXWVEKd_$vT`k`W4 z(!8#$q6BK23Zru_XYIsCj+^}<_&nB7+S<;8odyec>@w-?-|mvI$rc?>19RES;B$4Ykcp1A^Q1a@~|Jr z&^@DEcFIxhiiUWu_l<3rM3kMq$FJWD3Nz0qqi~xrKf+1+rFHc;_vY zf(FAOX$(ixBpv#Tdh`1cgr`RMG~r5`1==ET3agHHV^|#%`8vCYXi>#(1SR18ZC#ns z_IU%uW&w6C}sx^fblv zF?eaK4!8bE65WQ7Lyvi$a;>DrRFNn!T_W_xUeP6wTFFCr(KLx{S&Ahc+PShiSS%VZ zGB0v33gZf?@h#FENKi(Vv^DNF+~(rB8{o+v7f8ZUU>8Kt1^?9fO(cYy(Us4goee$& z#De%l>onkNQPAME*}iw#cX@Y3RL#m9NJRK0L0l~JH0p&{3tBcr?CeX9!zBMy;y_U9 zWVRQJ1w)z-EMKfI{YFB|JOc0I|EXfw zrMGT=I*IGm7q1wS<@CCd1)LotA!DQGwJ&5`3>O;u2}EhU>k0qFgt8`!Z&!(Dw9=$; z*~iLM(CPl_{HjCEm1B-B@DF(0@&l{9T#E9XH*b&G1NK9h zDtFOqFgOU^7_Wb&Ah3Y&tyLS&2ZymcJL6~=PQ^$o^=dBdP*tr8s^52%!#(txYeQ&9 zb~Q8c1zl|lSLG$adMK4yBloTYx>T+D+2wrJ*Skgd{zV#Z|GV2d-)hZKG~d?l`&`=% zAi3U=n~g4f2KA4qWmUz(6d;NT?3efnnjO`PB(?JrhV$6VgElMj31FEuY6%4E5 zkr#N(>Rs>#8cIN<74>O!`bjivQtXa$DzgHJ6ggm) zXv=?2RQsW;_n+iWDs(ScJhiK`oPRX`I91hI2!D(HNbpYlh;$eY zJ^S*yY?7VYRMxZ*v)vmk#EWbM^QuwzLx3CYTZ}IaWchkE=q1}_rQYW24g*oc6o+rt}vwC7}k9hq*6YFB=&u`n9 z(`ZyF)?Z}%=1=AH&evSFz($hjVU;$Nsg!={BqNy&prW3y{ti|&0&#;w3J^$EUzM4knvRC**@6Uh-WmYe6wspR^S#6NgzHEk{($tH-eCM55KQG6~Rn#KH>)VZ$^n8NFO`C zYy@-)o0_uox>jM6o^N$p5kdzAW`Pzi-G_Bha(p9yn1+s`6jMy5s%ml(9LwZ|M3`S| z?vvnDGW`&+%4o9%cj$aZd-rJkJ+U4fOK0&V!J~n4!=s6_@uQAG^d@EGFcvIBgm7yQ zDt{>9IVhaV^98j}sM-}CoyMR27^UEsu%BA>=M$7yf@I&xRMmLV!Am*E`nOHTt3L5B zc?NyY8?{cSHR2rO@mH%q;UGORjS|*y8KPbqFP05etBpoI%{=IF7AQyteH4#~3kZif zai9#QpY!#6-ni}jJH2E1{xM}?2CC|WPXubbpri*+eg@v~2DwPW=s}*jEUkG=yS>~D zV+_AGN=&eOkbQrR@v82c&y_q4jgL)qdtS~^y>+kbT(PDq6-GmOn0oCKO-hq(OD9Db zf5{H`SXij=!)eagkV1Q87LSj}YYH-qSlFJo>S#lRbQ&{Mdtx-=XotV27s@I7sN=%K zXqPH(`sYTTamwvDQ@zzPA_RP9M8cec8?o^LI25}d|F4(nv;y@S4C-U4`!YU{LTS^| zg^-YYL0ZJm*DFw+9}fGQ6`u(uDNd30EPGuO8SJ|_@e(sjzzI?$0D<$i&>xwK!5et{AciFZ!95k!ypFzK-Aic(MzL>N3 z=`tYWEq-VH@m3G{2uhIqcU=ZnqEP;2=c}yTvid+eN^v|2g{*0G-*_5J@BODPUEHgps;+Tk zSedP(6AEhi(aU1_PH9K^ASZB5pfvV6-ukPIB7CbLqsa=87@8#iE<6u2Lf)t`}7Zy6=te=T_K_odwWP7-d9AXiun$uZ|%k0 z3dUPbZ$!3^r|PJl9ts&Orc#3slsDEwq$&sl7;w3LdaAr@M$n|@h@<{KH+$~p6em0+ zMIH*X%MMmjU-AHY8riK3_IbU++*__?Xn>H1i*4dtBkpJD<2LQ_?CPB-uF4m!MS+pL z8={6yi41jF_s0vXB(f4sGSB-HmD`5tP)fR+mO^ZLB~AeWxp7sm1e#+qsU%EeLvE-( zY`?h@#cr{R<nn{La`DswYW$X}m?4ZwGJXyK>nbf!r;%Ck`4UHy2zg)n=#G+U=fr zZVo21*Te5|2CGq^FK>ItFex|la?Q5aX|?(J{bR%Lj&IG|I<^OE##Y0S%>>2KW{P~T z;6*4L&vNlUZHU#T-JkZ`Y9n-v_4I=tDf_Zm_O+$-b+@sLen^@!|nFqz`;X~_w7o^VFryx zHD%IG5D)cbb2eU=qWpz&!y7DFB-JwnqV}wLk)o+LMBJIQ9J|Y`+1TD zGf&3P?y#7wpuG^zQ!t97i>5(>BR6q;dhl;$=sECb?-KLsg((lEO@5m71Ryo8A>d*QbwjJ1xrpZ*9@3+h?plYVseIo*d*ZL; zwu2W476RP zc~H5YcVwapFWtMivCHR~6+iKhe>Z^FIGxOef^nVnd)=4DST?Fw>-Q8l-Q9n@zh3jF z&RGTl;HFSxugO2q1liRM@`0qZH zF-SOL={}2H!b;g0%OTi1hfO6u%%~&p8{TK=Z9Bt{u|DtyuY7JqsBuPnCJ{qNLQc~yHAm=!2q7$@@E@NK#g;MSjI0zhJ(S#aS zoL=3tbTEBHTidfmPRktMOcr>98qZgQV;wpdJW{|)E4V$8$!1jXujxpubO(jM=m;?T ztu<6raPd!e~(P-AOfN)S8!33u14Iu)JD zCC3-jpDaD?`Rr%|VCisjyHhfDCGu&hYzue!mN~s`gCEo@G7g-~SCkhmp2eJVGZ(tD zRl|(JhVwHb(m&w}A5P1>PKUmj%W0%N5apG)NyBCzU{jTQ6_r~_IA^HAo0dW=vj7b{IS zE69dnFjO{WVz~DHg!>jwmV}Z=*w%~B6$tyLpHC~}^MO4DdrBVBXfM3`a3Y|nFvBBf}cjro!RC~@LiIjfEiza zkeC|`m@Rs1!)f2UXB4BvVMW&v6DM-yN|R%a+ifQ(A^x}9EoG4thJuWYMm_FV$btk= zekh;flJI%0M!}5{j*^U(=aK?&0AT*c_34nb%U43vq*w`0t!JM%KR1JC42)EiNz&#x zFn8%Q7jAs2Tp6Jg9IZckv94Z@3(Q)nf?~B{5txNSPMqmpL;4Rhr>r|I7FB%$Dt;0} zXrYN-@eB&?a5&)9P?=Y44@=4G97ZkF4FK^@?_%? zT3mZZh|V5(c@!>u@KoNRcH6y8I6qLf`~7q!8wN<+YV)0&_QTx26v`)|ie)^l18_K? zSm{yIkfQB%W2uPO;J88w>W@W4!75KAxPD@6l*|5UpEuZ}M%NsvvM&sVSoM3;=d(it zy~cMelM5p^V+lpr&uqQbDL;QF+oT7ep6&oJ|DW)FWskJPVnjGY#c+=W!>5yeZQsD_ z-SDuoE!14{Lt_Nit@Vy%ArS?I*UgDNHjN;j&Bg$E&kE12jzR>Mn|dIh_kidh3E9>L zr0L^Px70l66P8B&5WE_=Z#_Eg?+8 z(h+u)^v`?*vY19!C|5FB|MbD^PVViTao6Miq>p&-gb(H*b|qP9v>Wk%_R9|zyvRiiy3FCNpHFsopEpvs_oMd2?_>Zz4}+y|*?*}R?KbTndDItx1-Hf+_;wd6 zeH7l_2Vx5$GE_dolWs3_gxoh#uLDq6LNE-L8&o_O+L;BcMp{1)`^B{mRQ9az9K|$8 z8@V0?>x^Zy{_aCv~7q2&2ExQ2}yj?7n8tT~2RIE>Rl+8~P;~+5GHZ&Vz~Gb?C(z>UqD}|H>}W#4-FlM`2Evq30bQ zgc>XL;_(z!t3#gv3dX(6?fSyPe!~R0(tEx%Ms9K1P)6Zp34n5&yHew3h9bGe9o6b`%wl?$JY+%b)c7XH`jlke=D+cd!$dm`G!AZvjCEaVxf#F_Lrlx z1M8!Z8rJoyF`2VP^{QfwGa1>lv-%1}%j?nyY9H%@lk2wbB46j}=0(F<7^E84+Iq%~8kYh-i?jd1CO1AqSkp&*)g~UahV3L`;u*j&1|m`uO?iGRJDk-_IUw6NUlB0!Vm zU0)iT$Hm`h4-GK4in)uvi?4{W4S{Y%zQX^PF?B`uGfJ)NR-+;d#_$eT=5Y8;k(w>l zj}h##`SFmRmdg==Hv(gCs*b5UoQM)lLOdnKai!JZR_0vma(AcE@wHLBv%Pq=CSLd3 zj7p0yk`J5J7X*PH^M$$3w?y%rQJ)7|XR7ePKAX)@n-%JQFY~45pWN?V8DSP9MVYQd z=oH@Btmb>wT^UQhXvGU%xY}&KD+S_?;Do;qaIL%Eh#Pr(BJ@L*-bMZ`Pns zCZiWM=UWePFN_U*gmOjTS(&HHTuZ> zRv{3!y7RAxv^u`bU{K>GtJKkpYz@TrvNw3+g?Vj&nuVd)r9&m6v?(wW{cY}`K!$9$ z6`+g5`*{ZrO{`WhV%>reMq%GTRkt#@ca}8XYy&skB08h>%KE(gZrsT>q;3(e`hrroZfWC>aYW{s~oA;vhRl z7kiH|c21+@sPfQ~Be)#gwRjh_cOAEfcG6Vjsni{Qo&+pn{((@2r1XjLnBNV_|6T(* zGFyvHUEKfKyntm5c2IKe5VR&Ymo9eLXx-Bg0HKg?Xp&ENZBWIa#9=4GP%Ha)ZZa~Y zN~a~fY-U8vYSWcuMbq{p({qCz(=(uF#qKS7UOa=GF3kA^n!x81qY}{i07GH~{BKB- z;HO(@Z@*^9P;Ul0hQSbxS{x;bt*R1Z?MD*~28Y;fzoDkJ+1vX(GBhgD95)M(EXioj z71N;jYv5HxNENM)Y~6N9wI*xPB&I52i;|fu*|{-}ChK$qDiOqa!emg<*-Y zMRq7pq%NM*WfjfqvM`_5fle{+om1rr)g6m5YNNliRKYZmM`VJf?1 zm&<{9(AU^bX^lOFk8n>zGtP*SP>N{O5!t^t*%2h#dcCg&ZPdTAIo@Wt$Tz;fZwurK925-3r;Xl8D-;)rrazS~>Vh}^MLReTF zk_ny4AJ#m1wr@CoF!*5EQfMduqPfy}vBUWYu^pL0^kK#6?zCsauAV%2By{{@TjuW> zAOnHOW_9m7`Rt_KEbb-~k<4JwhB#KChNxL68PN@o?+)7T9w~t7cKwW~N)$@MX34uJ z#KqYS7?H4)H%|R~8rO0Bfj5+%N_1a3N;hs=D$1ch%(WgIpKnQII^Xc03!~fZ9JHu( z+2k_#eOC_nB5UD|OBLac=W_J{tykI$!OF7QKeN#OMt4^x;M9kf0@Oqc9~(b*%sZ7= zPe9j|j2r(%Vbt>aOeyg~tjCCyZFhh8i7Qf%c{YaKP;!kRS$<+u!j9<*d8>DkJu)8A z#$UfC^fQGF5h2^Uqgqq(@CNjW@ymc_3KR43Jr&4B8%dg7OJxHO&$ZZ;Do-cuwXgQL zT<&Z~;y||B;UOd(v2cGEE@Vl<~qL?K~{_|I2hBFCJs z1yt(x2izWB@ptUsY@K0o%g>U3M*!VU=Zwq(xlx6Y*^d`LFCj*US=JKa4UH>y*fcqW z^76c5XGZ-V?N5{xeWib|@J~OSc7KUT+f8gq+7?$+EM+P|T$x$XUW+|Ri~A|1TU3U4 z8dbcQ<)Yj^=}*w;<-d+7-mL(#-6Ih5uH5IJ!*^1VQvC`HsWNCAsx%_C)Ef+$oOg49 zLr+lPYT7U2rYM`hjMW}brW6+J`L{p1As zw^UFt{9tgRyJ;O#IW^>C80KKPT50$_=c;_%(~Eh63vwAx?9+>Zq#JIMZWfA3b<8ZD zb|)Y;cxj<9=I0n{C8(#TLRRTf90mrzsV%N%4<~|)P2SM4eokVI6$5;T0K@g&)-f`W z!Qh$;WZUvNYY=7GTmdwEWZyb14?>Hcmn)fzmad<9X5=r_yn)SoeiNd26@NRt0qZABCR0G(z)AxtCNr+$TWtc15p&< zQBHCvbdNFfN`tK)Q+cS3kbrI^5wUd-TPp}f<2&wKPv-HRb9HjU|Ml(Id6dD{*Qq>G zQLy3+%-ClJUTHe}yX(ld^PhQ0!A(4Yz7behTv@2>3p^!FYhtQ$0LutP$6+D?N;csE z+)v?5vafpAfnaAO|L0{Fpa7eg2>Q404RC?!i2((o$d(4(zu7s)*9l;ja)iU3)Z0JW zpe~lfl^tSV9q(`fQdA~am{_WKcix|E&0eh3oFZV#!xm2L8snmj&qB)DKtRemh4`Y*>^wgKjF}a_I|em zQ7n?o(Wrkrg9!`@>Rsg`sx=-CnKfZJej1D>iTRr2gg3juqXxtpSL8bV789*doy6d= z5OH-Amq`^VBrF^#X_y&lN55f5?#EWv`IEDO+Pwc|R&FEcZuoyHJVx0n3opy0qk*y6 z>9W~eDPl+kh_Y0+bM-3?cOP8Wf-g5`3tA=XAu5<^?U(3#57r)@JA-yJM)wBi8*5Jz^GLLE50wtDqp z&o?*T^k3``bpjLDP+>LH=s1HE}PbLIWkVrS;9 z=dgz7Kyw}svwuks3$|t|R^`iOEI}+*ZvK9&`bjACG8cPuO>7MrBB+z2ZD+aMi1A3k zJvTA}uxL8X<$?<_$OXhnuuW@y$yaMv3@^D41S3yz#C~p<3EI|Zr@)swmrpy#^KZaZ zaTL9g=`M3ira>=G8&ggF)ZZK~kd>f5IHO!&^`EfEv-%Io?@r3El_Yk@H*iMdak^Aw9&^blY^>!O1(%_UU!4|+Bjllj zDN%0rwX4TGUc$Yn+4Am^y?+NnIjwv)SiwFE;&6E5A4uc! z&7zgO+VT$JdPj+>;~vds%tFxr#^>@vxK1B>_So-8S4HLbrish`Mw39T0Hk+kVbe&M z$?gVB{CHOK?v#G$)1*zsj(b9GUCG{PHVp)jd}iMH0D_M(k6U4eH10f{To$*0{1r(7 z;U}y{fO4bb_o0+**3M+~$BT~FCxbcMx+A6^D~{MvPF<5zl48Y*&8IBKKhNJ;*M#|!2J;H62gbl!1POeE1Ff47h(Mt^G-uaqV1u<6u2&9=+(-Q#Yj1QqKSjJg};Bqc4f@GgJR`?8!*jBmE54?I?k&%YGt9C)+(cz_j5jmw7>P=S;X3-ZK1hhLav zP74uLCDQZ8`tjA2LEku5^NsM^eSo9crE5>S+mrdwJ7sCI=pN#@ZKui`y=>9)&Q z`Jjr1l~_5_ObHSJVKGTYLH1+ij-m7Ydr;#C*~9fTAENZ}9=pp6zjXF(T_b!bi>>~p z-2M5{-tlZjknZVh$sxP(IcztVjY=(T(jn>A0w6Iqe}5dR;&!WczMPY$0Vdw*1f{qO zXcV2VWhF~$zZpFV_dXvx-Xpq?N?*fbd~;iMI#pr# zErBwLz~DEp$48h=cT@Al;QLdt;Af>aXUR&IY#x)}fFS`ng^!9#S3h41B z4`>Q!t!XrX5a55P%c+TDYY_7MGO@52&P0AB{?rPBIS|k?=3$D|K^nUA;V|FvEZ4j# zz`teMz+pcBo}^Zu_&S&FM&7Ue;qn5T1zqDVN-;T?8cqluMx|V`j-H#ZMSWA~!5!z@ z2B-pdta)(WQ**occjaD=j`1{hua6WU3#n0>#+JiF)enx!ROthJeffuaeI`E) zUtr4m#hj)swvuBQa!1f4w*lgxd*#cjhThgDkJue|d&=CtAN_Xt$9W7u>#-_1nscB9 z$oz5|B$XtgwYEbH6^}S`Q^$MJ32fFQf(7b(GZyM&Y`Gj}tGPtvvahmLDzQOTN(0>Q zH;F9ZBm?6?-1>b93{3k&SrSzevBVzE&+6$H-%6=yHJWU17j#&7?&sWZ+_FR6&m-RK zlY){G&Aq~$E3ggTLMltgF=`CP(XHr{A$+qgZEn4p6>pp^5!KrrEAW*7@|!ZW6(3I! zMHTocdpwI$ZDGmh6xNf)Ej=!~qoUmTkY?N;;nPW8lqvLH|OldWSPk#6Iu&7FFe}A;> z2@jlt(D5e+j7rnnD$f!QV9DpSR3hHu|z~H6P5Nazpl>kh?c-A zp7O{$LH$zX}7Wrt37^wAu<{KKM97@B^yYxTWi*3rl z6z`q|l!({$_zr#mLckzN`uMI$df5Mfg>X8Q7y(a$fgm;MJ0Y+bB!GJr{@h9KFi)bz zwMZ5`6MvQll(t))e|W^<$wQ>Nm1q#9n5nDiav@S&~97$O7IkR$G(T>F_OYZzEA zS@-$3Jdr-IzknFC&AH0kwVBlmnBD1h^@iZnbP}IA0ygyJ$&R56&YP40#T~QohmuDfoTu z#ka`7)_EhufGLph-8cO*uGx+|J6`2;jY6MR`MI&<)_U1psS&qpXGfFDfqj6MmT!XhZ&Gy~Vq+03P)Jm-$07 z{y$}#9VtaoVPUX6Lz<=d0afn}5?~j?tEX#2TJ2`UXndYp>Zra^FNr|_lHT$J1c8i6 z=8dGI{rw<7^UL>#v*k%Is&GZ(YkNIFgPJb|ig?ObB6vJbFo13@oyyc-o*$w}yok*3 zNQMV!d+@!5B6ZF6zYRAIy-w_A=z8;i-0(mE^mFzv)fi6Kn<>3r^&%Y2mtoNBv<3n? zgnlXHiC#B6TP{ioZ*6pVh$m8`AyddkcHM$gu!&@S|ZY~`rSZ(8sKtc zLK1}w?fT4TDzw;O33xMn0oTSL6tI#y2u^mmPJ=mG8nan08nP0Xh=NAFDU$hOrB&EY zEEso|uew~HB&B3CRR|Nks-UKfw6H5F@FB|94$$GZ$Vaj;kT`&fE`FY1K}{YTb484g zhzOo;E-16YPu-KzG24OB+c4QYw;7;dCms1KA+* za~0fLYjGG#{bs^#2A3`Xav=jq3(W7|tj5?<7@Y-}OI*|ci51;CP>x6UjGPbF+m$;C z^fX}Y6@82&{@r<7h-+;Qo5Vkh{^pS^VgAlREbee1j6Qc&9|g^SLr=&fAkOr?(*FSc zkB(FKCsbB<^_A$9GJn7y7}))NZTT}tP`(3@d zclWcNC5=u1s6a+XT}=fKp?@@@4~H+}0r{#@W|VK{)VouI2`>#ZARI+h&v{d*|SLRhNy z|39!)26g|JCjU=_Si9Fokb2MvJ&wzl)!k8^kQH zs7>Z8S-ppkaUB`E164k3#*HDAjI$1#>*CI*kyj`B{5NXc`VyUoe`K^02PVv?6aE38 zwwB)YQNseA&6ZU*D5)l2sK{CXuXn*Eiav=74qsSHlGAD*J(a~uL-UDHLMOa|Jt(>V zQbWFEEZ&Z-%JB74*iJb=;i}Lndo{xO<=2X6B;-LVO6efAeh+)C#pGNiDjrl9U66%N z$pe28t!!!OpjI7>;6DUkEA{)jz(!{-{}#Ti5#hr$yE#Xpsr%xUfi1_fV58r6wzR9g zT75AP$CT34Gi1^3f1wH86Hs=zap$pnBvm!}dGsz!(t3N$TY(Kp>#jGhx!kP4-A@EX zB!=>b`w}bC(m45?(Y7jYeX8geg@A1qR};5f$j#z*1&!W{t}&Yq-K9Z&XS}9DJ1J>v zbJk0`FJWiJ7C%THg4%FyJx38mH5BF6s30rfMp z1T8?91^8HhV$EciO zn}1#Zxpwf%Z-XCWdhG`~8P1~N7wZC@R!{iROwhLfrF7%`VUz^SOHN|mcIV* zVOHq{?(O3I+v=avO6P|pa(p^I`fQ|SZIENIzQ_$+tK-;^$l?RxSPjY-&7=0ZTPo z2cI0Cb~y?)tLh|mAMt{J`!g(6>aRdy5I~=PPnt|VieiWU3T?xYRHOfAN*p&thI+HoVNct&Y5kktV7iP%m^%01TyWt* z0e>?xNrk}@>^?{9r%R+UNSQ>;WoUk{Y$siOpD5FRy-bx60Ezp=ti3~ZRHfCcSNdRw zr?(=K^UH#7){`HJKS?<0Fe#5WU)y}G)r4-T-6O(K%wJhUFRUj;RE5QQJ=eVuRsY%Q z)Z*S@$^ALElpapcn3$gayMy`4l&IKYEy+!jI5dNi3=&u2brz1)56mjb>EgF9`EE?; zY01z%VI~c**VM>^HxX$M@t#<;(Q~9q z;(f0i!S=})g^4PuZ}-GI+n0VO^YvCpzoZ^PR=(8nj5^2F@nTO=-pyR|SJ>m>AbwCm zCzbqYQ4H4D`UM>kF`Tu+O(#ZUu-cDCN7dp5JN_Vs45@-IUpTq8G^8Ext>8zbm5+h8 zpQS7Lm1c`9L6>Ilx`&=s{=g~veFCtN4%RhMZh0B5k z2{)K+{%-!%d3r&-uyAnsdPlj4wS>Q)HT#cpO7CFB0*GzhIMMQktNJ!J7lxhF{^VUL z&7di;TxZXFw%o~7@S1?11zmkP=pD&!6?q#%7%xL}xK4&Qp-m(=^oQJn1b{;W&L1;N zv}@$aF|+L~>gG>jlZ zxm*seVnNKP1|2Dx3yM;TO4{p7uy{O1E0W@_)LP*{zlH3Tb0!e+?b@mu!3>K}W1Fb= zOj=(o|H#u4;BPxQuN%M~@+5Y<{bGXd%QM!hQm12-OjtucuBp-(O>7cD9f7KY%QsEy zC;?y#)OQzD_ltn>PHp(7YdzJhvw?7D9;3!&e`Gep{FSEhv}QvGRXJX#MmE&#x%N8j zDIj=|2k}Yun@k+mFnu;ZbGESZZk@e3zE53kJ~Qx*UPi`4v16CS!MdLMzP(Q3dX)oX za{mA+uy=g*VfHsi|DSRZEPi4WPdco2sKCzLe{loM8341aGDGx(y-gs|KlJ_m`=^h~ zd-z}eB>MelBVymacZIt&Z~tr{ChxSPjOaDgZ$0cXh?f#7!k)IhfIHywPp7B6z(Ei1 zf?JRAwg9J~@e()ZJy(3Pt$)QZ%uVDJPY+}}y*FGxeK$Ji`K%7^rO>AZ>b#MI$SiK@ zOIA=kDA%6VqW4p`ITE~J{}ptd7f>6zCl*rpTQjtIN!sp1LVunnB1xhDO2VE3Y>8}7Qs{3pZ<&N&Nu}y~ zMUke%-;M{3M=vyOAEJvJOkU@XbWbcRT;nE*<=UBNE0&9El7JjZRRpW%ciZcquMPW9 z08~(rS!j11@vNYJJ@Q(lS-u^WDzL$C0Ys^)95}5a?DuGCz?zhNMl4IyZrI|wKqNHe3%tMLnH zvggz`ZiE5$C=MiveBO>&S`N3b!|}QvMug_Ucar5ih}1rzZ&-m+0U;`aa*2&e<41m)I0uHM+O#z zw+|){=YOSz&a2_pYsZEcuS}W#1>cP%0OpfCP6Spi5!AD6-i1SQPY|4wNr30-Dny!M ze$ChD`h3ZhNtIqFTb4~)9(1OB@w6_pr6@vmQUofw0kfSPEnpJT1>TqS6B)3q>yB<= z?NeV?jNlSnau5_v-vrNuk4kyDjwaBvuUicb7pkg^kE{z~Z?dZ!Sv_VNH2PyL04kSW8XYPPaISDq{9*R@&WrmNmdEQP#2b;! zK@qFfYpcAuuf7m%wk_F1n>#z7@$3b6#4xS83@LhY*-ds5H}zZv6i$1RwuCGkW?j}2 zCsi=b2DM7c1kI`mee~UC$CxTsyk67#R9Ze)jff=OeldzDS}PqS)?Fr67PBxBU(_*> z2YV07)5!pqIT&)k-xe1vkLH|P_qYElrgb=i*-?0Bdth-IAychOw)4_6PY3s_`;(kNm~m6W^RF-EX$Q(Hq+lh0H7Z0Z2Db zp6%4)KksoOHL7(rL7?1l21e0h)SSgP9%0AgLRHa6g=T=~)<@sm>iXG+hp_;SsfCOG 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 literal 0 HcmV?d00001 diff --git a/release-contribution-guide-schema-webpack.png b/release-contribution-guide-schema-webpack.png new file mode 100644 index 0000000000000000000000000000000000000000..fa8066082cfb97f51e6e8bc5392bfc799b5aee62 GIT binary patch literal 97021 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 literal 0 HcmV?d00001 From 1d70d516b3bebf019a49fe22786024f0c98710ed Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Tue, 23 Apr 2019 12:11:45 +0300 Subject: [PATCH 21/31] docs: update change log --- CONTRIBUTING.md | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41aa62d06..b42d49f3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,55 +112,51 @@ npm --no-git-tag-version version [version] --allow-same-version -m "release: cut ``` NOTE: Check the changelog!!! -7. Create release-branch with change log +4. Create release-branch with change log ``` git checkout -b release-[version] ``` - -7. Add changes +5. Add changes ``` git add changed-files git commit -m "release: cut the %s release" git push ``` -8. Create git tag version with format 0.22.3 -``` -git tag version -git push --tags -``` -9. Create a pull request from git in web or try to use script below. Be careful to base your branch on the correct "release" branch -``` -curl -d '{"title": "release: cut the [version] release","body": "docs: update changelog","head": "[BRANCH]","base": "release"}' -X POST https://api.github.com/repos/NativeScript/nativescript-dev-webpack/pulls -H "Authorization: token ${GIT_TOKEN}" -``` -10. Merge PR into release branch. +NOTE: Make sure the PR is based on release branch -11. If all checks has passed publish package. Usually the night builds will be triggered and the package will be ready to be released on the next day. -12. Don't forget to tag the release branch +6. Merge PR into release branch. + +7. The merge will produce package with rc tag in npm. If all checks have passed, publish official package. Usually the night builds will be triggered and the package will be ready to be released on the next day. + +8. Don't forget to tag the release branch ``` git tag [version] -git push tags +git push --tags ``` -Tips to remove tags: +Only if needed to Tips to remove tags: ``` git push --delete origin [version] git tag -d [version] ``` +## Checkout master branch and bump version usually should be minor or major. + ## Merge changes from release into master +## NOTE: Don't use git IDE/WEB + ![](./merge-guidance-schema.png) ### Here are steps described in the diagram above. 1. Make sure you are in release branch: ``` -git checkout release -git pull +git checkout release && git pull ``` 2. Create PR to merge changes back in master and preserve history: ``` -git checkout -b merge-release-in-master-branch -git push --set-upstream origin merge-release-in-master-branch +git checkout -b merge-release-in-master-[branch]/[sha] +git push --set-upstream origin merge-release-in-master-branch-[branch]/[sha] git merge origin/master ``` 3. Resolve conflicts. Choose to keep the version of master branch. If it is needed to revert versions of modules, see at the bottom. @@ -175,15 +171,15 @@ git commit git push ``` -6. Create pull request. Replace replace env merge-release-in-master-branch with its value +6. Create pull request which should be based on master. Replace replace env merge-release-in-master-branch with its value ``` curl -d '{"title": "chore: merge release in master","body": "chore: merge release in master","head": "merge-release-in-master","base": "master"}' -X POST https://api.github.com/repos/NativeScript/NativeScript/pulls -H "Authorization: token ${GIT_TOKEN}" ``` -**If needed, revert version of modules and platform declarations to take the one from master:** +**If needed, to revert file and take it from master:** ``` -git checkout origin/master nativescript-angular/package.json +git checkout origin/master nativescript-angular/[some-file] git commit --amend git push --force-with-lease ``` -This will require to repeat steps from 1 to 4, since we need to keep the branches with the same history +This could require to repeat steps from 1 to 4, since we need to keep the branches with the same history From 1fc98215262b9ee7ccc84d3f9eb5e3c843afce98 Mon Sep 17 00:00:00 2001 From: Zdravko Branzov Date: Tue, 23 Apr 2019 16:33:30 +0300 Subject: [PATCH 22/31] chore: add new test for #1771 --- .gitignore | 1 + .../app/navigation/basic.navigation.component.ts | 2 +- .../e2e/modal.shared.e2e-spec.ts | 7 ++++++- e2e/modal-navigation-ng/e2e/screens/screen.ts | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 610c9ed25..9399b9cd3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ ng-sample/app/**/*.js # Webpack configuration files webpack.config.js tsconfig.esm.json +tsconfig.tns.json # IDEs and editors /.idea diff --git a/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts b/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts index ad0cd0031..49f7a2567 100644 --- a/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts +++ b/e2e/modal-navigation-ng/app/navigation/basic.navigation.component.ts @@ -16,8 +16,8 @@ import { ModalViewComponent } from "~/modal-shared/modal-view.component"; + - ` }) diff --git a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts index fd76f81eb..1ad368ff2 100644 --- a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts @@ -15,7 +15,7 @@ describe("modal-shared:", () => { }); roots.forEach(root => { - describe("Shared modal from second and back", () => { + describe("Shared modal from home component and back", () => { before(async () => { await screen[root](); @@ -46,6 +46,11 @@ describe("modal-shared:", () => { await screen.closeModal(); }); + it("should open/close shared modal with presentation style from home component", async () => { + await screen.loadSharedModalWithPresentationStyle(true); + await screen.closeModal(); + }); + it("should find home component again", async () => { await screen.loadedHome(); }); diff --git a/e2e/modal-navigation-ng/e2e/screens/screen.ts b/e2e/modal-navigation-ng/e2e/screens/screen.ts index 107d34b41..b9c22c8eb 100644 --- a/e2e/modal-navigation-ng/e2e/screens/screen.ts +++ b/e2e/modal-navigation-ng/e2e/screens/screen.ts @@ -165,6 +165,11 @@ export class Screen { await btnTap.click(); } + private showSharedModalPresentationStyle = async () => { + const btnTap = await this._driver.waitForElement("Show shared 'popover' modal"); + await btnTap.click(); + } + loadedModalPage = async () => { const btnShowNestedModalPage = await this._driver.findElementByAutomationText(showNestedModalPage); assert.isTrue(await btnShowNestedModalPage.isDisplayed(), `${showNestedModalPage} is not displayed`); @@ -309,4 +314,13 @@ export class Screen { const lbl = await this._driver.waitForElement(sharedModalView, 5000); assert.isTrue(await lbl.isDisplayed()); } + + loadSharedModalWithPresentationStyle = async (loadShowModalPageWithFrame: boolean) => { + if (loadShowModalPageWithFrame) { + await this.showSharedModalPresentationStyle(); + } + + const lbl = await this._driver.waitForElement(sharedModalView, 5000); + assert.isTrue(await lbl.isDisplayed()); + } } \ No newline at end of file From 4ce38fec39dda55ff6bd427d38523dde9f9f82cf Mon Sep 17 00:00:00 2001 From: Zdravko Branzov Date: Wed, 24 Apr 2019 10:09:15 +0300 Subject: [PATCH 23/31] chore: stabilise tests --- e2e/config/mocha.opts | 2 +- e2e/modal-navigation-ng/e2e/screens/screen.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/e2e/config/mocha.opts b/e2e/config/mocha.opts index ea635b0dc..65bcb91f7 100644 --- a/e2e/config/mocha.opts +++ b/e2e/config/mocha.opts @@ -1,4 +1,4 @@ ---timeout 140000 +--timeout 60000 --recursive e2e --reporter mocha-multi --reporter-options spec=-,mocha-junit-reporter=test-results.xml \ No newline at end of file diff --git a/e2e/modal-navigation-ng/e2e/screens/screen.ts b/e2e/modal-navigation-ng/e2e/screens/screen.ts index b9c22c8eb..690edd0ab 100644 --- a/e2e/modal-navigation-ng/e2e/screens/screen.ts +++ b/e2e/modal-navigation-ng/e2e/screens/screen.ts @@ -1,4 +1,4 @@ -import { AppiumDriver } from "nativescript-dev-appium"; +import { AppiumDriver, SearchOptions } from "nativescript-dev-appium"; import { assert } from "chai"; const home = "Home Component" @@ -166,7 +166,7 @@ export class Screen { } private showSharedModalPresentationStyle = async () => { - const btnTap = await this._driver.waitForElement("Show shared 'popover' modal"); + const btnTap = await this._driver.findElementByText("popover", SearchOptions.contains); await btnTap.click(); } @@ -217,6 +217,7 @@ export class Screen { } loadedModalNoFrame = async () => { + await this._driver.wait(2000); const btnShowDialogConfirm = await this._driver.waitForElement(showDialog); const btnCloseModal = await this._driver.waitForElement(closeModal); assert.isTrue(await btnShowDialogConfirm.isDisplayed()); From ceb1a172c7d17318452456927e669bac40b5fb5d Mon Sep 17 00:00:00 2001 From: Zdravko Branzov Date: Wed, 24 Apr 2019 14:23:15 +0300 Subject: [PATCH 24/31] release: cut the 7.2.4 release --- CHANGELOG.md | 18 ++++++++++++++++++ nativescript-angular/package.json | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a955ef1f..c0b4062fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## [7.2.4](https://github.com/NativeScript/nativescript-angular/compare/7.2.3...7.2.4) (2019-04-24) + + +### Bug Fixes + +* **router:** routing services should be provided in forRoot only ([#1729](https://github.com/NativeScript/nativescript-angular/issues/1729)) ([0f6a975](https://github.com/NativeScript/nativescript-angular/commit/0f6a975)) +* on destroy remove the lastBootstrappedModule ([5e13263](https://github.com/NativeScript/nativescript-angular/commit/5e13263)) +* page might be null'ed before clearHistory's navigatedToEvent ([ea66985](https://github.com/NativeScript/nativescript-angular/commit/ea66985)) +* remove rootContent on exit ([14e787f](https://github.com/NativeScript/nativescript-angular/commit/14e787f)) +* the exit event is triggered on restart ([3dffbd5](https://github.com/NativeScript/nativescript-angular/commit/3dffbd5)) + + +### Features + +* **modal:** add ‘ios presentationStyle’ option to ModalDialogParams ([9cfa127](https://github.com/NativeScript/nativescript-angular/commit/9cfa127)) + + + ## [7.2.3](https://github.com/NativeScript/nativescript-angular/compare/7.2.2...7.2.3) (2019-03-14) diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index 062189aaa..b185a77f9 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -1,6 +1,6 @@ { "name": "nativescript-angular", - "version": "7.3.0", + "version": "7.2.4", "description": "An Angular renderer that lets you build mobile apps with NativeScript.", "homepage": "https://www.nativescript.org/", "bugs": "https://github.com/NativeScript/nativescript-angular/issues", From 2f59b0f4852efe2f90a2fe9d0f81b399a563c4de Mon Sep 17 00:00:00 2001 From: Zdravko Branzov Date: Wed, 24 Apr 2019 17:02:36 +0300 Subject: [PATCH 25/31] chore: merge changelog info --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b4062fd..bba452911 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,7 @@ ### Bug Fixes * **router:** routing services should be provided in forRoot only ([#1729](https://github.com/NativeScript/nativescript-angular/issues/1729)) ([0f6a975](https://github.com/NativeScript/nativescript-angular/commit/0f6a975)) -* on destroy remove the lastBootstrappedModule ([5e13263](https://github.com/NativeScript/nativescript-angular/commit/5e13263)) -* page might be null'ed before clearHistory's navigatedToEvent ([ea66985](https://github.com/NativeScript/nativescript-angular/commit/ea66985)) -* remove rootContent on exit ([14e787f](https://github.com/NativeScript/nativescript-angular/commit/14e787f)) -* the exit event is triggered on restart ([3dffbd5](https://github.com/NativeScript/nativescript-angular/commit/3dffbd5)) +* ngOnDestroy not called on Android back button ([#923](https://github.com/NativeScript/nativescript-angular/issues/923)) ### Features From 003254fcac33533fa2d3b9591afb194aaa46c016 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Mon, 6 May 2019 12:23:32 +0300 Subject: [PATCH 26/31] tests: add mochawesome reporter --- e2e/config/mocha.opts | 5 +- e2e/modal-navigation-ng/.gitignore | 1 + .../e2e/modal-frame.e2e-spec.ts | 33 ++-- .../e2e/modal-layout.e2e-spec.ts | 25 +-- .../e2e/modal-on-init.e2e-spec.ts | 83 +++++----- .../e2e/modal.shared.e2e-spec.ts | 102 ++++++------ e2e/modal-navigation-ng/e2e/setup.ts | 17 +- e2e/modal-navigation-ng/package.json | 12 +- e2e/nested-router-tab-view/.gitignore | 3 +- .../e2e/custom-tabs.e2e-spec.ts | 15 +- .../e2e/home-tabs.e2e-spec.ts | 32 ++-- e2e/nested-router-tab-view/e2e/setup.ts | 15 +- .../e2e/split-view.e2e-spec.ts | 37 +++-- .../e2e/tab-view.e2e-spec.ts | 5 +- e2e/nested-router-tab-view/package.json | 14 +- e2e/renderer/.gitignore | 1 + e2e/renderer/e2e/action-bar.e2e-spec.ts | 33 ++-- e2e/renderer/e2e/ngfor.e2e-spec.ts | 20 ++- e2e/renderer/e2e/ngforof.e2e-spec.ts | 20 ++- e2e/renderer/e2e/ngif.e2e-spec.ts | 99 +++++------ .../e2e/page-router-outlet.e2e-spec.ts | 81 ++++----- e2e/renderer/e2e/setup.ts | 20 ++- e2e/renderer/e2e/tab-view.e2e-spec.ts | 18 +- e2e/renderer/package.json | 12 +- e2e/router-tab-view/.gitignore | 3 +- e2e/router-tab-view/e2e/setup.ts | 15 +- .../e2e/tab-view-navigation.e2e-spec.ts | 30 ++-- e2e/router-tab-view/package.json | 15 +- e2e/router/.gitignore | 3 +- e2e/router/e2e/router.e2e-spec.ts | 156 ++++++++++-------- e2e/router/e2e/setup.ts | 24 +-- e2e/router/package.json | 10 +- e2e/single-page/.gitignore | 3 +- e2e/single-page/e2e/setup.ts | 24 +-- e2e/single-page/e2e/tests.e2e-spec.ts | 22 ++- e2e/single-page/package.json | 16 +- 36 files changed, 560 insertions(+), 464 deletions(-) diff --git a/e2e/config/mocha.opts b/e2e/config/mocha.opts index 65bcb91f7..719c7e05a 100644 --- a/e2e/config/mocha.opts +++ b/e2e/config/mocha.opts @@ -1,4 +1,5 @@ --timeout 60000 --recursive e2e ---reporter mocha-multi ---reporter-options spec=-,mocha-junit-reporter=test-results.xml \ No newline at end of file +--reporter mochawesome +--reporter-options quiet=true,html=true,inline=true,autoOpen=true +--exit \ No newline at end of file diff --git a/e2e/modal-navigation-ng/.gitignore b/e2e/modal-navigation-ng/.gitignore index 06947f093..23d2d7b1d 100644 --- a/e2e/modal-navigation-ng/.gitignore +++ b/e2e/modal-navigation-ng/.gitignore @@ -6,3 +6,4 @@ app/**/*.js e2e/**/*.js e2e/reports test-results.xml +mochawesome-report diff --git a/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts index d5de6c2bb..ff58c3843 100644 --- a/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts @@ -1,4 +1,4 @@ -import { AppiumDriver, createDriver } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium"; import { Screen } from "./screens/screen" import { roots, @@ -10,20 +10,23 @@ import { testDialogBackground } from "./screens/shared-screen" -describe("modal-frame:", () => { +describe("modal-frame:", async function () { let driver: AppiumDriver; let screen: Screen; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); - roots.forEach(root => { - describe(`${root} modal frame background scenarios:`, () => { + for (let index = 0; index < roots.length; index++) { + const root = roots[index]; + describe(`${root} modal frame background scenarios:`, async function () { - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; await screen[root](); }); @@ -38,45 +41,45 @@ describe("modal-frame:", () => { } }); - after(async () => { + after(async function () { await screen.closeModal(); await screen.loadedHome(); }); - it("should show dialog confirm, run in background", async () => { + it("should show dialog confirm, run in background", async function () { await screen.loadModalFrame(true); await testDialogBackground(driver, screen); }); - it("should run modal page with frame in background", async () => { + it("should run modal page with frame in background", async function () { await screen.loadModalFrame(false); await modalFrameBackground(driver, screen); }); - it("should navigate to second page, run in background, go back", async () => { + it("should navigate to second page, run in background, go back", async function () { await screen.loadModalFrame(false); await testSecondPageBackground(driver, screen); }); - it("should show nested modal page with frame, run in background, close", async () => { + it("should show nested modal page with frame, run in background, close", async function () { await screen.loadModalFrame(false); await testNestedModalFrameBackground(driver, screen); }); - it("should show nested modal page, run in background, close", async () => { + it("should show nested modal page, run in background, close", async function () { await screen.loadModalFrame(false); await testNestedModalPageBackground(driver, screen); }); - it("should navigate to second page, close", async () => { + it("should navigate to second page, close", async function () { await screen.loadModalFrame(false); await testSecondPageClose(driver, screen); }); - it("should navigate to second page, run in background, go back", async () => { + it("should navigate to second page, run in background, go back", async function () { await screen.loadModalFrame(true); await testSecondPageBackground(driver, screen); }); }); - }); + }; }); diff --git a/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts index b518f1a95..1cdcdf333 100644 --- a/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts @@ -1,4 +1,4 @@ -import { AppiumDriver, createDriver } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium"; import { Screen } from "./screens/screen" import { roots, @@ -6,19 +6,20 @@ import { testDialogBackground, } from "./screens/shared-screen" -describe("modal-layout:", () => { - +describe("modal-layout:", async function () { let driver: AppiumDriver; let screen: Screen; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); - - roots.forEach(root => { - describe(`${root} modal no frame background scenarios:`, () => { - before(async () => { + for (let index = 0; index < roots.length; index++) { + const root = roots[index]; + describe(`${root} modal no frame background scenarios:`, async function () { + before(async function () { + nsCapabilities.testReporter.context = this; await screen[root](); }); @@ -30,21 +31,21 @@ describe("modal-layout:", () => { } }); - after(async () => { + after(async function () { await screen.closeModal(); await screen.loadedHome(); }); - it("should show nested modal page, run in background, close", async () => { + it("should show nested modal page, run in background, close", async function () { await screen.loadModalNoFrame(true); await testNestedModalPageBackground(driver, screen, false); }); - it("should show dialog confirm inside modal view with no frame, run in background", async () => { + it("should show dialog confirm inside modal view with no frame, run in background", async function () { await screen.loadModalNoFrame(false); await testDialogBackground(driver, screen, false); }); }); - }); + }; }); diff --git a/e2e/modal-navigation-ng/e2e/modal-on-init.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-on-init.e2e-spec.ts index 394fa5d49..34f682b77 100644 --- a/e2e/modal-navigation-ng/e2e/modal-on-init.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal-on-init.e2e-spec.ts @@ -1,4 +1,4 @@ -import { AppiumDriver, createDriver } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium"; import { Screen, sharedModalView, homeComponent } from "./screens/screen"; import { assertComponent, @@ -12,29 +12,33 @@ const roots = [ "setNamedFrameRootViewModal", ]; -describe("modal-on-init:", () => { +describe("modal-on-init:", async function () { let driver: AppiumDriver; let screen: Screen; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; + driver = await createDriver(); screen = new Screen(driver); }); - after("modal-on-init after all hook", async () => { + after("modal-on-init after all hook", async function () { await driver.logTestArtifacts("modal-on-init"); }); - roots.forEach(root => { - describe("Shared Modal on Init", () => { - before(async () => { + for (let index = 0; index < roots.length; index++) { + const root = roots[index]; + describe("Shared Modal on Init", async function () { + before(async function () { + nsCapabilities.testReporter.context = this; await screen[root](); console.log(`Root: ${root}`); }); - beforeEach(async function() {}); + beforeEach(async function () { }); - afterEach(async function() { + afterEach(async function () { if (this.currentTest.state === "failed") { await driver.logTestArtifacts(this.currentTest.title); await driver.resetApp(); @@ -42,72 +46,73 @@ describe("modal-on-init:", () => { } }); - after("root after all hook", async function() { + after("root after all hook", async function () { await driver.logTestArtifacts(`${root}_root_after_all_hook`); }); - it("should shared modal view", async () => { + it("should shared modal view", async function () { await assertComponent(driver, sharedModalView); }); - it("run in background", async () => { + it("run in background", async function () { await driver.backgroundApp(1); await assertComponent(driver, sharedModalView); }); - it("should close shared modal ", async () => { + it("should close shared modal ", async function () { await screen.closeModal(); await screen.loadedHome(); }); - it("should open/close shared modal", async () => { + it("should open/close shared modal", async function () { await screen.loadSharedModal(true); await screen.closeModal(); await screen.loadedHome(); }); - it("should open/close shared modal again", async () => { + it("should open/close shared modal again", async function () { await screen.loadSharedModal(true); if (driver.isAndroid) { - await driver.navBack(); - }else{ - await screen.closeModal(); + await driver.navBack(); + } else { + await screen.closeModal(); } await screen.loadedHome(); }); - it("should open/close modal with frame", async () => { + it("should open/close modal with frame", async function () { await screen.loadModalFrame(true); await screen.closeModal(); }); - it("should open/close shared modal again", async () => { + it("should open/close shared modal again", async function () { await screen.loadSharedModal(true); await screen.closeModal(); }); - it("run in background again", async () => { + it("run in background again", async function () { await driver.backgroundApp(1); await screen.loadedHome(); }); - it("should open/close shared modal second", async () => { - await screen.loadModalFrame(true); - await screen.closeModal(); + it("should open/close shared modal second", async function () { + await screen.loadModalFrame(true); + await screen.closeModal(); }); }); - }); + }; - describe("Shared Modal on Init", () => { + describe("Shared Modal on Init", async function () { const root = "setLayoutRootViewModal"; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; await screen[root](); console.log(`Root: ${root}`); }); - beforeEach(async function() {}); + beforeEach(async function () { }); - afterEach(async function() { + afterEach(async function () { if (this.currentTest.state === "failed") { await driver.logTestArtifacts(this.currentTest.title); await driver.resetApp(); @@ -115,40 +120,40 @@ describe("modal-on-init:", () => { } }); - after("root after all hook", async function() { + after("root after all hook", async function () { await driver.logTestArtifacts(`${root}_root_after_all_hook`); }); - it("should shared modal view", async () => { + it("should shared modal view", async function () { await assertComponent(driver, sharedModalView); }); - it("run in background", async () => { + it("run in background", async function () { await driver.backgroundApp(1); await assertComponent(driver, sharedModalView); }); - it("should close shared modal ", async () => { + it("should close shared modal ", async function () { await screen.closeModal(); await screen.loadedHome(); }); - it("should open/close shared modal", async () => { + it("should open/close shared modal", async function () { await screen.loadModalFrame(true); await screen.closeModal(); }); - it("run in background again", async () => { + it("run in background again", async function () { await driver.backgroundApp(1); await screen.loadedHome(); }); - it("should open/close shared modal second", async () => { - await screen.loadModalFrame(true); - await screen.closeModal(); + it("should open/close shared modal second", async function () { + await screen.loadModalFrame(true); + await screen.closeModal(); }); - it("should open/close shared modal", async () => { + it("should open/close shared modal", async function () { await screen.loadSharedModal(true); await screen.closeModal(); }); diff --git a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts index 1ad368ff2..0bfbb6501 100644 --- a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts @@ -1,23 +1,26 @@ -import { AppiumDriver, createDriver } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium"; import { Screen } from "./screens/screen" import { assertComponent, goBack, navigateToSecondComponent } from "./screens/shared-screen"; const homeComponent = "Home Component"; const roots = ["setFrameRootView", "setTabRootView"]; -describe("modal-shared:", () => { +describe("modal-shared:", async function () { let driver: AppiumDriver; let screen: Screen; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); - roots.forEach(root => { - describe("Shared modal from home component and back", () => { + for (let index = 0; index < roots.length; index++) { + const root = roots[index]; + describe("Shared modal from home component and back", async function () { - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; await screen[root](); }); @@ -32,125 +35,128 @@ describe("modal-shared:", () => { } }); - it("should find home component", async () => { + it("should find home component", async function () { await assertComponent(driver, homeComponent); }); - it("should open/close shared modal from home component", async () => { + it("should open/close shared modal from home component", async function () { await screen.loadSharedModal(true); await screen.closeModal(); }); - it("should open/close shared modal from home component again", async () => { + it("should open/close shared modal from home component again", async function () { await screen.loadSharedModal(true); await screen.closeModal(); }); - it("should open/close shared modal with presentation style from home component", async () => { + it("should open/close shared modal with presentation style from home component", async function () { await screen.loadSharedModalWithPresentationStyle(true); await screen.closeModal(); }); - it("should find home component again", async () => { + it("should find home component again", async function () { await screen.loadedHome(); }); - it("should navigate to second component", async () => { + it("should navigate to second component", async function () { await navigateToSecondComponent(driver); }); - it("should find second component", async () => { + it("should find second component", async function () { await assertComponent(driver, "second component"); }); - it("should open/close shared modal from second component", async () => { + it("should open/close shared modal from second component", async function () { await screen.loadSharedModal(true); await screen.closeModal(); }); - it("should find second component again", async () => { + it("should find second component again", async function () { await assertComponent(driver, "second component"); }); - it("should navigate back to home component", async () => { + it("should navigate back to home component", async function () { await goBack(driver); await assertComponent(driver, homeComponent); }); }); - }); + }; - describe("modal-shared-different-component:", () => { + describe("modal-shared-different-component:", async function () { let driver: AppiumDriver; let screen: Screen; - - before(async () => { + + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); - - roots.forEach(root => { - describe("Shared modal from different components", () => { - before(async () => { + + for (let index = 0; index < roots.length; index++) { + const root = roots[index]; + describe("Shared modal from different components", async function () { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - - after(async () => { + + after(async function () { await driver.quit(); console.log("Quit driver!"); }); - + afterEach(async function () { if (this.currentTest.state === "failed") { await driver.logTestArtifacts(this.currentTest.title); } }); - - it("should find home component", async () => { + + it("should find home component", async function () { await screen.loadedHome(); }); - - it("should open/close shared modal from home component", async () => { + + it("should open/close shared modal from home component", async function () { await screen.loadSharedModal(true); await screen.closeModal(); }); - - it("should find home component again", async () => { + + it("should find home component again", async function () { await screen.loadedHome(); }); - - it("should navigate to second component", async () => { + + it("should navigate to second component", async function () { await navigateToSecondComponent(driver); }); - - it("should find second component", async () => { + + it("should find second component", async function () { await assertComponent(driver, "second component"); }); - - it("should open/close shared modal from second component", async () => { + + it("should open/close shared modal from second component", async function () { await screen.loadSharedModal(true); await screen.closeModal(); }); - - it("should find second component again", async () => { + + it("should find second component again", async function () { await assertComponent(driver, "second component"); }); - - it("should navigate back to home component", async () => { + + it("should navigate back to home component", async function () { await goBack(driver); await screen.loadedHome(); }); - - it("should open/close shared modal from home component after manipulations with second", async () => { + + it("should open/close shared modal from home component after manipulations with second", async function () { await screen.loadSharedModal(true); await screen.closeModal(); }); - - it("should find home component again", async () => { + + it("should find home component again", async function () { await screen.loadedHome(); }); }); - }); + }; }); }); diff --git a/e2e/modal-navigation-ng/e2e/setup.ts b/e2e/modal-navigation-ng/e2e/setup.ts index 8b26e66e9..acfc6f70a 100644 --- a/e2e/modal-navigation-ng/e2e/setup.ts +++ b/e2e/modal-navigation-ng/e2e/setup.ts @@ -1,9 +1,18 @@ -import { startServer, stopServer } from "nativescript-dev-appium"; +import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium"; +const addContext = require('mochawesome/addContext'); -before("start server", async () => { +const testReporterContext = {}; +testReporterContext.name = "mochawesome"; +testReporterContext.reportDir = "mochawesome-report"; +testReporterContext.log = addContext; +testReporterContext.logImageTypes = [LogImageType.screenshots]; +nsCapabilities.testReporter = testReporterContext; + +before("start server", async function () { + nsCapabilities.testReporter.context = this; await startServer(); }); -after("stop server", async () => { +after("stop server", async function () { await stopServer(); -}); +}); \ No newline at end of file diff --git a/e2e/modal-navigation-ng/package.json b/e2e/modal-navigation-ng/package.json index fc6209dc9..31c2d0eeb 100644 --- a/e2e/modal-navigation-ng/package.json +++ b/e2e/modal-navigation-ng/package.json @@ -32,17 +32,15 @@ "devDependencies": { "@angular/compiler-cli": "~7.2.0", "@ngtools/webpack": "~7.2.0", - "@types/chai": "^4.0.2", - "@types/mocha": "^5.2.5", - "@types/node": "^10.12.12", + "@types/chai": "~4.1.7", + "@types/mocha": "~5.2.5", + "@types/node": "~10.12.18", "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", - "chai": "^4.2.0", "lazy": "1.0.11", - "mocha": "^5.2.0", - "mocha-junit-reporter": "^1.18.0", - "mocha-multi": "^1.0.1", + "mocha": "~5.2.0", + "mochawesome": "~3.1.2", "nativescript-dev-appium": "next", "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", diff --git a/e2e/nested-router-tab-view/.gitignore b/e2e/nested-router-tab-view/.gitignore index dbb747daa..e2c82407c 100644 --- a/e2e/nested-router-tab-view/.gitignore +++ b/e2e/nested-router-tab-view/.gitignore @@ -9,4 +9,5 @@ hooks e2e/reports test-results.xml -instrumentscli*.trace \ No newline at end of file +instrumentscli*.trace +mochawesome-report \ No newline at end of file diff --git a/e2e/nested-router-tab-view/e2e/custom-tabs.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/custom-tabs.e2e-spec.ts index e41b858e3..654e2a3ff 100644 --- a/e2e/nested-router-tab-view/e2e/custom-tabs.e2e-spec.ts +++ b/e2e/nested-router-tab-view/e2e/custom-tabs.e2e-spec.ts @@ -1,4 +1,4 @@ -import { AppiumDriver, createDriver } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium"; import { Screen } from "./screen" import { testPlayerNavigated, @@ -7,16 +7,17 @@ import { testTeamNextNavigated, } from "./shared.e2e-spec" -describe("custom-tabs:", () => { +describe("custom-tabs:", async function () { let driver: AppiumDriver; let screen: Screen; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); - after(async () => { + after(async function () { await driver.quit(); console.log("Quit driver!"); }); @@ -27,7 +28,7 @@ describe("custom-tabs:", () => { } }); - it("loaded custom tab component and tabs", async () => { + it("loaded custom tab component and tabs", async function () { await screen.navigateCustomTabsPage(); await screen.loadedCustomTabsPage(); await screen.loadedPlayersList(); @@ -35,7 +36,7 @@ describe("custom-tabs:", () => { await screen.loadedTeamList(); }); - it("navigate back to login and again to custom tabs", async () => { + it("navigate back to login and again to custom tabs", async function () { await backRoot(driver); await screen.loadedLogin(); await screen.navigateCustomTabsPage(); @@ -45,7 +46,7 @@ describe("custom-tabs:", () => { await screen.loadedTeamList(); }); - it("navigate back to login and again to custom tabs", async () => { + it("navigate back to login and again to custom tabs", async function () { await gotoPlayersTab(driver); await testPlayerNavigated(screen, screen.playerOne); await gotoTeamsTab(driver); diff --git a/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts index af4d6e65e..9444c7177 100644 --- a/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts +++ b/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts @@ -1,4 +1,4 @@ -import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, SearchOptions, nsCapabilities } from "nativescript-dev-appium"; import { Screen } from "./screen" import { testPlayerNavigated, @@ -12,22 +12,28 @@ import { const pages = ["Go To Home Page", "Go To Lazy Home Page"]; -describe("home-tabs:", () => { +describe("home-tabs:", async function () { let driver: AppiumDriver; let screen: Screen; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); - after(async () => { + after(async function () { await driver.quit(); console.log("Quit driver!"); }); - pages.forEach(page => { - describe(`${page} home-tab:`, () => { + for (let index = 0; index < pages.length; index++) { + const page = pages[index]; + describe(`${page} home-tab:`, async function () { + + before(async function () { + nsCapabilities.testReporter.context = this; + }); afterEach(async function () { if (this.currentTest.state === "failed") { @@ -35,14 +41,14 @@ describe("home-tabs:", () => { } }); - it("loaded home component and lists", async () => { + it("loaded home component and lists", async function () { await screen.navigateToHomePage(page); await screen.loadedHome(); await screen.loadedPlayersList(); await screen.loadedTeamList(); }); - it("should navigate to Tabs then to About forward", async () => { + it("should navigate to Tabs then to About forward", async function () { await screen.navigateToTabsPage(); await screen.loadedTabs(); await screen.loadedPlayersList(); @@ -51,7 +57,7 @@ describe("home-tabs:", () => { await screen.loadedNestedAbout(); }); - it("should go back to Tabs and then back to Home", async () => { + it("should go back to Tabs and then back to Home", async function () { await backActivatedRoute(driver); await screen.loadedTabs(); await screen.loadedPlayersList(); @@ -61,7 +67,7 @@ describe("home-tabs:", () => { await screen.loadedTeamList(); }); - it("should navigate to Tabs without Players/Teams navigation", async () => { + it("should navigate to Tabs without Players/Teams navigation", async function () { await screen.navigateToTabsPage(); await screen.loadedTabs(); await screen.loadedPlayersList(); @@ -71,7 +77,7 @@ describe("home-tabs:", () => { await screen.loadedTeamList(); }); - it("should navigate Player One/Team One then go to Tabs and back", async () => { + it("should navigate Player One/Team One then go to Tabs and back", async function () { await testPlayerNavigated(screen, screen.playerOne); await testTeamNavigated(screen, screen.teamOne); await screen.navigateToTabsPage(); @@ -88,7 +94,7 @@ describe("home-tabs:", () => { await screen.loadedTeamList(); }); - it("should navigate 2 times in Players go to Tabs and back", async () => { + it("should navigate 2 times in Players go to Tabs and back", async function () { await testPlayerNavigated(screen, screen.playerOne); await testPlayerNextNavigated(screen, screen.playerTwo); await screen.navigateToTabsPage(); @@ -112,7 +118,7 @@ describe("home-tabs:", () => { await screen.loadedLogin; }); }); - }); + }; }); async function backActivatedRoute(driver: AppiumDriver) { diff --git a/e2e/nested-router-tab-view/e2e/setup.ts b/e2e/nested-router-tab-view/e2e/setup.ts index 8b26e66e9..cd73140bb 100644 --- a/e2e/nested-router-tab-view/e2e/setup.ts +++ b/e2e/nested-router-tab-view/e2e/setup.ts @@ -1,9 +1,18 @@ -import { startServer, stopServer } from "nativescript-dev-appium"; +import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium"; +const addContext = require('mochawesome/addContext'); -before("start server", async () => { +const testReporterContext = {}; +testReporterContext.name = "mochawesome"; +testReporterContext.reportDir = "mochawesome-report"; +testReporterContext.log = addContext; +testReporterContext.logImageTypes = [LogImageType.screenshots]; +nsCapabilities.testReporter = testReporterContext; + +before("start server", async function () { + nsCapabilities.testReporter.context = this; await startServer(); }); -after("stop server", async () => { +after("stop server", async function () { await stopServer(); }); diff --git a/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts index abf6ce955..6b7710ebb 100644 --- a/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts +++ b/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts @@ -1,4 +1,4 @@ -import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, SearchOptions, nsCapabilities } from "nativescript-dev-appium"; import { Screen } from "./screen" import { testPlayerNavigated, @@ -11,22 +11,29 @@ import { const pages = ["Go To Home Page", "Go To Lazy Home Page"]; -describe("split-view:", () => { +describe("split-view:", async function () { let driver: AppiumDriver; let screen: Screen; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); - after(async () => { + after(async function () { await driver.quit(); console.log("Quit driver!"); }); - pages.forEach(page => { - describe(`${page} split-view:`, () => { + + for (let index = 0; index < pages.length; index++) { + const page = pages[index]; + describe(`${page} split-view:`, async function () { + + before(async function () { + nsCapabilities.testReporter.context = this; + }); afterEach(async function () { if (this.currentTest.state === "failed") { @@ -34,7 +41,7 @@ describe("split-view:", () => { } }); - it("loaded home component and lists", async () => { + it("loaded home component and lists", async function () { await screen.navigateToHomePage(page); await screen.loadedHome(); await screen.loadedPlayersList(); @@ -42,7 +49,7 @@ describe("split-view:", () => { await canGoBack(screen, screen.canGoBackActivatedRoute, true); }); - it("should navigate Player One/Team One then back separately", async () => { + it("should navigate Player One/Team One then back separately", async function () { await testPlayerNavigated(screen, screen.playerOne); await testTeamNavigated(screen, screen.teamOne); await canGoBack(screen, screen.canGoBackPlayers, true); @@ -54,7 +61,7 @@ describe("split-view:", () => { await screen.loadedTeamList(); }); - it("should navigate Player One/Team One then back separately (keep order)", async () => { + it("should navigate Player One/Team One then back separately (keep order)", async function () { await testPlayerNavigated(screen, screen.playerOne); await testTeamNavigated(screen, screen.teamOne); await backTeams(driver); @@ -63,7 +70,7 @@ describe("split-view:", () => { await screen.loadedPlayersList(); }); - it("should navigate Player One/Team One then back simultaneously", async () => { + it("should navigate Player One/Team One then back simultaneously", async function () { await testPlayerNavigated(screen, screen.playerOne); await testTeamNavigated(screen, screen.teamOne); await canGoBack(screen, screen.canGoBackBoth, true); @@ -73,7 +80,7 @@ describe("split-view:", () => { await screen.loadedPlayersList(); }); - it("should navigate Player One/Team One then next Player/Team then back separately", async () => { + it("should navigate Player One/Team One then next Player/Team then back separately", async function () { await testPlayerNavigated(screen, screen.playerOne); await testTeamNavigated(screen, screen.teamOne); await testPlayerNextNavigated(screen, screen.playerTwo); @@ -88,7 +95,7 @@ describe("split-view:", () => { await screen.loadedPlayersList(); }); - it("should navigate Player One/Team One then back", async () => { + it("should navigate Player One/Team One then back", async function () { await testPlayerNavigated(screen, screen.playerOne); await testTeamNavigated(screen, screen.teamOne); await back(driver); @@ -99,7 +106,7 @@ describe("split-view:", () => { await screen.loadedPlayersList(); }); - it("should navigate Player One then navigate Players list then back", async () => { + it("should navigate Player One then navigate Players list then back", async function () { await testPlayerNavigated(screen, screen.playerOne); await testPlayerNextNavigated(screen, screen.playerTwo); await testPlayersNavigated(screen); @@ -111,7 +118,7 @@ describe("split-view:", () => { await screen.loadedPlayersList(); }); - it("should navigate Player One/Team One then Android back button", async () => { + it("should navigate Player One/Team One then Android back button", async function () { if (driver.isAndroid) { await testPlayerNavigated(screen, screen.playerOne); await testTeamNavigated(screen, screen.teamOne); @@ -148,7 +155,7 @@ describe("split-view:", () => { await screen.loadedLogin; }); }); - }); + }; }); async function backActivatedRoute(driver: AppiumDriver) { diff --git a/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts index 3fa7b35e0..d40cc07dd 100644 --- a/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts +++ b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts @@ -1,4 +1,4 @@ -import { AppiumDriver, createDriver } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, nsCapabilities } from "nativescript-dev-appium"; import { Screen } from "./screen" import { testPlayerNavigated, @@ -7,11 +7,12 @@ import { testTeamNextNavigated, } from "./shared.e2e-spec" -describe("tab-view:", () => { +describe("tab-view:", async function () { let driver: AppiumDriver; let screen: Screen; before(async () => { + nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); diff --git a/e2e/nested-router-tab-view/package.json b/e2e/nested-router-tab-view/package.json index d4398ba15..ff096ee2f 100644 --- a/e2e/nested-router-tab-view/package.json +++ b/e2e/nested-router-tab-view/package.json @@ -32,17 +32,15 @@ "devDependencies": { "@angular/compiler-cli": "~7.2.0", "@ngtools/webpack": "~7.2.0", - "@types/chai": "^4.0.2", - "@types/mocha": "^5.2.5", - "@types/node": "^10.12.12", + "@types/chai": "~4.1.7", + "@types/mocha": "~5.2.5", + "@types/node": "~10.12.18", "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", - "chai": "^4.2.0", "lazy": "1.0.11", - "mocha": "^5.2.0", - "mocha-junit-reporter": "^1.18.0", - "mocha-multi": "^1.0.1", + "mocha": "~5.2.0", + "mochawesome": "~3.1.2", "nativescript-dev-appium": "next", "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", @@ -52,4 +50,4 @@ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json", "compile-tests": "tsc -p e2e --watch" } -} \ No newline at end of file +} diff --git a/e2e/renderer/.gitignore b/e2e/renderer/.gitignore index 5a67ca289..f2486e508 100644 --- a/e2e/renderer/.gitignore +++ b/e2e/renderer/.gitignore @@ -6,4 +6,5 @@ app/**/*.js e2e/**/*.js test-results.xml e2e/reports +mochawesome-report diff --git a/e2e/renderer/e2e/action-bar.e2e-spec.ts b/e2e/renderer/e2e/action-bar.e2e-spec.ts index 011c17645..7b62008ff 100644 --- a/e2e/renderer/e2e/action-bar.e2e-spec.ts +++ b/e2e/renderer/e2e/action-bar.e2e-spec.ts @@ -2,22 +2,24 @@ import { AppiumDriver, createDriver, SearchOptions, - UIElement + UIElement, + nsCapabilities } from "nativescript-dev-appium"; import { isOnTheLeft } from "./helpers/location"; import { assert } from "chai"; -describe("Action Bar scenario", () => { +describe("Action Bar scenario", async function () { let driver: AppiumDriver; - describe("dynamically add/remove ActionItems", async () => { + describe("dynamically add/remove ActionItems", async function () { let firstActionItem: UIElement; let secondActionItem: UIElement; let toggleFirstButton: UIElement; let toggleSecondButton: UIElement; before(async () => { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -28,7 +30,7 @@ describe("Action Bar scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("ActionBar dynamic"); await navigationButton.click(); @@ -37,7 +39,7 @@ describe("Action Bar scenario", () => { await driver.findElementByAutomationText("Action Bar Dynamic Items"); }); - it("should find elements", async () => { + it("should find elements", async function () { firstActionItem = await driver.findElementByAutomationText("one"); secondActionItem = await driver.findElementByAutomationText("two"); @@ -45,7 +47,7 @@ describe("Action Bar scenario", () => { toggleSecondButton = await driver.findElementByAutomationText("toggle 2"); }); - it("should initially render the action items in the correct order", async () => { + it("should initially render the action items in the correct order", async function () { await checkOrderIsCorrect(); }); @@ -61,7 +63,7 @@ describe("Action Bar scenario", () => { })(); }); - it("should attach first element in the correct position", async () => { + it("should attach first element in the correct position", async function () { await toggleFirst(); await checkOrderIsCorrect(); }); @@ -78,12 +80,12 @@ describe("Action Bar scenario", () => { })(); }); - it("should attach second element in the correct position", async () => { + it("should attach second element in the correct position", async function () { await toggleSecond(); await checkOrderIsCorrect(); }); - it("should detach and then reattach both at correct places", async () => { + it("should detach and then reattach both at correct places", async function () { await toggleFirst(); await toggleSecond(); @@ -107,11 +109,12 @@ describe("Action Bar scenario", () => { }); - describe("Action Bar extension with dynamic ActionItem", async () => { + describe("Action Bar extension with dynamic ActionItem", async function () { let toggleButton: UIElement; let conditional: UIElement; before(async () => { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -122,28 +125,28 @@ describe("Action Bar scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("ActionBarExtension"); await navigationButton.click(); }); - it("should find elements", async () => { + it("should find elements", async function () { toggleButton = await driver.findElementByAutomationText("toggle"); conditional = await driver.findElementByAutomationText("conditional"); }); - it("should detach conditional action item when its condition is false", async () => { + it("should detach conditional action item when its condition is false", async function () { await toggle(); const conditionalBtn = await driver.waitForElement("conditional", 1000); assert.isUndefined(conditionalBtn, "Conditional button should not be visible!"); }); - it("should reattach conditional action item at correct place", async () => { + it("should reattach conditional action item at correct place", async function () { await toggle(); await checkOrderIsCorrect(); }); - + const checkOrderIsCorrect = async () => { await isOnTheLeft(toggleButton, conditional); }; diff --git a/e2e/renderer/e2e/ngfor.e2e-spec.ts b/e2e/renderer/e2e/ngfor.e2e-spec.ts index 007def76f..cca35b8c1 100644 --- a/e2e/renderer/e2e/ngfor.e2e-spec.ts +++ b/e2e/renderer/e2e/ngfor.e2e-spec.ts @@ -2,12 +2,13 @@ import { AppiumDriver, createDriver, SearchOptions, - UIElement + UIElement, + nsCapabilities } from "nativescript-dev-appium"; import { isAbove } from "./helpers/location"; -describe("ngFor scenario", () => { +describe("ngFor scenario", async function () { let driver: AppiumDriver; let addButton: UIElement; let removeButton: UIElement; @@ -15,6 +16,7 @@ describe("ngFor scenario", () => { let lastAddedElementId = 0; before(async () => { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -25,7 +27,7 @@ describe("ngFor scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("NgFor"); await navigationButton.click(); @@ -34,7 +36,7 @@ describe("ngFor scenario", () => { await driver.findElementByAutomationText("ngFor"); }); - it("should find elements", async () => { + it("should find elements", async function () { const first = await driver.findElementByAutomationText( lastAddedElementId.toString()); elements.push(first); @@ -45,33 +47,33 @@ describe("ngFor scenario", () => { await isAbove(first, addButton); }); - it("should render elements in correct order", async () => { + it("should render elements in correct order", async function () { await isAbove(elements[0], addButton); await isAbove(addButton, removeButton); }); - it("should place new elements in the right places", async () => { + it("should place new elements in the right places", async function () { for (let i = 0; i < 5; i += 1) { await addElement(); await checkAppendedCorrectly(); } }); - it("shouldn't reorder elements when last is removed", async () => { + it("shouldn't reorder elements when last is removed", async function () { while (elements.length) { await removeElement(); await checkCorrectOrderAll(); } }); - it("should render new elements correctly after all old ones are removed", async () => { + it("should render new elements correctly after all old ones are removed", async function () { for (let i = 0; i < 5; i += 1) { await addElement(); await checkCorrectOrderAll(); } }); - it("shouldn't reorder elements when middle is removed", async () => { + it("shouldn't reorder elements when middle is removed", async function () { const middleIndex = Math.floor(elements.length / 2); await removeElement(middleIndex); await checkCorrectOrderAll(); diff --git a/e2e/renderer/e2e/ngforof.e2e-spec.ts b/e2e/renderer/e2e/ngforof.e2e-spec.ts index 219fa08bb..e14e079f4 100644 --- a/e2e/renderer/e2e/ngforof.e2e-spec.ts +++ b/e2e/renderer/e2e/ngforof.e2e-spec.ts @@ -2,7 +2,8 @@ import { AppiumDriver, createDriver, SearchOptions, - UIElement + UIElement, + nsCapabilities } from "nativescript-dev-appium"; import { isAbove } from "./helpers/location"; @@ -21,6 +22,7 @@ describe("ngForOf scenario", function () { let lastAddedElementId = 0; before(async () => { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -31,7 +33,7 @@ describe("ngForOf scenario", function () { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("NgForOf"); await navigationButton.click(); @@ -40,7 +42,7 @@ describe("ngForOf scenario", function () { await driver.findElementByAutomationText("ngForOf"); }); - it("should find elements", async () => { + it("should find elements", async function () { const firstElement = await getElement(lastAddedElementId); elements.push(firstElement); @@ -51,35 +53,35 @@ describe("ngForOf scenario", function () { await isAbove(firstElement.button, addButton); }); - it("should render elements in correct order", async () => { + it("should render elements in correct order", async function () { await elementTupleCorrectlyRendered(elements[0]); await isAbove(elements[0].button, addButton); await isAbove(addButton, removeButton); }); - it("should place new elements in the right places", async () => { + it("should place new elements in the right places", async function () { for (let i = 0; i < 2; i += 1) { await addElement(); await checkAppendedCorrectly(); } }); - it("shouldn't reorder elements when last is removed", async () => { + it("shouldn't reorder elements when last is removed", async function () { while (elements.length) { await removeElement(); await checkCorrectOrderAll(); } }); - it("should render new elements correctly after all old ones are removed", async () => { + it("should render new elements correctly after all old ones are removed", async function () { for (let i = 0; i < 5; i += 1) { await addElement(); await checkCorrectOrderAll(); } }); - it("shouldn't reorder elements when middle is removed", async () => { + it("shouldn't reorder elements when middle is removed", async function () { const middleIndex = Math.floor(elements.length / 2); await removeElement(middleIndex); await checkCorrectOrderAll(); @@ -148,7 +150,7 @@ describe("ngForOf scenario", function () { const getElement = async (id: number) => { let label = null; let button = null; - + if (driver.platformName.toLowerCase().includes("ios")) { label = await driver.findElementByAccessibilityId( "label: " + id.toString()); diff --git a/e2e/renderer/e2e/ngif.e2e-spec.ts b/e2e/renderer/e2e/ngif.e2e-spec.ts index e164114f8..c81c37a26 100644 --- a/e2e/renderer/e2e/ngif.e2e-spec.ts +++ b/e2e/renderer/e2e/ngif.e2e-spec.ts @@ -2,7 +2,8 @@ import { AppiumDriver, createDriver, SearchOptions, - UIElement + UIElement, + nsCapabilities } from "nativescript-dev-appium"; import { isAbove } from "./helpers/location"; @@ -13,8 +14,9 @@ describe("ngIf scenario", () => { let driver: AppiumDriver; let toggleButton: UIElement; - describe("without layout", async () => { - before(async () => { + describe("without layout", async function () { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -25,7 +27,7 @@ describe("ngIf scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("NgIf no layout"); await navigationButton.click(); @@ -34,27 +36,28 @@ describe("ngIf scenario", () => { await driver.findElementByAutomationText("ngIf - no layout"); }); - it("should find elements", async () => { + it("should find elements", async function () { await driver.findElementByAutomationText("false"); toggleButton = await driver.findElementByAutomationText("Toggle"); }); - it("show 'true' button when show is true", async () => { + it("show 'true' button when show is true", async function () { await toggleButton.click(); await driver.findElementByAutomationText("true"); }); }); - describe("label inbetween", async () => { + describe("label inbetween", async function () { let firstButton: UIElement; let secondButton: UIElement; let conditionalLabel: UIElement; let toggle: UIElement; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); - await driver.driver.resetApp(); + await driver.driver.resetApp(); }); afterEach(async function () { @@ -63,7 +66,7 @@ describe("ngIf scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("NgIf inbetween"); await navigationButton.click(); @@ -72,7 +75,7 @@ describe("ngIf scenario", () => { await driver.findElementByAutomationText("ngIf - inbetween"); }); - it("should find elements", async () => { + it("should find elements", async function () { firstButton = await driver.findElementByAutomationText("Button 1"); secondButton = await driver.findElementByAutomationText("Button 2"); toggleButton = await driver.findElementByAutomationText("Toggle"); @@ -83,7 +86,7 @@ describe("ngIf scenario", () => { }); it("detach label when condition is false", done => { - (async () => { + (async function () { await toggleButton.click(); try { @@ -95,14 +98,15 @@ describe("ngIf scenario", () => { }); }); - describe("with else template", async () => { + describe("with else template", async function () { let ifButton: UIElement; let elseButton: UIElement; let toggle: UIElement; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); - await driver.driver.resetApp(); + await driver.driver.resetApp(); }); afterEach(async function () { @@ -111,7 +115,7 @@ describe("ngIf scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("NgIfElse"); await navigationButton.click(); @@ -120,7 +124,7 @@ describe("ngIf scenario", () => { await driver.findElementByAutomationText("ngIfElse"); }); - it("should find elements", async () => { + it("should find elements", async function () { toggleButton = await driver.findElementByAutomationText("Toggle"); ifButton = await driver.findElementByAutomationText("If"); }); @@ -131,7 +135,7 @@ describe("ngIf scenario", () => { .catch(() => done()); }); - it("should attach 'else' template when condition is changed to false", async () => { + it("should attach 'else' template when condition is changed to false", async function () { await toggleButton.click(); elseButton = await driver.findElementByAutomationText("Else"); @@ -144,7 +148,7 @@ describe("ngIf scenario", () => { }); it("should swap the content when condition is changed", done => { - (async () => { + (async function () { await toggleButton.click(); try { @@ -156,14 +160,15 @@ describe("ngIf scenario", () => { }); }); - describe("with then-else template", async () => { + describe("with then-else template", async function () { let thenButton: UIElement; let elseButton: UIElement; let toggle: UIElement; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); - await driver.driver.resetApp(); + await driver.driver.resetApp(); }); afterEach(async function () { @@ -172,7 +177,7 @@ describe("ngIf scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("NgIf Then Else"); await navigationButton.click(); @@ -181,7 +186,7 @@ describe("ngIf scenario", () => { await driver.findElementByAutomationText("ngIf Then Else"); }); - it("should find elements", async () => { + it("should find elements", async function () { toggleButton = await driver.findElementByAutomationText("Toggle"); thenButton = await driver.findElementByAutomationText("Then"); }); @@ -192,7 +197,7 @@ describe("ngIf scenario", () => { .catch(() => done()); }); - it("should attach 'else' template when condition is changed to false", async () => { + it("should attach 'else' template when condition is changed to false", async function () { await toggleButton.click(); elseButton = await driver.findElementByAutomationText("Else"); @@ -205,7 +210,7 @@ describe("ngIf scenario", () => { }); it("should swap the content when condition is changed", done => { - (async () => { + (async function () { await toggleButton.click(); try { @@ -217,14 +222,15 @@ describe("ngIf scenario", () => { }); }); - describe("then-else templates inside content view", async () => { + describe("then-else templates inside content view", async function () { let thenButton: UIElement; let elseButton: UIElement; let toggle: UIElement; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); - await driver.driver.resetApp(); + await driver.driver.resetApp(); }); afterEach(async function () { @@ -233,7 +239,7 @@ describe("ngIf scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("Content view"); await navigationButton.click(); @@ -242,7 +248,7 @@ describe("ngIf scenario", () => { await driver.findElementByAutomationText("Content View"); }); - it("should find elements", async () => { + it("should find elements", async function () { toggleButton = await driver.findElementByAutomationText("Toggle"); thenButton = await driver.findElementByAutomationText("Then"); }); @@ -253,7 +259,7 @@ describe("ngIf scenario", () => { .catch(() => done()); }); - it("should attach 'else' template when condition is changed to false", async () => { + it("should attach 'else' template when condition is changed to false", async function () { await toggleButton.click(); elseButton = await driver.findElementByAutomationText("Else"); @@ -266,7 +272,7 @@ describe("ngIf scenario", () => { }); it("should swap the content when condition is changed", done => { - (async () => { + (async function () { await toggleButton.click(); try { @@ -278,13 +284,14 @@ describe("ngIf scenario", () => { }); }); - describe("subsequent ifs", async () => { + describe("subsequent ifs", async function () { let firstButton: UIElement; let secondButton: UIElement; let firstLabel: UIElement; let secondLabel: UIElement; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -294,14 +301,14 @@ describe("ngIf scenario", () => { await driver.logTestArtifacts(this.currentTest.title); } }); - - it("should navigate to page", async () => { + + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("NgIf Subsequent Ifs"); await navigationButton.click(); }); - it("should find elements", async () => { + it("should find elements", async function () { firstButton = await driver.findElementByAutomationText("Toggle first"); secondButton = await driver.findElementByAutomationText("Toggle second"); @@ -314,7 +321,7 @@ describe("ngIf scenario", () => { assert.isDefined(secondLabel); }); - it("should toggle on first view", async () => { + it("should toggle on first view", async function () { await firstButton.click(); let conditional = await driver.findElementByAutomationText("first"); @@ -324,7 +331,7 @@ describe("ngIf scenario", () => { }); it("should toggle off first view", done => { - (async () => { + (async function () { await firstButton.click(); driver.findElementsByAutomationText("first", 500) @@ -333,7 +340,7 @@ describe("ngIf scenario", () => { })(); }); - it("should toggle on second view", async () => { + it("should toggle on second view", async function () { await secondButton.click(); let conditional = await driver.findElementByAutomationText("second"); @@ -342,7 +349,7 @@ describe("ngIf scenario", () => { }); it("should toggle off second view", done => { - (async () => { + (async function () { await secondButton.click(); driver.findElementByAutomationText("first", 500) @@ -351,7 +358,7 @@ describe("ngIf scenario", () => { })(); }); - it("should toggle on both views", async () => { + it("should toggle on both views", async function () { await firstButton.click(); await secondButton.click(); @@ -363,7 +370,7 @@ describe("ngIf scenario", () => { }); it("should toggle off both views", done => { - (async () => { + (async function () { await firstButton.click(); await secondButton.click(); @@ -377,7 +384,7 @@ describe("ngIf scenario", () => { })(); }); - it("should toggle on both views in reverse", async () => { + it("should toggle on both views in reverse", async function () { await secondButton.click(); await firstButton.click(); @@ -389,7 +396,7 @@ describe("ngIf scenario", () => { }); it("should toggle off both views in reverse", done => { - (async () => { + (async function () { await secondButton.click(); await firstButton.click(); diff --git a/e2e/renderer/e2e/page-router-outlet.e2e-spec.ts b/e2e/renderer/e2e/page-router-outlet.e2e-spec.ts index ad79f6116..fb1ef8a84 100644 --- a/e2e/renderer/e2e/page-router-outlet.e2e-spec.ts +++ b/e2e/renderer/e2e/page-router-outlet.e2e-spec.ts @@ -2,15 +2,17 @@ import { AppiumDriver, createDriver, SearchOptions, - UIElement + UIElement, + nsCapabilities } from "nativescript-dev-appium"; import { assert } from "chai"; -describe("page-router-outlet-scenario", () => { +describe("page-router-outlet-scenario", async function () { let driver: AppiumDriver; - describe("actionBarVisibility 'always' shows action bars", async () => { - before(async () => { + describe("actionBarVisibility 'always' shows action bars", async function () { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -21,7 +23,7 @@ describe("page-router-outlet-scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("ActionBarVisibility Always"); await navigationButton.click(); @@ -29,30 +31,31 @@ describe("page-router-outlet-scenario", () => { await driver.findElementByAutomationText("ShowActionBar"); }); - it("should not hide action bar by default", async () => { - const screenMatches = await driver.compareScreen("actionBarVisibility-always-default", 5); + it("should not hide action bar by default", async function () { + const screenMatches = await driver.compareScreen("actionBarVisibility-always-default", 5); assert(screenMatches); }); - it("should not hide action bar when hidden by page", async () => { + it("should not hide action bar when hidden by page", async function () { const hideActionBarButton = await driver.findElementByAutomationText("HideActionBar"); hideActionBarButton.click(); - const screenMatches = await driver.compareScreen("actionBarVisibility-always-hidden", 5); + const screenMatches = await driver.compareScreen("actionBarVisibility-always-hidden", 5); assert(screenMatches); }); - it("should not do anything when shown action bar by page", async () => { + it("should not do anything when shown action bar by page", async function () { const showActionBarButton = await driver.findElementByAutomationText("ShowActionBar"); showActionBarButton.click(); - const screenMatches = await driver.compareScreen("actionBarVisibility-always-shown", 5); + const screenMatches = await driver.compareScreen("actionBarVisibility-always-shown", 5); assert(screenMatches); }); }); - describe("actionBarVisibility 'never' doesn't show action bars", async () => { - before(async () => { + describe("actionBarVisibility 'never' doesn't show action bars", async function () { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -63,7 +66,7 @@ describe("page-router-outlet-scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("ActionBarVisibility Never"); await navigationButton.click(); @@ -71,31 +74,32 @@ describe("page-router-outlet-scenario", () => { await driver.findElementByAutomationText("ShowActionBar"); }); - it("should hide action bar by default", async () => { - const screenMatches = await driver.compareScreen("actionBarVisibility-never-default", 5); + it("should hide action bar by default", async function () { + const screenMatches = await driver.compareScreen("actionBarVisibility-never-default", 5); assert(screenMatches); }); - it("should not show action bar when shown by page", async () => { + it("should not show action bar when shown by page", async function () { const showActionBarButton = await driver.findElementByAutomationText("ShowActionBar"); showActionBarButton.click(); - const screenMatches = await driver.compareScreen("actionBarVisibility-never-shown", 5); + const screenMatches = await driver.compareScreen("actionBarVisibility-never-shown", 5); assert(screenMatches); }); - it("should not do anything when hidden action bar by page", async () => { + it("should not do anything when hidden action bar by page", async function () { const hideActionBarButton = await driver.findElementByAutomationText("HideActionBar"); hideActionBarButton.click(); - const screenMatches = await driver.compareScreen("actionBarVisibility-never-hidden", 5); + const screenMatches = await driver.compareScreen("actionBarVisibility-never-hidden", 5); assert(screenMatches); }); }); - describe("actionBarVisibility 'never' doesn't show action bars in lazy module page", async () => { + describe("actionBarVisibility 'never' doesn't show action bars in lazy module page", async function () { let imagePostFix = ""; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); if (driver.isIOS && driver.nsCapabilities.device.name.toLowerCase().includes("x")) { @@ -109,7 +113,7 @@ describe("page-router-outlet-scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("ActionBarVisibility Never Lazy"); await navigationButton.click(); @@ -117,30 +121,31 @@ describe("page-router-outlet-scenario", () => { await driver.findElementByAutomationText("ShowActionBar"); }); - it("should hide action bar by default", async () => { - const screenMatches = await driver.compareScreen(`actionBarVisibility-never-default${imagePostFix}`, 5); + it("should hide action bar by default", async function () { + const screenMatches = await driver.compareScreen(`actionBarVisibility-never-default${imagePostFix}`, 5); assert(screenMatches); }); - it("should not show action bar when shown by page", async () => { + it("should not show action bar when shown by page", async function () { const showActionBarButton = await driver.findElementByAutomationText("ShowActionBar"); showActionBarButton.click(); - const screenMatches = await driver.compareScreen(`actionBarVisibility-never-shown${imagePostFix}`, 5); + const screenMatches = await driver.compareScreen(`actionBarVisibility-never-shown${imagePostFix}`, 5); assert(screenMatches); }); - it("should not do anything when hidden action bar by page", async () => { + it("should not do anything when hidden action bar by page", async function () { const hideActionBarButton = await driver.findElementByAutomationText("HideActionBar"); hideActionBarButton.click(); - const screenMatches = await driver.compareScreen(`actionBarVisibility-never-hidden${imagePostFix}`, 5); + const screenMatches = await driver.compareScreen(`actionBarVisibility-never-hidden${imagePostFix}`, 5); assert(screenMatches); }); }); - describe("actionBarVisibility 'auto' shows action bars based on page", async () => { - before(async () => { + describe("actionBarVisibility 'auto' shows action bars based on page", async function () { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -151,7 +156,7 @@ describe("page-router-outlet-scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function () { const navigationButton = await driver.findElementByAutomationText("ActionBarVisibility Auto"); await navigationButton.click(); @@ -159,24 +164,24 @@ describe("page-router-outlet-scenario", () => { await driver.findElementByAutomationText("ShowActionBar"); }); - it("should show action bar by default", async () => { - const screenMatches = await driver.compareScreen("actionBarVisibility-auto-default", 5); + it("should show action bar by default", async function () { + const screenMatches = await driver.compareScreen("actionBarVisibility-auto-default", 5); assert(screenMatches); }); - it("should hide action bar when hidden by page", async () => { + it("should hide action bar when hidden by page", async function () { const hideActionBarButton = await driver.findElementByAutomationText("HideActionBar"); hideActionBarButton.click(); - const screenMatches = await driver.compareScreen("actionBarVisibility-auto-hidden", 5); + const screenMatches = await driver.compareScreen("actionBarVisibility-auto-hidden", 5); assert(screenMatches); }); - it("should show action bar when shown by page", async () => { + it("should show action bar when shown by page", async function () { const showActionBarButton = await driver.findElementByAutomationText("ShowActionBar"); showActionBarButton.click(); - const screenMatches = await driver.compareScreen("actionBarVisibility-auto-shown", 5); + const screenMatches = await driver.compareScreen("actionBarVisibility-auto-shown", 5); assert(screenMatches); }); }); diff --git a/e2e/renderer/e2e/setup.ts b/e2e/renderer/e2e/setup.ts index d52e381d0..11b854fca 100644 --- a/e2e/renderer/e2e/setup.ts +++ b/e2e/renderer/e2e/setup.ts @@ -1,13 +1,19 @@ -import { startServer, stopServer, createDriver, AppiumDriver } from "nativescript-dev-appium"; +import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium"; +const addContext = require('mochawesome/addContext'); -let driver: AppiumDriver; +const testReporterContext = {}; +testReporterContext.name = "mochawesome"; +testReporterContext.reportDir = "mochawesome-report"; +testReporterContext.log = addContext; +testReporterContext.logImageTypes = [LogImageType.screenshots]; +nsCapabilities.testReporter = testReporterContext; -before("start server", async () => { +before("start server", async function () { + nsCapabilities.testReporter.context = this; await startServer(); - driver = await createDriver(); }); -after("stop server", async () => { - await driver.quit(); +after("stop server", async function () { + nsCapabilities.testReporter.context = this; await stopServer(); -}); \ No newline at end of file +}); diff --git a/e2e/renderer/e2e/tab-view.e2e-spec.ts b/e2e/renderer/e2e/tab-view.e2e-spec.ts index f5415289f..82f3b6f58 100644 --- a/e2e/renderer/e2e/tab-view.e2e-spec.ts +++ b/e2e/renderer/e2e/tab-view.e2e-spec.ts @@ -2,19 +2,21 @@ import { AppiumDriver, createDriver, SearchOptions, - UIElement + UIElement, + nsCapabilities } from "nativescript-dev-appium"; import { assert } from "chai"; -describe("TabView-scenario", () => { +describe("TabView-scenario", async function(){ let driver: AppiumDriver; - describe("dynamically change TabView item title, icon and textTransform", async () => { + describe("dynamically change TabView item title, icon and textTransform", async function(){ let firstTabItem: UIElement; let secondTabItem: UIElement; let thirdTabItem: UIElement; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); }); @@ -25,7 +27,7 @@ describe("TabView-scenario", () => { } }); - it("should navigate to page", async () => { + it("should navigate to page", async function(){ const navigationButton = await driver.findElementByAutomationText("TabItem Binding"); await navigationButton.click(); @@ -33,7 +35,7 @@ describe("TabView-scenario", () => { await driver.findElementByAutomationText("Tab Item Binding"); }); - it("should find elements", async () => { + it("should find elements", async function(){ await driver.findElementByAutomationText("First Tab"); const notSelectedTabItems = await driver.findElementsByText("not selected"); @@ -46,7 +48,7 @@ describe("TabView-scenario", () => { assert(screenMatches); }); - it("should navigate to second tab item", async () => { + it("should navigate to second tab item", async function(){ await secondTabItem.click(); await driver.findElementByAutomationText("Second Tab"); @@ -61,7 +63,7 @@ describe("TabView-scenario", () => { assert(screenMatches); }); - it("should navigate to third tab item", async () => { + it("should navigate to third tab item", async function(){ await thirdTabItem.click(); await driver.findElementByAutomationText("Third Tab"); diff --git a/e2e/renderer/package.json b/e2e/renderer/package.json index 8683bd45b..e32d7588e 100644 --- a/e2e/renderer/package.json +++ b/e2e/renderer/package.json @@ -25,19 +25,17 @@ "devDependencies": { "@angular/compiler-cli": "~7.2.0", "@ngtools/webpack": "~7.2.0", - "@types/chai": "~4.1.3", - "@types/mocha": "~5.2.1", - "@types/node": "10.11.4", + "@types/chai": "~4.1.7", + "@types/mocha": "~5.2.5", + "@types/node": "~10.12.18", "babel-traverse": "6.25.0", "babel-types": "6.25.0", "babylon": "6.17.4", - "chai": "~4.1.1", "chai-as-promised": "~7.1.1", "colors": "^1.1.2", "lazy": "1.0.11", - "mocha": "~5.1.0", - "mocha-junit-reporter": "~1.17.0", - "mocha-multi": "~1.0.0", + "mocha": "~5.2.0", + "mochawesome": "~3.1.2", "nativescript-dev-appium": "next", "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", diff --git a/e2e/router-tab-view/.gitignore b/e2e/router-tab-view/.gitignore index dbb747daa..e2c82407c 100644 --- a/e2e/router-tab-view/.gitignore +++ b/e2e/router-tab-view/.gitignore @@ -9,4 +9,5 @@ hooks e2e/reports test-results.xml -instrumentscli*.trace \ No newline at end of file +instrumentscli*.trace +mochawesome-report \ No newline at end of file diff --git a/e2e/router-tab-view/e2e/setup.ts b/e2e/router-tab-view/e2e/setup.ts index 8b26e66e9..cd73140bb 100644 --- a/e2e/router-tab-view/e2e/setup.ts +++ b/e2e/router-tab-view/e2e/setup.ts @@ -1,9 +1,18 @@ -import { startServer, stopServer } from "nativescript-dev-appium"; +import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium"; +const addContext = require('mochawesome/addContext'); -before("start server", async () => { +const testReporterContext = {}; +testReporterContext.name = "mochawesome"; +testReporterContext.reportDir = "mochawesome-report"; +testReporterContext.log = addContext; +testReporterContext.logImageTypes = [LogImageType.screenshots]; +nsCapabilities.testReporter = testReporterContext; + +before("start server", async function () { + nsCapabilities.testReporter.context = this; await startServer(); }); -after("stop server", async () => { +after("stop server", async function () { await stopServer(); }); diff --git a/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts b/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts index b57be824d..f0ead8efa 100644 --- a/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts +++ b/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts @@ -1,31 +1,27 @@ -import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; +import { AppiumDriver, createDriver, SearchOptions, nsCapabilities } from "nativescript-dev-appium"; import { assert } from "chai"; -describe("TabView with page-router-outlet in each tab", () => { +describe("TabView with page-router-outlet in each tab", async function () { let driver: AppiumDriver; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); }); - after(async () => { - await driver.quit(); - console.log("Quit driver!"); - }); - afterEach(async function () { if (this.currentTest.state === "failed") { await driver.logTestArtifacts(this.currentTest.title); } }); - it("should find any tabs by text", async () => { + it("should find any tabs by text", async function () { await driver.findElementByAutomationText("Players"); await driver.findElementByAutomationText("Teams"); await driver.findElementByAutomationText("Player List"); }); - it("should be able to switch between tabs", async () => { + it("should be able to switch between tabs", async function () { await driver.findElementByAutomationText("Player List"); await selectTeamTab(driver); @@ -33,7 +29,7 @@ describe("TabView with page-router-outlet in each tab", () => { await selectPlayerTab(driver); }); - it("should go forward and go back on first(player) tab", async () => { + it("should go forward and go back on first(player) tab", async function () { await driver.findElementByAutomationText("Player List"); await navigateToPlayerItem(driver, "Player One", "1"); @@ -42,7 +38,7 @@ describe("TabView with page-router-outlet in each tab", () => { await driver.findElementByAutomationText("Player List"); }); - it("should go forward and go back on second(team) tab", async () => { + it("should go forward and go back on second(team) tab", async function () { await driver.findElementByAutomationText("Player List"); await selectTeamTab(driver); @@ -55,7 +51,7 @@ describe("TabView with page-router-outlet in each tab", () => { await selectPlayerTab(driver); }); - it("should navigate first(player) tab, second(team) tab and back in the same order ", async () => { + it("should navigate first(player) tab, second(team) tab and back in the same order ", async function () { await driver.findElementByAutomationText("Player List"); // Go forward in player tab @@ -79,7 +75,7 @@ describe("TabView with page-router-outlet in each tab", () => { await driver.findElementByAutomationText("Player List"); }); - it("should navigate second(team) tab, first(player) and back in the same order ", async () => { + it("should navigate second(team) tab, first(player) and back in the same order ", async function () { await driver.findElementByAutomationText("Player List"); // Go forward in team tab @@ -106,7 +102,7 @@ describe("TabView with page-router-outlet in each tab", () => { await selectPlayerTab(driver); }); - it("should navigate first(player) tab, second(team) tab and back in reverse order ", async () => { + it("should navigate first(player) tab, second(team) tab and back in reverse order ", async function () { await driver.findElementByAutomationText("Player List"); // Go forward in player tab @@ -129,10 +125,10 @@ describe("TabView with page-router-outlet in each tab", () => { await driver.navBack(); await driver.findElementByAutomationText("Team List"); - await selectPlayerTab(driver); + await selectPlayerTab(driver); }); - it("should navigate second(team) tab, first(player) tab and back in reverse order ", async () => { + it("should navigate second(team) tab, first(player) tab and back in reverse order ", async function () { await driver.findElementByAutomationText("Player List"); // Go forward in team tab diff --git a/e2e/router-tab-view/package.json b/e2e/router-tab-view/package.json index d4a2a71db..0672cbcfc 100644 --- a/e2e/router-tab-view/package.json +++ b/e2e/router-tab-view/package.json @@ -6,10 +6,10 @@ "nativescript": { "id": "org.nativescript.routertabview", "tns-android": { - "version": "3.4.1-2018-02-06-02" + "version": "next" }, "tns-ios": { - "version": "3.4.1" + "version": "next" } }, "dependencies": { @@ -30,16 +30,15 @@ "zone.js": "^0.8.4" }, "devDependencies": { - "@types/chai": "^4.0.2", - "@types/mocha": "^5.2.5", - "@types/node": "^10.12.12", + "@types/chai": "~4.1.7", + "@types/mocha": "~5.2.5", + "@types/node": "~10.12.18", "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", "lazy": "1.0.11", - "mocha": "~5.1.0", - "mocha-junit-reporter": "~1.17.0", - "mocha-multi": "~1.0.0", + "mocha": "~5.2.0", + "mochawesome": "~3.1.2", "nativescript-dev-appium": "next", "nativescript-dev-typescript": "next", "typescript": "~3.1.1" diff --git a/e2e/router/.gitignore b/e2e/router/.gitignore index 8e824499e..e3eb2283a 100644 --- a/e2e/router/.gitignore +++ b/e2e/router/.gitignore @@ -7,4 +7,5 @@ hooks /**/*.js /**/*.map e2e/reports -test-results.xml \ No newline at end of file +test-results.xml +mochawesome-report \ No newline at end of file diff --git a/e2e/router/e2e/router.e2e-spec.ts b/e2e/router/e2e/router.e2e-spec.ts index ba69f92ef..0b5d1d1e2 100644 --- a/e2e/router/e2e/router.e2e-spec.ts +++ b/e2e/router/e2e/router.e2e-spec.ts @@ -3,53 +3,56 @@ import { UIElement, createDriver, SearchOptions, + nsCapabilities, } from "nativescript-dev-appium"; -describe("Simple navigate and back", () => { +describe("Simple navigate and back", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to Second(1)/master", async () => { + it("should navigate to Second(1)/master", async function(){ await findAndClick(driver, "GO TO SECOND"); await assureSecondComponent(driver, 1); await assureNestedMasterComponent(driver); }); - it("should navigate back to First", async () => { + it("should navigate back to First", async function(){ await goBack(driver); await assureFirstComponent(driver); }); }); -describe("Navigate inside nested outlet", () => { +describe("Navigate inside nested outlet", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to Second(1)/master", async () => { + it("should navigate to Second(1)/master", async function(){ await findAndClick(driver, "GO TO SECOND"); await assureSecondComponent(driver, 1) await assureNestedMasterComponent(driver); }); - it("should navigate to Second(1)/detail(1) and back", async () => { + it("should navigate to Second(1)/detail(1) and back", async function(){ const detailBtn = await driver.findElementByAutomationText("DETAIL 1"); detailBtn.click(); await assureSecondComponent(driver, 1) @@ -60,7 +63,7 @@ describe("Navigate inside nested outlet", () => { await assureNestedMasterComponent(driver); }); - it("should navigate to Second(1)/detail(2) and back", async () => { + it("should navigate to Second(1)/detail(2) and back", async function(){ const detailBtn = await driver.findElementByAutomationText("DETAIL 2"); detailBtn.click(); await assureSecondComponent(driver, 1) @@ -71,32 +74,33 @@ describe("Navigate inside nested outlet", () => { await assureNestedMasterComponent(driver); }); - it("should navigate back to First", async () => { + it("should navigate back to First", async function(){ await goBack(driver); await assureFirstComponent(driver); }); }); -describe("Navigate to same component with different param", () => { +describe("Navigate to same component with different param", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to Second(1)/master", async () => { + it("should navigate to Second(1)/master", async function(){ await findAndClick(driver, "GO TO SECOND"); await assureSecondComponent(driver, 1) await assureNestedMasterComponent(driver); }); - it("should navigate to Second(2)/master", async () => { + it("should navigate to Second(2)/master", async function(){ const navigationButton = await driver.findElementByAutomationText("GO TO NEXT SECOND"); navigationButton.click(); @@ -105,39 +109,40 @@ describe("Navigate to same component with different param", () => { await assureNestedMasterComponent(driver); }); - it("should navigate back to Second(1)/master", async () => { + it("should navigate back to Second(1)/master", async function(){ await goBack(driver); await assureSecondComponent(driver, 1) await assureNestedMasterComponent(driver); }); - it("should navigate back to First", async () => { + it("should navigate back to First", async function(){ await goBack(driver); await assureFirstComponent(driver); }); }); -describe("Nested navigation + page navigation", () => { +describe("Nested navigation + page navigation", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to Second(1)/master", async () => { + it("should navigate to Second(1)/master", async function(){ await findAndClick(driver, "GO TO SECOND"); await assureSecondComponent(driver, 1) await assureNestedMasterComponent(driver); }); - it("should navigate to Second(1)/detail(1)", async () => { + it("should navigate to Second(1)/detail(1)", async function(){ const detailBtn = await driver.findElementByAutomationText("DETAIL 1"); detailBtn.click(); @@ -145,7 +150,7 @@ describe("Nested navigation + page navigation", () => { await assureNestedDetailComponent(driver, 1); }); - it("should navigate to Second(2)/master", async () => { + it("should navigate to Second(2)/master", async function(){ const navigationButton = await driver.findElementByAutomationText("GO TO NEXT SECOND"); navigationButton.click(); @@ -154,7 +159,7 @@ describe("Nested navigation + page navigation", () => { await assureNestedMasterComponent(driver); }); - it("should navigate to Second(2)/detail(2)", async () => { + it("should navigate to Second(2)/detail(2)", async function(){ const detailBtn = await driver.findElementByAutomationText("DETAIL 2"); detailBtn.click(); @@ -162,13 +167,13 @@ describe("Nested navigation + page navigation", () => { await assureNestedDetailComponent(driver, 2); }); - it("should navigate to First", async () => { + it("should navigate to First", async function(){ await findAndClick(driver, "GO TO FIRST"); await assureFirstComponent(driver); }); - it("should navigate the whole stack", async () => { + it("should navigate the whole stack", async function(){ await goBack(driver); await assureSecondComponent(driver, 2) await assureNestedDetailComponent(driver, 2); @@ -190,31 +195,32 @@ describe("Nested navigation + page navigation", () => { }); }); -describe("Nested name navigation + page navigation", () => { +describe("Nested name navigation + page navigation", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to Second(1)/master", async () => { + it("should navigate to Second(1)/master", async function(){ await findAndClick(driver, "GO TO SECOND"); await assureSecondComponent(driver, 1) await assureNestedMasterComponent(driver); }); - it("should load nested named Master", async () => { + it("should load nested named Master", async function(){ await findAndClick(driver, "LOAD NESTED NAMED OUTLET"); await assureNamedNestedMasterComponent(driver); }); - it("should navigate to nested named Master Detail/1", async () => { + it("should navigate to nested named Master Detail/1", async function(){ const navigationButton = await driver.findElementByAutomationText("DETAIL-NAMED 1"); navigationButton.click(); @@ -222,7 +228,7 @@ describe("Nested name navigation + page navigation", () => { await assureNamedNestedDetailComponent(driver, 1); }); - it("should navigate back to Master and navigate to Detail/2", async () => { + it("should navigate back to Master and navigate to Detail/2", async function(){ let navigationButton = await driver.findElementByAutomationText("BACK-NESTED"); navigationButton.click(); @@ -237,68 +243,71 @@ describe("Nested name navigation + page navigation", () => { }); }); -describe("Shouldn't be able to navigate back on startup", () => { +describe("Shouldn't be able to navigate back on startup", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("shouldn't be able to go back", async () => { + it("shouldn't be able to go back", async function(){ await goBack(driver); await driver.findElementByAutomationText("canGoBack() - false"); }); }); -describe("Shouldn't be able to navigate back after cleared history", () => { +describe("Shouldn't be able to navigate back after cleared history", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to Second(1)/master", async () => { + it("should navigate to Second(1)/master", async function(){ await findAndClick(driver, "GO TO SECOND"); await assureSecondComponent(driver, 1) await assureNestedMasterComponent(driver); }); - it("should navigate to Second(1)/master", async () => { + it("should navigate to Second(1)/master", async function(){ await findAndClick(driver, "GO TO FIRST(CLEAR)"); await assureFirstComponent(driver); }); - it("shouldn't be able to go back", async () => { + it("shouldn't be able to go back", async function(){ await goBack(driver); await driver.findElementByAutomationText("canGoBack() - false"); }); }); -describe("Navigate to componentless route", () => { +describe("Navigate to componentless route", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to ComponentlessSecond(100)/detail(200)", async () => { + it("should navigate to ComponentlessSecond(100)/detail(200)", async function(){ const navigationButton = await driver.findElementByAutomationText("GO TO C-LESS SECOND"); navigationButton.click(); @@ -307,13 +316,13 @@ describe("Navigate to componentless route", () => { await assureNestedDetailComponent(driver, 200); }); - it("should navigate to First", async () => { + it("should navigate to First", async function(){ await findAndClick(driver, "GO TO FIRST"); await assureFirstComponent(driver); }); - it("should navigate the whole stack", async () => { + it("should navigate the whole stack", async function(){ await goBack(driver); await assureSecondComponent(driver, 100) await assureNestedDetailComponent(driver, 200); @@ -323,39 +332,40 @@ describe("Navigate to componentless route", () => { }); }); -describe("Navigate to lazy module", () => { +describe("Navigate to lazy module", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to lazy/home", async () => { + it("should navigate to lazy/home", async function(){ await findAndClick(driver, "GO TO LAZY HOME"); await assureLazyComponent(driver); }); - it("should navigate to First", async () => { + it("should navigate to First", async function(){ await findAndClick(driver, "GO TO FIRST"); await assureFirstComponent(driver); }); - it("should navigate back to lazy/home", async () => { + it("should navigate back to lazy/home", async function(){ await goBack(driver); await assureLazyComponent(driver); }); - it("should navigate to First again", async () => { + it("should navigate to First again", async function(){ await findAndClick(driver, "GO TO FIRST"); await assureFirstComponent(driver); }); - it("should navigate the whole stack", async () => { + it("should navigate the whole stack", async function(){ await goBack(driver); await assureLazyComponent(driver); @@ -364,37 +374,38 @@ describe("Navigate to lazy module", () => { }); }); -describe("Navigate to componentless lazy module route", () => { +describe("Navigate to componentless lazy module route", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should navigate to nest/more (componentless lazy route)", async () => { + it("should navigate to nest/more (componentless lazy route)", async function(){ await findAndClick(driver, "GO TO C-LESS LAZY"); await assureComponentlessLazyComponent(driver); }); - it("should navigate to lazy/home", async () => { + it("should navigate to lazy/home", async function(){ await findAndClick(driver, "GO TO LAZY HOME"); await assureLazyComponent(driver); }); - it("should navigate to First", async () => { + it("should navigate to First", async function(){ await findAndClick(driver, "GO TO FIRST"); await assureFirstComponent(driver); }); - it("should navigate the whole stack", async () => { + it("should navigate the whole stack", async function(){ await goBack(driver); await assureLazyComponent(driver); @@ -406,31 +417,32 @@ describe("Navigate to componentless lazy module route", () => { }); }); -describe("Simple navigate and back should trigger only one CD on FirstComponent", () => { +describe("Simple navigate and back should trigger only one CD on FirstComponent", async function(){ let driver: AppiumDriver; - before(async () => { + before(async function(){ + nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.resetApp(); }); - it("should find First", async () => { + it("should find First", async function(){ await assureFirstComponent(driver); }); - it("should reset counter", async () => { + it("should reset counter", async function(){ await findAndClick(driver, "RESET"); await driver.waitForElement("CHECK: 1"); }); - it("should navigate to Second(1)/master", async () => { + it("should navigate to Second(1)/master", async function(){ await findAndClick(driver, "GO TO SECOND"); await assureSecondComponent(driver, 1); await assureNestedMasterComponent(driver); }); - it("should navigate back to First", async () => { + it("should navigate back to First", async function(){ await goBack(driver); await assureFirstComponent(driver); await driver.waitForElement("CHECK: 2"); diff --git a/e2e/router/e2e/setup.ts b/e2e/router/e2e/setup.ts index 0c0add14a..11b854fca 100644 --- a/e2e/router/e2e/setup.ts +++ b/e2e/router/e2e/setup.ts @@ -1,19 +1,19 @@ -import { startServer, stopServer , createDriver , AppiumDriver } from "nativescript-dev-appium"; +import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium"; +const addContext = require('mochawesome/addContext'); -let driver: AppiumDriver; +const testReporterContext = {}; +testReporterContext.name = "mochawesome"; +testReporterContext.reportDir = "mochawesome-report"; +testReporterContext.log = addContext; +testReporterContext.logImageTypes = [LogImageType.screenshots]; +nsCapabilities.testReporter = testReporterContext; -before("start server", async () => { +before("start server", async function () { + nsCapabilities.testReporter.context = this; await startServer(); - driver = await createDriver(); }); -after("stop server", async () => { - await driver.quit(); +after("stop server", async function () { + nsCapabilities.testReporter.context = this; 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 0f38b6720..b7902b35f 100644 --- a/e2e/router/package.json +++ b/e2e/router/package.json @@ -6,7 +6,7 @@ "nativescript": { "id": "org.nativescript.router", "tns-android": { - "version": "5.1.0" + "version": "next" } }, "dependencies": { @@ -35,15 +35,13 @@ "babel-traverse": "6.25.0", "babel-types": "6.25.0", "babylon": "6.17.4", - "chai": "~4.1.1", "chai-as-promised": "~7.1.1", "colors": "^1.1.2", "lazy": "1.0.11", "mocha": "~5.2.0", - "mocha-junit-reporter": "~1.18.0", - "mocha-multi": "~1.0.1", - "nativescript-dev-appium": "^4.0.10-2019-01-16-01", - "nativescript-dev-typescript": "^0.7.10-next-2019-01-07-183215-03", + "mochawesome": "~3.1.2", + "nativescript-dev-appium": "next", + "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", "tslib": "^1.7.1", "typescript": "^3.1.6" diff --git a/e2e/single-page/.gitignore b/e2e/single-page/.gitignore index 8e824499e..e3eb2283a 100644 --- a/e2e/single-page/.gitignore +++ b/e2e/single-page/.gitignore @@ -7,4 +7,5 @@ hooks /**/*.js /**/*.map e2e/reports -test-results.xml \ No newline at end of file +test-results.xml +mochawesome-report \ No newline at end of file diff --git a/e2e/single-page/e2e/setup.ts b/e2e/single-page/e2e/setup.ts index 0c0add14a..11b854fca 100644 --- a/e2e/single-page/e2e/setup.ts +++ b/e2e/single-page/e2e/setup.ts @@ -1,19 +1,19 @@ -import { startServer, stopServer , createDriver , AppiumDriver } from "nativescript-dev-appium"; +import { startServer, stopServer, ITestReporter, nsCapabilities, LogImageType } from "nativescript-dev-appium"; +const addContext = require('mochawesome/addContext'); -let driver: AppiumDriver; +const testReporterContext = {}; +testReporterContext.name = "mochawesome"; +testReporterContext.reportDir = "mochawesome-report"; +testReporterContext.log = addContext; +testReporterContext.logImageTypes = [LogImageType.screenshots]; +nsCapabilities.testReporter = testReporterContext; -before("start server", async () => { +before("start server", async function () { + nsCapabilities.testReporter.context = this; await startServer(); - driver = await createDriver(); }); -after("stop server", async () => { - await driver.quit(); +after("stop server", async function () { + nsCapabilities.testReporter.context = this; 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/single-page/e2e/tests.e2e-spec.ts b/e2e/single-page/e2e/tests.e2e-spec.ts index 4a9857538..cf06732f1 100644 --- a/e2e/single-page/e2e/tests.e2e-spec.ts +++ b/e2e/single-page/e2e/tests.e2e-spec.ts @@ -1,16 +1,24 @@ import { AppiumDriver, createDriver, + nsCapabilities, } from "nativescript-dev-appium"; -describe("Single page app", () => { +describe("Single page app", async function () { let driver: AppiumDriver; - before(async () => { + before(async function () { + nsCapabilities.testReporter.context = this; driver = await createDriver(); }); - it("should load first page", async () => { + afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logTestArtifacts(this.currentTest.title); + } + }); + + it("should load first page", async function () { await driver.findElementByAutomationText("First Component"); // ActionBar Title and item @@ -18,7 +26,7 @@ describe("Single page app", () => { await driver.findElementByAutomationText("ACTION1"); }); - it("should load second(1) page", async () => { + it("should load second(1) page", async function () { await findAndClick(driver, "SECOND(1)"); await driver.findElementByAutomationText("Second Component: 1"); @@ -28,7 +36,7 @@ describe("Single page app", () => { await driver.findElementByAutomationText("ACTION2"); }); - it("should load second(2) page", async () => { + it("should load second(2) page", async function () { await findAndClick(driver, "SECOND(2)"); await driver.findElementByAutomationText("Second Component: 2"); @@ -39,7 +47,7 @@ describe("Single page app", () => { await driver.findElementByAutomationText("ADD"); }); - it("should open and close modal view", async () => { + it("should open and close modal view", async function () { await findAndClick(driver, "Show Modal"); await driver.findElementByAutomationText("Welcome to modal"); @@ -48,7 +56,7 @@ describe("Single page app", () => { await driver.findElementByAutomationText("Second Component: 2"); }); - it("should go back to second(1) and first", async () => { + it("should go back to second(1) and first", async function () { await findAndClick(driver, "Back"); await driver.findElementByAutomationText("Second Component: 1"); await findAndClick(driver, "Back"); diff --git a/e2e/single-page/package.json b/e2e/single-page/package.json index c324e1e25..812376667 100644 --- a/e2e/single-page/package.json +++ b/e2e/single-page/package.json @@ -6,10 +6,10 @@ "nativescript": { "id": "org.nativescript.singlepage", "tns-ios": { - "version": "4.0.1-2018-03-26-01" + "version": "next" }, "tns-android": { - "version": "4.0.0-2018.3.22.2" + "version": "next" } }, "dependencies": { @@ -32,19 +32,17 @@ "devDependencies": { "@angular/compiler-cli": "~7.2.0", "@ngtools/webpack": "~7.2.0", - "@types/chai": "^4.0.2", - "@types/mocha": "^5.2.5", - "@types/node": "^10.12.12", + "@types/chai": "~4.1.7", + "@types/mocha": "~5.2.5", + "@types/node": "~10.12.18", "babel-traverse": "6.25.0", "babel-types": "6.25.0", "babylon": "6.17.4", - "chai": "~4.1.1", "chai-as-promised": "~7.1.1", "colors": "^1.1.2", "lazy": "1.0.11", - "mocha": "~3.5.0", - "mocha-junit-reporter": "^1.13.0", - "mocha-multi": "^0.11.0", + "mocha": "~5.2.0", + "mochawesome": "~3.1.2", "nativescript-dev-appium": "next", "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", From b37ba3b0b7149aaf8cd57f0ef2fe5b08b8aaa5b6 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Tue, 7 May 2019 22:28:24 +0300 Subject: [PATCH 27/31] tests: replace anonymous functions --- .../e2e/modal.shared.e2e-spec.ts | 3 --- .../e2e/tab-view.e2e-spec.ts | 12 ++++++------ e2e/renderer/e2e/action-bar.e2e-spec.ts | 18 +++++++++--------- e2e/renderer/e2e/ngfor.e2e-spec.ts | 8 ++++---- e2e/renderer/e2e/ngforof.e2e-spec.ts | 8 ++++---- e2e/renderer/e2e/ngif.e2e-spec.ts | 6 +++--- 6 files changed, 26 insertions(+), 29 deletions(-) diff --git a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts index 0bfbb6501..97170ff88 100644 --- a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts +++ b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts @@ -24,9 +24,6 @@ describe("modal-shared:", async function () { await screen[root](); }); - beforeEach(async function () { - }); - afterEach(async function () { if (this.currentTest.state === "failed") { await driver.logTestArtifacts(this.currentTest.title); diff --git a/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts index d40cc07dd..60d74c835 100644 --- a/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts +++ b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts @@ -11,13 +11,13 @@ describe("tab-view:", async function () { let driver: AppiumDriver; let screen: Screen; - before(async () => { + before(async function () { nsCapabilities.testReporter.context = this; driver = await createDriver(); screen = new Screen(driver); }); - after(async () => { + after(async function () { await driver.quit(); console.log("Quit driver!"); }); @@ -28,14 +28,14 @@ describe("tab-view:", async function () { } }); - it("loaded home component and lists", async () => { + it("loaded home component and lists", async function () { await screen.navigateToHomePage(); await screen.loadedHome(); await screen.loadedPlayersList(); await screen.loadedTeamList(); }); - it("loaded tabs component, Players List and Teams List pages", async () => { + it("loaded tabs component, Players List and Teams List pages", async function () { await screen.navigateToTabsPage(); await screen.loadedTabs(); await screen.loadedPlayersList(); @@ -45,7 +45,7 @@ describe("tab-view:", async function () { await screen.loadedPlayersList(); }); - it("should navigate Player One/Team One then back separately", async () => { + it("should navigate Player One/Team One then back separately", async function () { await testPlayerNavigated(screen, screen.playerOne); await gotoTeamsTab(driver); await testTeamNavigated(screen, screen.teamOne); @@ -56,7 +56,7 @@ describe("tab-view:", async function () { await screen.loadedPlayersList(); }); - it("should navigate Player One/Team One then next Player/Team then back", async () => { + it("should navigate Player One/Team One then next Player/Team then back", async function () { await testPlayerNavigated(screen, screen.playerOne); await testPlayerNextNavigated(screen, screen.playerTwo); await gotoTeamsTab(driver); diff --git a/e2e/renderer/e2e/action-bar.e2e-spec.ts b/e2e/renderer/e2e/action-bar.e2e-spec.ts index 7b62008ff..b03dad5da 100644 --- a/e2e/renderer/e2e/action-bar.e2e-spec.ts +++ b/e2e/renderer/e2e/action-bar.e2e-spec.ts @@ -18,7 +18,7 @@ describe("Action Bar scenario", async function () { let toggleFirstButton: UIElement; let toggleSecondButton: UIElement; - before(async () => { + before(async function () { nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); @@ -52,7 +52,7 @@ describe("Action Bar scenario", async function () { }); it("should detach first element when its condition is false", done => { - (async () => { + (async function () { await toggleFirst(); try { @@ -69,7 +69,7 @@ describe("Action Bar scenario", async function () { }); it("should detach second element when its condition is false", done => { - (async () => { + (async function () { await toggleSecond(); try { @@ -95,15 +95,15 @@ describe("Action Bar scenario", async function () { await checkOrderIsCorrect(); }); - const checkOrderIsCorrect = async () => { + const checkOrderIsCorrect = async function () { await isOnTheLeft(firstActionItem, secondActionItem); }; - const toggleFirst = async () => { + const toggleFirst = async function () { await toggleFirstButton.click(); }; - const toggleSecond = async () => { + const toggleSecond = async function () { await toggleSecondButton.click(); }; @@ -113,7 +113,7 @@ describe("Action Bar scenario", async function () { let toggleButton: UIElement; let conditional: UIElement; - before(async () => { + before(async function () { nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); @@ -147,11 +147,11 @@ describe("Action Bar scenario", async function () { await checkOrderIsCorrect(); }); - const checkOrderIsCorrect = async () => { + const checkOrderIsCorrect = async function () { await isOnTheLeft(toggleButton, conditional); }; - const toggle = async () => { + const toggle = async function () { await toggleButton.click(); }; }); diff --git a/e2e/renderer/e2e/ngfor.e2e-spec.ts b/e2e/renderer/e2e/ngfor.e2e-spec.ts index cca35b8c1..1b9fb1331 100644 --- a/e2e/renderer/e2e/ngfor.e2e-spec.ts +++ b/e2e/renderer/e2e/ngfor.e2e-spec.ts @@ -15,7 +15,7 @@ describe("ngFor scenario", async function () { let elements: UIElement[] = []; let lastAddedElementId = 0; - before(async () => { + before(async function () { nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); @@ -79,7 +79,7 @@ describe("ngFor scenario", async function () { await checkCorrectOrderAll(); }); - const addElement = async () => { + const addElement = async function () { await addButton.click(); lastAddedElementId += 1; @@ -103,7 +103,7 @@ describe("ngFor scenario", async function () { lastAddedElementId -= 1; }; - const checkAppendedCorrectly = async () => { + const checkAppendedCorrectly = async function () { const lastAdded = await driver.findElementByAutomationText( lastAddedElementId.toString()); @@ -112,7 +112,7 @@ describe("ngFor scenario", async function () { await isAbove(addButton, removeButton); }; - const checkCorrectOrderAll = async () => { + const checkCorrectOrderAll = async function () { for (let i = 0; i < elements.length - 1; i += 1) { await isAbove(elements[i], elements[i + 1]); } diff --git a/e2e/renderer/e2e/ngforof.e2e-spec.ts b/e2e/renderer/e2e/ngforof.e2e-spec.ts index e14e079f4..5b272ff9d 100644 --- a/e2e/renderer/e2e/ngforof.e2e-spec.ts +++ b/e2e/renderer/e2e/ngforof.e2e-spec.ts @@ -21,7 +21,7 @@ describe("ngForOf scenario", function () { let elements: ElementTuple[] = []; let lastAddedElementId = 0; - before(async () => { + before(async function () { nsCapabilities.testReporter.context = this; driver = await createDriver(); await driver.driver.resetApp(); @@ -88,7 +88,7 @@ describe("ngForOf scenario", function () { }); - const addElement = async () => { + const addElement = async function () { await addButton.click(); lastAddedElementId += 1; @@ -114,7 +114,7 @@ describe("ngForOf scenario", function () { lastAddedElementId -= 1; }; - const checkAppendedCorrectly = async () => { + const checkAppendedCorrectly = async function () { const lastAdded = await getElement(lastAddedElementId); await elementIsAbove(elements.slice(-2)[0], lastAdded); @@ -122,7 +122,7 @@ describe("ngForOf scenario", function () { await isAbove(addButton, removeButton); }; - const checkCorrectOrderAll = async () => { + const checkCorrectOrderAll = async function () { for (let i = 0; i < elements.length - 1; i += 1) { await elementIsAbove(elements[i], elements[i + 1]); } diff --git a/e2e/renderer/e2e/ngif.e2e-spec.ts b/e2e/renderer/e2e/ngif.e2e-spec.ts index c81c37a26..128dc2b74 100644 --- a/e2e/renderer/e2e/ngif.e2e-spec.ts +++ b/e2e/renderer/e2e/ngif.e2e-spec.ts @@ -10,7 +10,7 @@ import { isAbove } from "./helpers/location"; import { assert } from "chai"; -describe("ngIf scenario", () => { +describe("ngIf scenario", async function () { let driver: AppiumDriver; let toggleButton: UIElement; @@ -376,7 +376,7 @@ describe("ngIf scenario", () => { driver.findElementByAutomationText("first", 500) .then(_ => { throw new Error("first label found!"); }) - .catch(() => { + .catch(async function () { driver.findElementByAutomationText("second", 500) .then(_ => { throw new Error("second label found!"); }) .catch(() => done()); @@ -402,7 +402,7 @@ describe("ngIf scenario", () => { driver.findElementByAutomationText("first", 500) .then(_ => { throw new Error("first label found!"); }) - .catch(() => { + .catch(async function () { driver.findElementByAutomationText("second", 500) .then(_ => { throw new Error("second label found!"); }) .catch(() => done()); From b6dbe574ed6049ad97161e6503f66f59f4979ccb Mon Sep 17 00:00:00 2001 From: Alexander Vakrilov Date: Wed, 8 May 2019 16:40:03 +0300 Subject: [PATCH 28/31] fix: mark reattached view for CD (#1803) * fix: mark reattached view for CD * chore: fix AOT build error --- e2e/router/app/counter.service.ts | 13 ++++++ e2e/router/app/first/first.component.ts | 15 ++++--- e2e/router/app/second/second.component.ts | 11 ++++- e2e/router/e2e/router.e2e-spec.ts | 40 +++++++++++++++++++ e2e/router/package.json | 2 +- .../router/page-router-outlet.ts | 1 + 6 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 e2e/router/app/counter.service.ts diff --git a/e2e/router/app/counter.service.ts b/e2e/router/app/counter.service.ts new file mode 100644 index 000000000..15fe455c1 --- /dev/null +++ b/e2e/router/app/counter.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +@Injectable({ + providedIn: "root" +}) +export class CounterService { + counter$ = new BehaviorSubject(0); + + tick() { + this.counter$.next(this.counter$.value + 1); + } +} \ No newline at end of file diff --git a/e2e/router/app/first/first.component.ts b/e2e/router/app/first/first.component.ts index 5d93d97b7..f76f54f91 100644 --- a/e2e/router/app/first/first.component.ts +++ b/e2e/router/app/first/first.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit, OnDestroy, OnChanges, DoCheck } from "@angular/core"; -import { ActivatedRoute, Router, Route } from "@angular/router"; -import { Location } from "@angular/common"; import { RouterExtensions } from "nativescript-angular/router"; - import { Page } from "tns-core-modules/ui/page"; +import { CounterService } from "../counter.service"; + @Component({ selector: "first", template: ` @@ -21,12 +20,18 @@ import { Page } from "tns-core-modules/ui/page"; + ` }) export class FirstComponent implements OnInit, OnDestroy, DoCheck { public message: string = ""; public doCheckCount: number = 0; - constructor(private routerExt: RouterExtensions, page: Page) { + + constructor( + private routerExt: RouterExtensions, + public service: CounterService, + page: Page) { + console.log("FirstComponent - constructor() page: " + page); } @@ -42,7 +47,7 @@ export class FirstComponent implements OnInit, OnDestroy, DoCheck { this.doCheckCount++; console.log("FirstComponent - ngDoCheck(): " + this.doCheckCount); } - + reset() { this.doCheckCount = 0; } diff --git a/e2e/router/app/second/second.component.ts b/e2e/router/app/second/second.component.ts index be9baae96..6b4c6ce7f 100644 --- a/e2e/router/app/second/second.component.ts +++ b/e2e/router/app/second/second.component.ts @@ -1,10 +1,11 @@ import { Component, OnInit, OnDestroy } from "@angular/core"; -import { ActivatedRoute, Router, Route } from "@angular/router"; +import { ActivatedRoute } from "@angular/router"; import { RouterExtensions } from "nativescript-angular/router"; import { Page } from "tns-core-modules/ui/page"; import { Observable } from "rxjs"; import { map } from "rxjs/operators"; +import { CounterService } from "../counter.service"; @Component({ selector: "second", @@ -19,6 +20,8 @@ import { map } from "rxjs/operators"; + + @@ -33,7 +36,11 @@ export class SecondComponent implements OnInit, OnDestroy { public depth$: Observable; public nextDepth$: Observable; - constructor(private routerExt: RouterExtensions, private route: ActivatedRoute, page: Page) { + constructor( + private routerExt: RouterExtensions, + private route: ActivatedRoute, + public service: CounterService, + page: Page) { console.log("SecondComponent - constructor() page: " + page); this.depth$ = route.params.pipe(map(r => r["depth"])); this.nextDepth$ = route.params.pipe(map(r => +r["depth"] + 1)); diff --git a/e2e/router/e2e/router.e2e-spec.ts b/e2e/router/e2e/router.e2e-spec.ts index 0b5d1d1e2..b71926499 100644 --- a/e2e/router/e2e/router.e2e-spec.ts +++ b/e2e/router/e2e/router.e2e-spec.ts @@ -449,6 +449,46 @@ describe("Simple navigate and back should trigger only one CD on FirstComponent" }); }); +describe("Simple navigate and back should trigger only one CD on FirstComponent even with 3 changes in service", () => { + let driver: AppiumDriver; + + before(async () => { + driver = await createDriver(); + await driver.resetApp(); + }); + + it("should find First", async () => { + await assureFirstComponent(driver); + }); + + it("should reset counter", async () => { + await findAndClick(driver, "RESET"); + await driver.waitForElement("CHECK: 1"); + await driver.waitForElement("COUNTER: 0"); + }); + + it("should navigate to Second(1)/master", async () => { + await findAndClick(driver, "GO TO SECOND"); + + await assureSecondComponent(driver, 1); + await assureNestedMasterComponent(driver); + }); + + it("should increase counter", async () => { + await findAndClick(driver, "TICK"); + await findAndClick(driver, "TICK"); + await findAndClick(driver, "TICK"); + }); + + it("should navigate back to First", async () => { + await goBack(driver); + await assureFirstComponent(driver); + await driver.waitForElement("CHECK: 2"); + await driver.waitForElement("COUNTER: 3"); + }); +}); + + async function assureFirstComponent(driver: AppiumDriver) { await driver.findElementByAutomationText("FirstComponent"); } diff --git a/e2e/router/package.json b/e2e/router/package.json index b7902b35f..1e7340917 100644 --- a/e2e/router/package.json +++ b/e2e/router/package.json @@ -44,7 +44,7 @@ "nativescript-dev-typescript": "next", "nativescript-dev-webpack": "next", "tslib": "^1.7.1", - "typescript": "^3.1.6" + "typescript": "~3.1.1" }, "scripts": { "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json", diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index 63f9ea4a8..7c018487b 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -262,6 +262,7 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire this.activated = ref; // reattach to ChangeDetection + this.activated.hostView.markForCheck(); this.activated.hostView.reattach(); this._activatedRoute = activatedRoute; this.markActivatedRoute(activatedRoute); From 59a1cdee55fa56bd06e80fec7cfd54cb9033d924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Sj=C3=B8gren?= Date: Fri, 10 May 2019 08:14:07 +0200 Subject: [PATCH 29/31] Fix/ns views not cleaned on removal (#1740) * fix(TemplatedItemsComponent): remove templates in ngOnDestroy() * fix(PageRouterOutlet): Destroy the activated componentRef on ngOnDestroy() * fix(NativeScriptRendererFactory): Empty the rootNgView when NativeScriptRendererFactory is destroyed * fix(ViewUtil): ViewUtil.removeChild() doesn't remove the child recursively * fix(NSLocationStrategy): remove outlets in ngOnDestroy() Make sure outlets and currentOutlet are removed when the NSLocationStrategy is destroyed. * fix: PageRouterOutlet.ngOnDestroy logged as NSLocationStrategy.ngOnDestroy * fix(ViewUtil): Removing FormattedString throw When removing the children from a Label and one of these children is a FormattedString, the FormattedTextProperty throw this exception: "TypeError: Cannot read property 'textTransform' of undefined" * fix: use ViewUtils to add Component to Page Without this the Page and componentView doesn't get the proper relation for then the page is destroyed. * fix: Remove all the children from the AppHostView * fix: ViewUtil.removeFromVisualTree logged under wrong name * fix(PageRouterOutlet): Don't remove grandchild views when moving component PageRouterOutlet.loadComponentInPage moves the componentView from its native parent, to the Page. Don't remove the componentView's children when it is removed from the native parent. * fix: ViewUtils tried to remote Span elements from Labels Just like with FormattedString this throws an exception * chor: skip detached elements for removeFromVisualTree --- .../directives/templated-items-comp.ts | 5 +++ nativescript-angular/renderer.ts | 12 +++++- .../router/ns-location-strategy.ts | 9 +++++ .../router/page-router-outlet.ts | 15 +++++-- nativescript-angular/view-util.ts | 40 ++++++++++++++----- 5 files changed, 67 insertions(+), 14 deletions(-) diff --git a/nativescript-angular/directives/templated-items-comp.ts b/nativescript-angular/directives/templated-items-comp.ts index f3644f071..a70746796 100644 --- a/nativescript-angular/directives/templated-items-comp.ts +++ b/nativescript-angular/directives/templated-items-comp.ts @@ -100,6 +100,11 @@ export abstract class TemplatedItemsComponent implements DoCheck, OnDestroy, Aft ngOnDestroy() { this.templatedItemsView.off("itemLoading", this.onItemLoading, this); + this.templatedItemsView = null; + + if (this._templateMap) { + this._templateMap.clear(); + } } private setItemTemplates() { diff --git a/nativescript-angular/renderer.ts b/nativescript-angular/renderer.ts index ea6998cde..450c27f5d 100644 --- a/nativescript-angular/renderer.ts +++ b/nativescript-angular/renderer.ts @@ -73,6 +73,16 @@ export class NativeScriptRendererFactory implements RendererFactory2 { this.componentRenderers.set(type.id, renderer); return renderer; } + + ngOnDestroy(): void { + if (isLogEnabled()) { + traceLog(`NativeScriptRendererFactory.ngOnDestroy()`); + } + + while (this.rootNgView && this.rootNgView.firstChild) { + this.viewUtil.removeChild(this.rootNgView, this.rootNgView.firstChild); + } + } } export class NativeScriptRenderer extends Renderer2 { @@ -129,7 +139,7 @@ export class NativeScriptRenderer extends Renderer2 { @profile parentNode(node: NgView): any { if (isLogEnabled()) { - traceLog(`NativeScriptRenderer.parentNode for node: ${node}`); + traceLog(`NativeScriptRenderer.parentNode for node: ${node} is ${node.parentNode}`); } return node.parentNode; } diff --git a/nativescript-angular/router/ns-location-strategy.ts b/nativescript-angular/router/ns-location-strategy.ts index 6eab73c06..58d4962d2 100644 --- a/nativescript-angular/router/ns-location-strategy.ts +++ b/nativescript-angular/router/ns-location-strategy.ts @@ -744,4 +744,13 @@ export class NSLocationStrategy extends LocationStrategy { private getOutletKey(path: string, outletName: string): string { return path ? path + "-" + outletName : outletName; } + + ngOnDestroy() { + if (isLogEnabled()) { + routerLog("NSLocationStrategy.ngOnDestroy()"); + } + + this.outlets = []; + this.currentOutlet = null; + } } diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index 7c018487b..fb9be5320 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -198,7 +198,16 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire }); this.locationStrategy.clearOutlet(this.frame); } else { - log("NSLocationStrategy.ngOnDestroy: no outlet available for page-router-outlet"); + log("PageRouterOutlet.ngOnDestroy: no outlet available for page-router-outlet"); + } + + if (this.isActivated) { + const c = this.activated.instance; + this.activated.hostView.detach(); + destroyComponentRef(this.activated); + + this.deactivateEvents.emit(c); + this.activated = null; } } @@ -349,9 +358,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire // Component loaded. Find its root native view. const componentView = componentRef.location.nativeElement; // Remove it from original native parent. - this.viewUtil.removeChild(componentView.parent, componentView); + this.viewUtil.removeChild(componentView.parent, componentView, false); // Add it to the new page - page.content = componentView; + this.viewUtil.insertChild(page, componentView); const navigatedFromCallback = (global).Zone.current.wrap((args: NavigatedData) => { if (args.isBackNavigation) { diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index 5a3dea4c8..6a8fbcb5b 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -157,9 +157,10 @@ export class ViewUtil { return next; } - public removeChild(parent: View, child: View) { + public removeChild(parent: View, child: View, removeGrandchildren = true) { if (isLogEnabled()) { - traceLog(`ViewUtil.removeChild parent: ${parent} child: ${child}`); + traceLog(`ViewUtil.removeChild parent: ${parent} child: ${child} ` + + `remove grandchildren: ${removeGrandchildren}`); } if (!parent) { @@ -169,8 +170,21 @@ export class ViewUtil { const extendedParent = this.ensureNgViewExtensions(parent); const extendedChild = this.ensureNgViewExtensions(child); + // Remove the child's children and their children + // Unless called from PageRouterOutlet when the child is moved from once parent to another. + while (extendedChild && extendedChild.firstChild && removeGrandchildren) { + const grandchild = extendedChild.firstChild; + if (isLogEnabled()) { + traceLog(`ViewUtil.removeChild parent: ${parent} child: ${extendedChild} grandchild: ${grandchild}`); + } + + this.removeChild(extendedChild, grandchild); + } + this.removeFromQueue(extendedParent, extendedChild); - this.removeFromVisualTree(extendedParent, extendedChild); + if (!isDetachedElement(extendedChild)) { + this.removeFromVisualTree(extendedParent, extendedChild); + } } private removeFromQueue(parent: NgView, child: NgView) { @@ -181,6 +195,7 @@ export class ViewUtil { if (parent.firstChild === child && parent.lastChild === child) { parent.firstChild = null; parent.lastChild = null; + child.nextSibling = null; return; } @@ -196,6 +211,8 @@ export class ViewUtil { if (previous) { previous.nextSibling = child.nextSibling; } + + child.nextSibling = null; } // NOTE: This one is O(n) - use carefully @@ -240,7 +257,7 @@ export class ViewUtil { private removeFromVisualTree(parent: NgView, child: NgView) { if (isLogEnabled()) { - traceLog(`ViewUtil.findPreviousElement parent: ${parent} child: ${child}`); + traceLog(`ViewUtil.removeFromVisualTree parent: ${parent} child: ${child}`); } if (parent.meta && parent.meta.removeChild) { @@ -249,8 +266,6 @@ export class ViewUtil { this.removeLayoutChild(parent, child); } else if (isContentView(parent) && parent.content === child) { parent.content = null; - parent.lastChild = null; - parent.firstChild = null; } else if (isView(parent)) { parent._removeView(child); } @@ -273,14 +288,15 @@ export class ViewUtil { } public createView(name: string): NgView { - if (isLogEnabled()) { - traceLog(`Creating view: ${name}`); - } - + const originalName = name; if (!isKnownView(name)) { name = "ProxyViewContainer"; } + if (isLogEnabled()) { + traceLog(`Creating view: ${originalName} ${name}`); + } + const viewClass = getViewClass(name); const view = new viewClass(); const ngView = this.setNgViewExtensions(view, name); @@ -300,6 +316,10 @@ export class ViewUtil { } private setNgViewExtensions(view: View, name: string): NgView { + if (isLogEnabled()) { + traceLog(`Make into a NgView view: ${view} name: "${name}"`); + } + const ngView = view as NgView; ngView.nodeName = name; ngView.meta = getViewMeta(name); From b6c1dc55a9d9fb32f818954af1eba8b8f1e5f5d5 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Sun, 12 May 2019 13:27:56 +0300 Subject: [PATCH 30/31] test: wrap buttons on second page --- e2e/router/app/second/second.component.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/e2e/router/app/second/second.component.ts b/e2e/router/app/second/second.component.ts index 6b4c6ce7f..fbdaeaee5 100644 --- a/e2e/router/app/second/second.component.ts +++ b/e2e/router/app/second/second.component.ts @@ -14,13 +14,14 @@ import { CounterService } from "../counter.service"; - - - - - - - + + + + + + + + From dce510865e4b307181eee4036c6df4f5e69e4a2f Mon Sep 17 00:00:00 2001 From: Alexander Vakrilov Date: Mon, 13 May 2019 16:52:18 +0300 Subject: [PATCH 31/31] fix: revert recursie remove -causing crashes (#1819) --- .../router/page-router-outlet.ts | 2 +- nativescript-angular/view-util.ts | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index fb9be5320..34ba4446f 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -358,7 +358,7 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire // Component loaded. Find its root native view. const componentView = componentRef.location.nativeElement; // Remove it from original native parent. - this.viewUtil.removeChild(componentView.parent, componentView, false); + this.viewUtil.removeChild(componentView.parent, componentView); // Add it to the new page this.viewUtil.insertChild(page, componentView); diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index 6a8fbcb5b..faeb62329 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -157,10 +157,9 @@ export class ViewUtil { return next; } - public removeChild(parent: View, child: View, removeGrandchildren = true) { + public removeChild(parent: View, child: View) { if (isLogEnabled()) { - traceLog(`ViewUtil.removeChild parent: ${parent} child: ${child} ` - + `remove grandchildren: ${removeGrandchildren}`); + traceLog(`ViewUtil.removeChild parent: ${parent} child: ${child}`); } if (!parent) { @@ -170,17 +169,6 @@ export class ViewUtil { const extendedParent = this.ensureNgViewExtensions(parent); const extendedChild = this.ensureNgViewExtensions(child); - // Remove the child's children and their children - // Unless called from PageRouterOutlet when the child is moved from once parent to another. - while (extendedChild && extendedChild.firstChild && removeGrandchildren) { - const grandchild = extendedChild.firstChild; - if (isLogEnabled()) { - traceLog(`ViewUtil.removeChild parent: ${parent} child: ${extendedChild} grandchild: ${grandchild}`); - } - - this.removeChild(extendedChild, grandchild); - } - this.removeFromQueue(extendedParent, extendedChild); if (!isDetachedElement(extendedChild)) { this.removeFromVisualTree(extendedParent, extendedChild);