From 445de822a2bf47fd5863c9b7ed92113e80263998 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 4 Sep 2017 19:16:44 +0300 Subject: [PATCH 1/9] refactor(renderer): invoke removeFromQueue for every element --- nativescript-angular/view-util.ts | 78 +++++++++++++++++++------------ 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index 5532a39dc..22b9fe566 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -79,6 +79,9 @@ export class ViewUtil { previous: NgView, next: NgView ): void { + traceLog(`ViewUtil.addToQueue parent: ${parent}, view: ${child}, ` + + `previous: ${previous}, next: ${next}`); + if (previous) { previous.nextSibling = child; } else { @@ -94,6 +97,7 @@ export class ViewUtil { private appendToQueue(parent: NgView, view: NgView) { traceLog(`ViewUtil.appendToQueue parent: ${parent} view: ${view}`); + if (parent.lastChild) { parent.lastChild.nextSibling = view; } @@ -102,6 +106,8 @@ export class ViewUtil { } private addToVisualTree(parent: NgView, child: NgView, next: NgView): void { + traceLog(`ViewUtil.addToVisualTreee parent: ${parent}, view: ${child}, next: ${next}`); + if (parent.meta && parent.meta.insertChild) { parent.meta.insertChild(parent, child); } else if (isLayout(parent)) { @@ -141,36 +147,18 @@ export class ViewUtil { } public removeChild(parent: NgView, child: NgView) { - if (!parent) { - return; - } - - if (parent.meta && parent.meta.removeChild) { - parent.meta.removeChild(parent, child); - } else if (isLayout(parent)) { - 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); - } - } + traceLog(`ViewUtil.removeChild parent: ${parent} child: ${child}`); - private removeLayoutChild(parent: NgLayoutBase, child: NgView): void { - const index = parent.getChildIndex(child); - this.removeFromQueue(parent, child, index); - if (index === -1) { + if (!parent) { return; } - parent.removeChild(child); + this.removeFromQueue(parent, child); + this.removeFromVisualTree(parent, child); } - private removeFromQueue(parent: NgLayoutBase, child: NgView, index: number) { - traceLog(`ViewUtil.removeFromQueue ` + - `parent: ${parent} child: ${child} index: ${index}`); + private removeFromQueue(parent: NgView, child: NgView) { + traceLog(`ViewUtil.removeFromQueue parent: ${parent} child: ${child}`); if (parent.firstChild === child && parent.lastChild === child) { parent.firstChild = null; @@ -182,7 +170,7 @@ export class ViewUtil { parent.firstChild = child.nextSibling; } - const previous = this.findPreviousElement(parent, child, index); + const previous = this.findPreviousElement(parent, child); if (parent.lastChild === child) { parent.lastChild = previous; } @@ -193,8 +181,14 @@ export class ViewUtil { } // NOTE: This one is O(n) - use carefully - private findPreviousElement(parent: NgLayoutBase, child: NgView, elementIndex: number): NgView { - const previousVisual = this.getPreviousVisualElement(parent, elementIndex); + private findPreviousElement(parent: NgView, child: NgView): NgView { + traceLog(`ViewUtil.findPreviousElement parent: ${parent} child: ${child}`); + + let previousVisual; + if (isLayout(parent)) { + previousVisual = this.getPreviousVisualElement(parent, child); + } + let previous = previousVisual || parent.firstChild; // since detached elements are not added to the visual tree, @@ -207,14 +201,16 @@ export class ViewUtil { return previous; } - private getPreviousVisualElement(parent: NgLayoutBase, elementIndex: number): NgView { + private getPreviousVisualElement(parent: NgLayoutBase, child: NgView): NgView { + const elementIndex = parent.getChildIndex(child); + if (elementIndex > 0) { return parent.getChildAt(elementIndex - 1) as NgView; } } // NOTE: This one is O(n) - use carefully - public getChildIndex(parent: any, child: NgView) { + private getChildIndex(parent: any, child: NgView) { if (isLayout(parent)) { return parent.getChildIndex(child); } else if (isContentView(parent)) { @@ -222,6 +218,30 @@ export class ViewUtil { } } + private removeFromVisualTree(parent: NgView, child: NgView) { + traceLog(`ViewUtil.findPreviousElement parent: ${parent} child: ${child}`); + + if (parent.meta && parent.meta.removeChild) { + parent.meta.removeChild(parent, child); + } else if (isLayout(parent)) { + 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); + } + } + + private removeLayoutChild(parent: NgLayoutBase, child: NgView): void { + const index = parent.getChildIndex(child); + + if (index !== -1) { + parent.removeChild(child); + } + } + public createComment(): InvisibleNode { return new CommentNode(); } From 1cfd272a0df78a2073363952f26c4eccb86fe26d Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 4 Sep 2017 19:34:46 +0300 Subject: [PATCH 2/9] refactor(renderer): make meta methods more generic --- nativescript-angular/element-registry.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/nativescript-angular/element-registry.ts b/nativescript-angular/element-registry.ts index 8c0fad5f1..6773c032f 100644 --- a/nativescript-angular/element-registry.ts +++ b/nativescript-angular/element-registry.ts @@ -72,14 +72,22 @@ const getClassName = instance => instance.constructor.name; export interface ViewClassMeta { skipAddToDom?: boolean; - insertChild?: (parent: NgView, child: NgView) => void; - removeChild?: (parent: NgView, child: NgView) => void; + insertChild?: (parent: any, child: any, previous?: any, next?: any) => void; + removeChild?: (parent: any, child: any) => void; } export function isDetachedElement(element): boolean { return (element && element.meta && element.meta.skipAddToDom); } +export function isView(view: any): view is NgView { + return view instanceof View; +} + +export function isInvisibleNode(view: any): view is InvisibleNode { + return view instanceof InvisibleNode; +} + export type ViewResolver = () => ViewClass; const elementMap = new Map(); From 4f8f00a9923848f9177c4633a98c6afbd50b8345 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 4 Sep 2017 19:35:34 +0300 Subject: [PATCH 3/9] refactor(action-bar): don't cast in action-bar insert/remove methods --- nativescript-angular/directives/action-bar.ts | 63 ++++++++++--------- nativescript-angular/view-util.ts | 8 +-- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/nativescript-angular/directives/action-bar.ts b/nativescript-angular/directives/action-bar.ts index 5158a1532..94566edfb 100644 --- a/nativescript-angular/directives/action-bar.ts +++ b/nativescript-angular/directives/action-bar.ts @@ -5,46 +5,53 @@ import { View } from "tns-core-modules/ui/core/view"; import { isBlank } from "../lang-facade"; import { - InvisibleNode, NgView, ViewClassMeta, + ViewExtensions, + isInvisibleNode, + isView, registerElement, } from "../element-registry"; +export function isActionItem(view: any): view is ActionItem { + return view instanceof ActionItem; +} + +export function isNavigationButton(view: any): view is NavigationButton { + return view instanceof NavigationButton; +} + +type NgActionBar = (ActionBar & ViewExtensions); + const actionBarMeta: ViewClassMeta = { skipAddToDom: true, - insertChild: (parent: NgView, child: NgView) => { - const bar = (parent); - const childView = child; - - if (child instanceof InvisibleNode) { + insertChild: (parent: NgActionBar, child: NgView, previous: NgView) => { + if (isInvisibleNode(child)) { return; - } else if (child instanceof NavigationButton) { - bar.navigationButton = childView; - childView.parent = bar; - } else if (child instanceof ActionItem) { - bar.actionItems.addItem(childView); - childView.parent = bar; - } else if (child instanceof View) { - bar.titleView = childView; + } else if (isNavigationButton(child)) { + parent.navigationButton = child; + child.templateParent = parent; + } else if (isActionItem(child)) { + parent.actionItems.addItem(child); + child.templateParent = parent; + } else if (isView(child)) { + parent.titleView = child; } }, - removeChild: (parent: NgView, child: NgView) => { - const bar = (parent); - const childView = child; - - if (child instanceof InvisibleNode) { + removeChild: (parent: NgActionBar, child: NgView) => { + if (isInvisibleNode(child)) { return; - } else if (child instanceof NavigationButton) { - if (bar.navigationButton === childView) { - bar.navigationButton = null; + } else if (isNavigationButton(child)) { + if (parent.navigationButton === child) { + parent.navigationButton = null; } - childView.parent = null; - } else if (child instanceof ActionItem) { - bar.actionItems.removeItem(childView); - childView.parent = null; - } else if (child instanceof View && bar.titleView && bar.titleView === childView) { - bar.titleView = null; + + child.templateParent = null; + } else if (isActionItem(child)) { + parent.actionItems.removeItem(child); + child.templateParent = null; + } else if (isView(child) && parent.titleView && parent.titleView === child) { + parent.titleView = null; } }, }; diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index 22b9fe566..ff3930b09 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -12,7 +12,9 @@ import { getViewClass, getViewMeta, isDetachedElement, + isInvisibleNode, isKnownView, + isView, } from "./element-registry"; import { platformNames, Device } from "tns-core-modules/platform"; @@ -29,10 +31,6 @@ export type NgContentView = ContentView & ViewExtensions; export type NgPlaceholder = Placeholder & ViewExtensions; export type BeforeAttachAction = (view: View) => void; -export function isView(view: any): view is NgView { - return view instanceof View; -} - export function isLayout(view: any): view is NgLayoutBase { return view instanceof LayoutBase; } @@ -64,7 +62,7 @@ export class ViewUtil { this.addToQueue(parent, child, previous, next); - if (child instanceof InvisibleNode) { + if (isInvisibleNode(child)) { child.templateParent = parent; } From e02a35d133f271a39bc6a60443bdcd7121d045f8 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 4 Sep 2017 20:32:13 +0300 Subject: [PATCH 4/9] refactor(action-bar): insert ActionItems at correct positions ActionBar's insertChild method is now passed a `next` view argument. When the view to insert is an ActionItem, `next` is used to find the correct position to insert the new item. fixes #689 --- nativescript-angular/directives/action-bar.ts | 33 +++++++++++++++++-- nativescript-angular/element-registry.ts | 2 +- nativescript-angular/view-util.ts | 9 ++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/nativescript-angular/directives/action-bar.ts b/nativescript-angular/directives/action-bar.ts index 94566edfb..5eda5e356 100644 --- a/nativescript-angular/directives/action-bar.ts +++ b/nativescript-angular/directives/action-bar.ts @@ -1,5 +1,10 @@ import { Directive, Component, ElementRef, Optional, OnDestroy } from "@angular/core"; -import { ActionItem, ActionBar, NavigationButton } from "tns-core-modules/ui/action-bar"; +import { + ActionBar, + ActionItem, + ActionItems, + NavigationButton, +} from "tns-core-modules/ui/action-bar"; import { Page } from "tns-core-modules/ui/page"; import { View } from "tns-core-modules/ui/core/view"; @@ -25,14 +30,14 @@ type NgActionBar = (ActionBar & ViewExtensions); const actionBarMeta: ViewClassMeta = { skipAddToDom: true, - insertChild: (parent: NgActionBar, child: NgView, previous: NgView) => { + insertChild: (parent: NgActionBar, child: NgView, next: any) => { if (isInvisibleNode(child)) { return; } else if (isNavigationButton(child)) { parent.navigationButton = child; child.templateParent = parent; } else if (isActionItem(child)) { - parent.actionItems.addItem(child); + addActionItem(parent, child, next); child.templateParent = parent; } else if (isView(child)) { parent.titleView = child; @@ -56,6 +61,28 @@ const actionBarMeta: ViewClassMeta = { }, }; +const addActionItem = (bar: NgActionBar, item: ActionItem, next: ActionItem) => { + if (next) { + insertActionItemBefore(bar, item, next); + } else { + appendActionItem(bar, item); + } +}; + +const insertActionItemBefore = (bar: NgActionBar, item: ActionItem, next: ActionItem) => { + const actionItems: ActionItems = bar.actionItems; + const actionItemsCollection: ActionItem[] = actionItems.getItems(); + + const indexToInsert = actionItemsCollection.indexOf(next); + actionItemsCollection.splice(indexToInsert, 0, item); + + (actionItems).setItems(actionItemsCollection); +}; + +const appendActionItem = (bar: NgActionBar, item: ActionItem) => { + bar.actionItems.addItem(item); +}; + registerElement("ActionBar", () => require("ui/action-bar").ActionBar, actionBarMeta); registerElement("ActionItem", () => require("ui/action-bar").ActionItem); registerElement("NavigationButton", () => require("ui/action-bar").NavigationButton); diff --git a/nativescript-angular/element-registry.ts b/nativescript-angular/element-registry.ts index 6773c032f..35f111cf8 100644 --- a/nativescript-angular/element-registry.ts +++ b/nativescript-angular/element-registry.ts @@ -72,7 +72,7 @@ const getClassName = instance => instance.constructor.name; export interface ViewClassMeta { skipAddToDom?: boolean; - insertChild?: (parent: any, child: any, previous?: any, next?: any) => void; + insertChild?: (parent: any, child: any, next?: any) => void; removeChild?: (parent: any, child: any) => void; } diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index ff3930b09..fd31c2536 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -67,7 +67,8 @@ export class ViewUtil { } if (!isDetachedElement(child)) { - this.addToVisualTree(parent, child, next); + const nextVisual = this.findNextVisual(next); + this.addToVisualTree(parent, child, nextVisual); } } @@ -104,10 +105,10 @@ export class ViewUtil { } private addToVisualTree(parent: NgView, child: NgView, next: NgView): void { - traceLog(`ViewUtil.addToVisualTreee parent: ${parent}, view: ${child}, next: ${next}`); + traceLog(`ViewUtil.addToVisualTree parent: ${parent}, view: ${child}, next: ${next}`); if (parent.meta && parent.meta.insertChild) { - parent.meta.insertChild(parent, child); + parent.meta.insertChild(parent, child, next); } else if (isLayout(parent)) { this.insertToLayout(parent, child, next); } else if (isContentView(parent)) { @@ -135,7 +136,7 @@ export class ViewUtil { } } - private findNextVisual(view: NgView) { + private findNextVisual(view: NgView): NgView { let next = view; while (next && isDetachedElement(next)) { next = next.nextSibling; From 4ba630f4c6661edb48c82f2176a9d1bc1c0e0547 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 4 Sep 2017 20:42:14 +0300 Subject: [PATCH 5/9] test(e2e): add Component with dynamic action bar --- .../action-bar-dynamic-items.component.ts | 27 +++++++++++++++++++ e2e/renderer/app/app-routing.module.ts | 7 +++++ e2e/renderer/app/content-view.component.ts | 1 - e2e/renderer/app/list.component.ts | 1 + 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 e2e/renderer/app/action-bar/action-bar-dynamic-items.component.ts diff --git a/e2e/renderer/app/action-bar/action-bar-dynamic-items.component.ts b/e2e/renderer/app/action-bar/action-bar-dynamic-items.component.ts new file mode 100644 index 000000000..0614e0798 --- /dev/null +++ b/e2e/renderer/app/action-bar/action-bar-dynamic-items.component.ts @@ -0,0 +1,27 @@ +import { Component } from "@angular/core"; + +@Component({ + template: ` + + + + + + + + + + + + + ` +}) +export class ActionBarDynamicItemsComponent { + public showNavigationButton = true; + public show1 = true; + public show2 = true; +} + diff --git a/e2e/renderer/app/app-routing.module.ts b/e2e/renderer/app/app-routing.module.ts index 576923862..2c3657ef9 100644 --- a/e2e/renderer/app/app-routing.module.ts +++ b/e2e/renderer/app/app-routing.module.ts @@ -1,6 +1,8 @@ import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NativeScriptRouterModule } from "nativescript-angular/router"; +import { ActionBarDynamicItemsComponent } from "./action-bar/action-bar-dynamic-items.component"; + import { ListComponent } from "./list.component"; import { NgForComponent } from "./ngfor.component"; import { NgForOfComponent } from "./ngforof.component"; @@ -17,6 +19,10 @@ export const routes = [ redirectTo: "/list", pathMatch: "full" }, + { + path: "action-bar-dynamic", + component: ActionBarDynamicItemsComponent, + }, { path: "list", component: ListComponent, @@ -56,6 +62,7 @@ export const routes = [ ]; export const navigatableComponents = [ + ActionBarDynamicItemsComponent, ListComponent, NgForComponent, NgForOfComponent, diff --git a/e2e/renderer/app/content-view.component.ts b/e2e/renderer/app/content-view.component.ts index 6ec92a1ba..a2aeb50ba 100644 --- a/e2e/renderer/app/content-view.component.ts +++ b/e2e/renderer/app/content-view.component.ts @@ -1,7 +1,6 @@ import { Component } from "@angular/core"; @Component({ - selector: "my-app", template: ` diff --git a/e2e/renderer/app/list.component.ts b/e2e/renderer/app/list.component.ts index ec4ca839c..fe37b87f4 100644 --- a/e2e/renderer/app/list.component.ts +++ b/e2e/renderer/app/list.component.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; @Component({ template: ` + From b051ba2b7b08b7abc17b3cc36b05253414e1dd80 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 4 Sep 2017 21:02:17 +0300 Subject: [PATCH 6/9] refactor(renderer): patch every View with ViewExtensions When a View is passed through the renderer on insert/remove it's patched with ViewExtensions for its class. That is done for parent views and for child views. fixes #978 --- nativescript-angular/view-util.ts | 56 ++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index fd31c2536..312ede1a5 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -51,24 +51,30 @@ export class ViewUtil { } public insertChild( - parent: NgView, - child: NgView, - previous: NgView = parent.lastChild, + parent: View, + child: View, + previous?: NgView, next?: NgView ) { if (!parent) { return; } - this.addToQueue(parent, child, previous, next); + const extendedParent = this.ensureNgViewExtensions(parent); + const extendedChild = this.ensureNgViewExtensions(child); + + if (!previous) { + previous = extendedParent.lastChild; + } + this.addToQueue(extendedParent, extendedChild, previous, next); if (isInvisibleNode(child)) { - child.templateParent = parent; + extendedChild.templateParent = extendedParent; } if (!isDetachedElement(child)) { const nextVisual = this.findNextVisual(next); - this.addToVisualTree(parent, child, nextVisual); + this.addToVisualTree(extendedParent, extendedChild, nextVisual); } } @@ -145,15 +151,18 @@ export class ViewUtil { return next; } - public removeChild(parent: NgView, child: NgView) { + public removeChild(parent: View, child: View) { traceLog(`ViewUtil.removeChild parent: ${parent} child: ${child}`); if (!parent) { return; } - this.removeFromQueue(parent, child); - this.removeFromVisualTree(parent, child); + const extendedParent = this.ensureNgViewExtensions(parent); + const extendedChild = this.ensureNgViewExtensions(child); + + this.removeFromQueue(extendedParent, extendedChild); + this.removeFromVisualTree(extendedParent, extendedChild); } private removeFromQueue(parent: NgView, child: NgView) { @@ -257,16 +266,34 @@ export class ViewUtil { } const viewClass = getViewClass(name); - let view = new viewClass(); - view.nodeName = name; - view.meta = getViewMeta(name); + const view = new viewClass(); + const ngView = this.setNgViewExtensions(view, name); + + return ngView; + } + + private ensureNgViewExtensions(view: View): NgView { + if (view.hasOwnProperty("meta")) { + return view as NgView; + } else { + const name = view.typeName; + const ngView = this.setNgViewExtensions(view, name); + + return ngView; + } + } + + private setNgViewExtensions(view: View, name: string): NgView { + const ngView = view as NgView; + ngView.nodeName = name; + ngView.meta = getViewMeta(name); // we're setting the node type of the view // to 'element' because of checks done in the // dom animation engine - view.nodeType = ELEMENT_NODE_TYPE; + ngView.nodeType = ELEMENT_NODE_TYPE; - return view; + return ngView; } public setProperty(view: NgView, attributeName: string, value: any, namespace?: string): void { @@ -382,4 +409,3 @@ export class ViewUtil { view.style[styleName] = unsetValue; } } - From f4e458661f71a28e05b98911f24fd100b7d1a252 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 4 Sep 2017 21:15:19 +0300 Subject: [PATCH 7/9] test(e2e): add ActionBarExtension component --- .../action-bar/action-bar-extension.component.ts | 16 ++++++++++++++++ e2e/renderer/app/app-routing.module.ts | 7 +++++++ e2e/renderer/app/list.component.ts | 1 + 3 files changed, 24 insertions(+) create mode 100644 e2e/renderer/app/action-bar/action-bar-extension.component.ts diff --git a/e2e/renderer/app/action-bar/action-bar-extension.component.ts b/e2e/renderer/app/action-bar/action-bar-extension.component.ts new file mode 100644 index 000000000..7d3b9b8e8 --- /dev/null +++ b/e2e/renderer/app/action-bar/action-bar-extension.component.ts @@ -0,0 +1,16 @@ +import { Component } from "@angular/core"; + +@Component({ + template: ` + + + + + + + + ` +}) +export class ActionBarExtensionComponent { + public show = true; +} diff --git a/e2e/renderer/app/app-routing.module.ts b/e2e/renderer/app/app-routing.module.ts index 2c3657ef9..a975d5be8 100644 --- a/e2e/renderer/app/app-routing.module.ts +++ b/e2e/renderer/app/app-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NativeScriptRouterModule } from "nativescript-angular/router"; import { ActionBarDynamicItemsComponent } from "./action-bar/action-bar-dynamic-items.component"; +import { ActionBarExtensionComponent } from "./action-bar/action-bar-extension.component"; import { ListComponent } from "./list.component"; import { NgForComponent } from "./ngfor.component"; @@ -23,6 +24,10 @@ export const routes = [ path: "action-bar-dynamic", component: ActionBarDynamicItemsComponent, }, + { + path: "action-bar-extension", + component: ActionBarExtensionComponent, + }, { path: "list", component: ListComponent, @@ -63,6 +68,8 @@ export const routes = [ export const navigatableComponents = [ ActionBarDynamicItemsComponent, + ActionBarExtensionComponent, + ListComponent, NgForComponent, NgForOfComponent, diff --git a/e2e/renderer/app/list.component.ts b/e2e/renderer/app/list.component.ts index fe37b87f4..ddbaa8b95 100644 --- a/e2e/renderer/app/list.component.ts +++ b/e2e/renderer/app/list.component.ts @@ -4,6 +4,7 @@ import { Component } from "@angular/core"; template: ` + From 8385049492fefa518fc883fad384d3f34efa5d4b Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 5 Sep 2017 07:31:01 +0300 Subject: [PATCH 8/9] style: fix linting errors --- nativescript-angular/directives/action-bar.ts | 1 - nativescript-angular/view-util.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nativescript-angular/directives/action-bar.ts b/nativescript-angular/directives/action-bar.ts index 5eda5e356..1b9053dd2 100644 --- a/nativescript-angular/directives/action-bar.ts +++ b/nativescript-angular/directives/action-bar.ts @@ -6,7 +6,6 @@ import { NavigationButton, } from "tns-core-modules/ui/action-bar"; import { Page } from "tns-core-modules/ui/page"; -import { View } from "tns-core-modules/ui/core/view"; import { isBlank } from "../lang-facade"; import { diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index 312ede1a5..f1075bac0 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -218,7 +218,7 @@ export class ViewUtil { } // NOTE: This one is O(n) - use carefully - private getChildIndex(parent: any, child: NgView) { + public getChildIndex(parent: any, child: NgView) { if (isLayout(parent)) { return parent.getChildIndex(child); } else if (isContentView(parent)) { From cac083114096d2c9462601d023e3e4740c8c6a5f Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 5 Sep 2017 19:15:27 +0300 Subject: [PATCH 9/9] test(e2e): cover dynamic ActionBar[Extension] scenarios --- e2e/renderer/e2e/action-bar.e2e-spec.ts | 163 ++++++++++++++++++++ e2e/renderer/e2e/helpers/appium-elements.ts | 21 +++ e2e/renderer/e2e/helpers/location.ts | 11 ++ 3 files changed, 195 insertions(+) create mode 100644 e2e/renderer/e2e/action-bar.e2e-spec.ts diff --git a/e2e/renderer/e2e/action-bar.e2e-spec.ts b/e2e/renderer/e2e/action-bar.e2e-spec.ts new file mode 100644 index 000000000..20f63d24b --- /dev/null +++ b/e2e/renderer/e2e/action-bar.e2e-spec.ts @@ -0,0 +1,163 @@ +import { + AppiumDriver, + createDriver, + SearchOptions, + elementHelper, +} from "nativescript-dev-appium"; + +import { isOnTheLeft } from "./helpers/location"; +import { DriverWrapper, ExtendedUIElement } from "./helpers/appium-elements"; + +describe("Action Bar scenario", () => { + let driver: AppiumDriver; + let driverWrapper: DriverWrapper; + + describe("dynamically add/remove ActionItems", async () => { + let firstActionItem: ExtendedUIElement; + let secondActionItem: ExtendedUIElement; + let toggleFirstButton: ExtendedUIElement; + let toggleSecondButton: ExtendedUIElement; + + before(async () => { + driver = await createDriver(); + driverWrapper = new DriverWrapper(driver); + }); + + after(async () => { + await driver.quit(); + console.log("Driver quits!"); + }); + + it("should navigate to page", async () => { + const navigationButton = + await driverWrapper.findElementByText("ActionBar dynamic", SearchOptions.exact); + await navigationButton.click(); + + const actionBar = + await driverWrapper.findElementByText("Action Bar Dynamic Items", SearchOptions.exact); + }); + + it("should find elements", async () => { + firstActionItem = await driverWrapper.findElementByText("one"); + secondActionItem = await driverWrapper.findElementByText("two"); + + toggleFirstButton = await driverWrapper.findElementByText("toggle 1"); + toggleSecondButton = await driverWrapper.findElementByText("toggle 2"); + }); + + it("should initially render the action items in the correct order", async () => { + await checkOrderIsCorrect(); + }); + + it("should detach first element when its condition is false", done => { + (async () => { + await toggleFirst(); + + try { + await driverWrapper.findElementByText("one", SearchOptions.exact); + } catch (e) { + done(); + } + })(); + }); + + it("should attach first element in the correct position", async () => { + await toggleFirst(); + await checkOrderIsCorrect(); + }); + + it("should detach second element when its condition is false", done => { + (async () => { + await toggleSecond(); + + try { + await driverWrapper.findElementByText("two", SearchOptions.exact); + } catch (e) { + done(); + } + })(); + }); + + it("should attach second element in the correct position", async () => { + await toggleSecond(); + await checkOrderIsCorrect(); + }); + + it("should detach and then reattach both at correct places", async () => { + await toggleFirst(); + await toggleSecond(); + + await toggleFirst(); + await toggleSecond(); + + await checkOrderIsCorrect(); + }); + + const checkOrderIsCorrect = async () => { + await isOnTheLeft(firstActionItem, secondActionItem); + }; + + const toggleFirst = async () => { + toggleFirstButton = await toggleFirstButton.refetch(); + await toggleFirstButton.click(); + }; + + const toggleSecond = async () => { + toggleSecondButton = await toggleSecondButton.refetch(); + await toggleSecondButton.click(); + }; + + }); + + describe("Action Bar extension with dynamic ActionItem", async () => { + let toggleButton: ExtendedUIElement; + let conditional: ExtendedUIElement; + + before(async () => { + driver = await createDriver(); + driverWrapper = new DriverWrapper(driver); + }); + + after(async () => { + await driver.quit(); + console.log("Driver quits!"); + }); + + it("should navigate to page", async () => { + const navigationButton = + await driverWrapper.findElementByText("ActionBarExtension", SearchOptions.exact); + await navigationButton.click(); + }); + + it("should find elements", async () => { + toggleButton = await driverWrapper.findElementByText("toggle"); + conditional = await driverWrapper.findElementByText("conditional"); + }); + + it("should detach conditional action item when its condition is false", done => { + (async () => { + await toggle(); + + try { + await driverWrapper.findElementByText("conditional", SearchOptions.exact); + } catch (e) { + done(); + } + })(); + }); + + it("should reattach conditional action item at correct place", async () => { + await toggle(); + await checkOrderIsCorrect(); + }); + + const checkOrderIsCorrect = async () => { + await isOnTheLeft(toggleButton, conditional); + }; + + const toggle = async () => { + toggleButton = await toggleButton.refetch(); + await toggleButton.click(); + }; + }); +}); diff --git a/e2e/renderer/e2e/helpers/appium-elements.ts b/e2e/renderer/e2e/helpers/appium-elements.ts index 26a020a4d..203808169 100644 --- a/e2e/renderer/e2e/helpers/appium-elements.ts +++ b/e2e/renderer/e2e/helpers/appium-elements.ts @@ -38,4 +38,25 @@ export class DriverWrapper { return result; } + + @refetchable() + async findElementByXPath(...args: any[]): Promise { + const result = await (this.driver).findElementByXPath(...args); + + return result; + } + + @refetchable() + async findElementsByXPath(...args: any[]): Promise { + const result = await (this.driver).findElementsByXPath(...args); + + return result || []; + } + + @refetchable() + async findElementsByClassName(...args: any[]): Promise { + const result = await (this.driver).findElementsByClassName(...args); + + return result || []; + } } diff --git a/e2e/renderer/e2e/helpers/location.ts b/e2e/renderer/e2e/helpers/location.ts index 189dfbbcc..df2a7cc67 100644 --- a/e2e/renderer/e2e/helpers/location.ts +++ b/e2e/renderer/e2e/helpers/location.ts @@ -11,3 +11,14 @@ export const isAbove = async (first: ExtendedUIElement, second: ExtendedUIElemen assert.isTrue(firstY < secondY); } + +export const isOnTheLeft = async (first: ExtendedUIElement, second: ExtendedUIElement) => { + first = await first.refetch(); + second = await second.refetch(); + + const { x: firstX } = await first.location(); + const { x: secondX } = await second.location(); + + assert.isTrue(firstX < secondX); +} +