From 4ce45666a5f1ce51c94470b007ec6148453c7e5b Mon Sep 17 00:00:00 2001 From: Martin Yankov Date: Wed, 21 Feb 2018 11:38:37 +0200 Subject: [PATCH] test: add reset root view tests (#5437) --- tests/app/navigation/reset-root-view-tests.ts | 141 ++++++++++++++++++ tests/app/testRunner.ts | 6 +- .../application/application-common.ts | 2 - .../application/application.android.ts | 9 +- .../application/application.ios.ts | 17 ++- .../ui/core/view-base/view-base.d.ts | 4 + .../ui/core/view-base/view-base.ts | 7 + tns-core-modules/ui/frame/frame-common.ts | 17 ++- tns-core-modules/ui/frame/frame.android.ts | 14 +- 9 files changed, 198 insertions(+), 19 deletions(-) create mode 100644 tests/app/navigation/reset-root-view-tests.ts diff --git a/tests/app/navigation/reset-root-view-tests.ts b/tests/app/navigation/reset-root-view-tests.ts new file mode 100644 index 000000000..dd8256f0a --- /dev/null +++ b/tests/app/navigation/reset-root-view-tests.ts @@ -0,0 +1,141 @@ +import * as TKUnit from "../TKUnit"; +import { Page } from "tns-core-modules/ui/page"; +import { Frame, NavigationEntry, stack } from "tns-core-modules/ui/frame"; +import { _resetRootView, getRootView } from "tns-core-modules/application"; +import { TabView, TabViewItem } from "tns-core-modules/ui/tab-view"; + +function createTestFrameRootEntry() { + const page = new Page(); + const frameRoot = new Frame(); + frameRoot.navigate(() => page); + + const entry: NavigationEntry = { + create: () => frameRoot + }; + + return { + entry: entry, + root: frameRoot, + page: page + }; +} + +function createTestTabRootEntry() { + const testFrameRoot1 = createTestFrameRootEntry(); + const testFrameRoot2 = createTestFrameRootEntry(); + + const tabView = new TabView(); + const tabEntry1 = new TabViewItem(); + tabEntry1.title = "frameRoot1"; + tabEntry1.view = testFrameRoot1.root; + const tabEntry2 = new TabViewItem(); + tabEntry2.title = "frameRoot2"; + tabEntry2.view = testFrameRoot2.root; + tabView.items = [tabEntry1, tabEntry2]; + + const entry: NavigationEntry = { + create: () => tabView + }; + + return { + entry: entry, + root: tabView, + page: testFrameRoot1.page + }; +} + +export function test_reset_frame_to_frame() { + const testFrameRoot1 = createTestFrameRootEntry(); + + _resetRootView(testFrameRoot1.entry); + TKUnit.waitUntilReady(() => testFrameRoot1.page.isLoaded); + + const rootView1 = getRootView(); + const frameStack1 = stack(); + TKUnit.assertEqual(rootView1, testFrameRoot1.root); + TKUnit.assertEqual(frameStack1.length, 1); + + const testFrameRoot2 = createTestFrameRootEntry(); + + _resetRootView(testFrameRoot2.entry); + TKUnit.waitUntilReady(() => testFrameRoot2.page.isLoaded); + + const rootView2 = getRootView(); + const frameStack2 = stack(); + TKUnit.assertEqual(rootView2, testFrameRoot2.root); + TKUnit.assertEqual(frameStack2.length, 1); +}; + +export function test_reset_frame_to_tab() { + const testFrameRoot = createTestFrameRootEntry(); + + _resetRootView(testFrameRoot.entry); + TKUnit.waitUntilReady(() => testFrameRoot.page.isLoaded); + + const rootView1 = getRootView(); + const frameStack1 = stack(); + TKUnit.assertEqual(rootView1, testFrameRoot.root); + TKUnit.assertEqual(frameStack1.length, 1); + + const testTabRoot = createTestTabRootEntry(); + + _resetRootView(testTabRoot.entry); + TKUnit.waitUntilReady(() => testTabRoot.page.isLoaded); + + const rootView2 = getRootView(); + const frameStack2 = stack(); + TKUnit.assertEqual(rootView2, testTabRoot.root); + TKUnit.assertEqual(frameStack2.length, 2); +}; + +export function test_reset_tab_to_frame() { + const testTabRoot = createTestTabRootEntry(); + + _resetRootView(testTabRoot.entry); + TKUnit.waitUntilReady(() => testTabRoot.page.isLoaded); + + const rootView2 = getRootView(); + const frameStack2 = stack(); + TKUnit.assertEqual(rootView2, testTabRoot.root); + TKUnit.assertEqual(frameStack2.length, 2); + + const testFrameRoot = createTestFrameRootEntry(); + + _resetRootView(testFrameRoot.entry); + TKUnit.waitUntilReady(() => testFrameRoot.page.isLoaded); + + const rootView1 = getRootView(); + const frameStack1 = stack(); + TKUnit.assertEqual(rootView1, testFrameRoot.root); + TKUnit.assertEqual(frameStack1.length, 1); +}; + +export function test_reset_tab_to_tab() { + const testTabRoot1 = createTestTabRootEntry(); + + _resetRootView(testTabRoot1.entry); + TKUnit.waitUntilReady(() => testTabRoot1.page.isLoaded); + + const rootView1 = getRootView(); + const frameStack1 = stack(); + TKUnit.assertEqual(rootView1, testTabRoot1.root); + TKUnit.assertEqual(frameStack1.length, 2); + + const testTabRoot2 = createTestTabRootEntry(); + + _resetRootView(testTabRoot2.entry); + TKUnit.waitUntilReady(() => testTabRoot2.page.isLoaded); + + const rootView2 = getRootView(); + const frameStack2 = stack(); + TKUnit.assertEqual(rootView2, testTabRoot2.root); + TKUnit.assertEqual(frameStack2.length, 2); +}; + +export function tearDownModule() { + // reset the root to frame for other tests + const resetFrameRoot = createTestFrameRootEntry(); + + _resetRootView(resetFrameRoot.entry); + TKUnit.waitUntilReady(() => resetFrameRoot.page.isLoaded); +} \ No newline at end of file diff --git a/tests/app/testRunner.ts b/tests/app/testRunner.ts index 1298d5a75..fc15187cc 100644 --- a/tests/app/testRunner.ts +++ b/tests/app/testRunner.ts @@ -1,7 +1,8 @@ /* tslint:disable */ import * as TKUnit from "./TKUnit"; +import { _resetRootView, getRootView } from "tns-core-modules/application"; import { messageType } from "tns-core-modules/trace"; -import { topmost, Frame } from "tns-core-modules/ui/frame"; +import { topmost, Frame, NavigationEntry } from "tns-core-modules/ui/frame"; import { Page } from "tns-core-modules/ui/page"; import { TextView } from "tns-core-modules/ui/text-view"; import { Button } from "tns-core-modules/ui/button"; @@ -233,6 +234,9 @@ allTests["SEARCH-BAR"] = searchBarTests; import * as navigationTests from "./navigation/navigation-tests"; allTests["NAVIGATION"] = navigationTests; +import * as resetRootViewTests from "./navigation/reset-root-view-tests"; +allTests["RESET-ROOT-VIEW"] = resetRootViewTests; + const testsSuitesWithLongDelay = { HTTP: 15 * 1000, } diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts index 25434d193..dd3a64e21 100644 --- a/tns-core-modules/application/application-common.ts +++ b/tns-core-modules/application/application-common.ts @@ -2,8 +2,6 @@ require("globals"); import { Observable, EventData } from "../data/observable"; -// types -import { View } from "../ui/core/view"; import { trace as profilingTrace, time, diff --git a/tns-core-modules/application/application.android.ts b/tns-core-modules/application/application.android.ts index 403132452..5416a1867 100644 --- a/tns-core-modules/application/application.android.ts +++ b/tns-core-modules/application/application.android.ts @@ -126,7 +126,7 @@ setApplication(androidApp); let mainEntry: NavigationEntry; let started = false; // NOTE: for backwards compatibility. Remove for 4.0.0. -let createRootFrame = true; +const createRootFrame = { value: true }; export function start(entry?: NavigationEntry | string) { if (started) { throw new Error("Application is already started."); @@ -141,11 +141,11 @@ export function start(entry?: NavigationEntry | string) { } export function shouldCreateRootFrame(): boolean { - return createRootFrame; + return createRootFrame.value; } export function run(entry?: NavigationEntry | string) { - createRootFrame = false; + createRootFrame.value = false; start(entry); } @@ -157,6 +157,7 @@ export function _resetRootView(entry?: NavigationEntry | string) { throw new Error("Cannot find android activity."); } + createRootFrame.value = false; mainEntry = typeof entry === "string" ? { moduleName: entry } : entry; const callbacks: AndroidActivityCallbacks = activity[CALLBACKS]; callbacks.resetActivityContent(activity); @@ -166,7 +167,7 @@ export function getMainEntry() { return mainEntry; } -export function getRootView() { +export function getRootView(): View { // Use start activity as a backup when foregroundActivity is still not set // in cases when we are getting the root view before activity.onResumed event is fired const activity = androidApp.foregroundActivity || androidApp.startActivity; diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts index ac3713969..c28d76a4f 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -100,7 +100,7 @@ class IOSApplication implements IOSApplicationDefinition { } } - get rootView() : View { + get rootView(): View { return this._rootView; } @@ -213,11 +213,16 @@ class IOSApplication implements IOSApplicationDefinition { } public setWindowContent(view?: View): void { + if (this._rootView) { + // if we already have a root view, we reset it. + this._rootView._onRootViewReset(); + } + const rootView = createRootView(view); this._rootView = rootView; const controller = getViewController(rootView); - if (createRootFrame) { + if (createRootFrame.value) { // Don't setup as styleScopeHost rootView._setupUI({}); } else { @@ -248,7 +253,7 @@ function createRootView(v?: View) { if (!rootView) { // try to navigate to the mainEntry (if specified) if (mainEntry) { - if (createRootFrame) { + if (createRootFrame.value) { const frame = rootView = new Frame(); frame.navigate(mainEntry); } else { @@ -272,7 +277,7 @@ export function getRootView() { } // NOTE: for backwards compatibility. Remove for 4.0.0. -let createRootFrame = true; +const createRootFrame = { value: true }; let started: boolean = false; export function start(entry?: string | NavigationEntry) { mainEntry = typeof entry === "string" ? { moduleName: entry } : entry; @@ -300,12 +305,12 @@ export function start(entry?: string | NavigationEntry) { } export function run(entry?: string | NavigationEntry) { - createRootFrame = false; + createRootFrame.value = false; start(entry); } export function _resetRootView(entry?: NavigationEntry | string) { - createRootFrame = false; + createRootFrame.value = false; mainEntry = typeof entry === "string" ? { moduleName: entry } : entry; iosApp.setWindowContent(); } diff --git a/tns-core-modules/ui/core/view-base/view-base.d.ts b/tns-core-modules/ui/core/view-base/view-base.d.ts index 7cd7ee7a4..13b650726 100644 --- a/tns-core-modules/ui/core/view-base/view-base.d.ts +++ b/tns-core-modules/ui/core/view-base/view-base.d.ts @@ -267,6 +267,10 @@ export abstract class ViewBase extends Observable { * Method is intended to be overridden by inheritors and used as "protected" */ public _dialogClosed(): void; + /** + * Method is intended to be overridden by inheritors and used as "protected" + */ + public _onRootViewReset(): void; _domId: number; diff --git a/tns-core-modules/ui/core/view-base/view-base.ts b/tns-core-modules/ui/core/view-base/view-base.ts index 474a50ed7..7056f8051 100644 --- a/tns-core-modules/ui/core/view-base/view-base.ts +++ b/tns-core-modules/ui/core/view-base/view-base.ts @@ -961,6 +961,13 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition public _dialogClosed(): void { return; } + + public _onRootViewReset(): void { + eachDescendant(this, (child: ViewBase) => { + child._onRootViewReset(); + return true; + }); + } } ViewBase.prototype.isCollapsed = false; diff --git a/tns-core-modules/ui/frame/frame-common.ts b/tns-core-modules/ui/frame/frame-common.ts index 7ac8eff81..aee925fa7 100644 --- a/tns-core-modules/ui/frame/frame-common.ts +++ b/tns-core-modules/ui/frame/frame-common.ts @@ -58,7 +58,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition { @profile public onLoaded() { super.onLoaded(); - + this._processNextNavigationEntry(); } @@ -424,10 +424,25 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition { this._isInFrameStack = false; } + private _removeFromFrameStack() { + if (!this._isInFrameStack) { + return; + } + + const index = frameStack.indexOf(this); + frameStack.splice(index, 1); + this._isInFrameStack = false; + } + public _dialogClosed(): void { this._popFromFrameStack(); } + public _onRootViewReset(): void { + this._removeFromFrameStack(); + super._onRootViewReset(); + } + get _childrenCount(): number { if (this.currentPage) { return 1; diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 56c38d545..3ab4d17d7 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -244,7 +244,7 @@ export class Frame extends FrameBase { this.goBack(); return true; } - + if (!this.navigationQueueIsEmpty()) { const manager = this._getFragmentManager(); if (manager) { @@ -935,6 +935,10 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { } public resetActivityContent(activity: android.app.Activity): void { + if (this._rootView) { + // if we already have a root view, we reset it. + this._rootView._onRootViewReset(); + } // Delete previously cached root view in order to recreate it. this._rootView = null; this.setActivityContent(activity, null, false); @@ -1020,11 +1024,11 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks { const notifyLaunch = profile("notifyLaunch", function notifyLaunch(intent: android.content.Intent, savedInstanceState: android.os.Bundle): View { const launchArgs: application.LaunchEventData = { - eventName: application.launchEvent, - object: application.android, - android: intent, savedInstanceState + eventName: application.launchEvent, + object: application.android, + android: intent, savedInstanceState }; - + application.notify(launchArgs); application.notify({ eventName: "loadAppCss", object: this, cssFile: application.getCssFileName() }); return launchArgs.root;