Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/app/ui-tests-app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as trace from "tns-core-modules/trace";
trace.addCategories(trace.categories.NativeLifecycle);
trace.addCategories(trace.categories.Navigation);
trace.addCategories(trace.categories.Transition);

trace.enable();

var countResume = 0;
Expand Down
12 changes: 10 additions & 2 deletions tests/app/ui/tab-view/tab-view-navigation-tests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as TKUnit from "../../TKUnit";
import * as helper from "../helper";
import { isIOS, isAndroid } from "tns-core-modules/platform";
import { Label } from "tns-core-modules/ui/label";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
import * as frameModule from "tns-core-modules/ui/frame";
Expand Down Expand Up @@ -201,8 +202,15 @@ export function testLoadedAndUnloadedAreFired_WhenNavigatingAwayAndBack() {

topFrame.currentPage.id = null;

TKUnit.arrayAssert(loadedEventsCount, [2, 0]);
TKUnit.arrayAssert(unloadedEventsCount, [1, 0]);
if (isIOS) {
TKUnit.arrayAssert(loadedEventsCount, [2, 0]);
TKUnit.arrayAssert(unloadedEventsCount, [1, 0]);
}

if (isAndroid) {
TKUnit.arrayAssert(loadedEventsCount, [2, 2]);
TKUnit.arrayAssert(unloadedEventsCount, [1, 1]);
}
}

function _clickTheFirstButtonInTheListViewNatively(tabView: TabView) {
Expand Down
278 changes: 225 additions & 53 deletions tests/app/ui/tab-view/tab-view-root-tests.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,253 @@
import * as helper from "../helper";
import TKUnit = require("../../TKUnit");
import { isIOS, isAndroid } from "tns-core-modules/platform";
import { _resetRootView } from "tns-core-modules/application/";
import { Frame, NavigationEntry } from "tns-core-modules/ui/frame";
import { Page } from "tns-core-modules/ui/page";
import { TabView, TabViewItem } from "tns-core-modules/ui/tab-view";

export function test_whenChangingTabsWithFramesCorrectEventsAreRaised() {
const actualEventsRaised = [];
function waitUntilNavigatedTo(pages: Page[], action: Function) {
let completed = 0;
function navigatedTo(args) {
args.object.page.off("navigatedTo", navigatedTo);
completed++;
}

pages.forEach(page => page.on("navigatedTo", navigatedTo));
action();
TKUnit.waitUntilReady(() => completed === pages.length, 100);
}

function createPage(i: number) {
const page = new Page();
page.id = `Tab${i} Frame${i} Page${i}`;

return page;
}

function attachPageEventHandlers(page: Page) {
page.on(Page.loadedEvent, () => actualEventsRaised.push(`${page.id} loaded`));
page.on(Page.unloadedEvent, () => actualEventsRaised.push(`${page.id} unloaded`));
page.on(Page.navigatingToEvent, () => actualEventsRaised.push(`${page.id} navigatingTo`));
page.on(Page.navigatingFromEvent, () => actualEventsRaised.push(`${page.id} navigatingFrom`));
page.on(Page.navigatedToEvent, () => actualEventsRaised.push(`${page.id} navigatedTo`));
page.on(Page.navigatedFromEvent, () => actualEventsRaised.push(`${page.id} navigatedFrom`));
function createFrame(i: number, page: Page) {
const frame = new Frame();
frame.navigate(() => page);
frame.id = `Tab${i} Frame${i}`;

return frame;
}

function createTabItem(i: number, frame: Frame) {
const tabEntry = new TabViewItem();
tabEntry.title = "Tab " + i;
tabEntry.view = frame;
tabEntry["index"] = i;

return tabEntry;
}

function createTabItemsWithFrames(count: number) {
const items = [];

for (var i = 0; i < count; i++) {
const page = createPage(i);
const frame = createFrame(i, page);
const tabItem = createTabItem(i, frame);

items.push({ page, frame, tabItem });
}

function attachFrameEventHandlers(frame: Frame) {
frame.on(Frame.loadedEvent, () => actualEventsRaised.push(`${frame.id} loaded`));
frame.on(Frame.unloadedEvent, () => actualEventsRaised.push(`${frame.id} unloaded`));
return items;
}

export function test_ios_and_android_offset_zero_should_raise_same_events() {
let actualEventsRaised = [];

function resetActualEventsRaised() {
for (var i = 0; i < actualEventsRaised.length; i++) {
actualEventsRaised[i] = [];
}
}

const page1 = new Page();
page1.id = "Tab1 Frame1 Page1";
attachPageEventHandlers(page1);
function attachEventHandlers(i: number, item) {
actualEventsRaised.push([]);

const frame1 = new Frame();
frame1.navigate(() => page1);
frame1.id = "Tab1 Frame1";
attachFrameEventHandlers(frame1);
const page = item.page;
page.on(Page.loadedEvent, () => actualEventsRaised[i].push(`${page.id} loaded`));
page.on(Page.unloadedEvent, () => actualEventsRaised[i].push(`${page.id} unloaded`));
page.on(Page.navigatingToEvent, () => actualEventsRaised[i].push(`${page.id} navigatingTo`));
page.on(Page.navigatingFromEvent, () => actualEventsRaised[i].push(`${page.id} navigatingFrom`));
page.on(Page.navigatedToEvent, () => actualEventsRaised[i].push(`${page.id} navigatedTo`));
page.on(Page.navigatedFromEvent, () => actualEventsRaised[i].push(`${page.id} navigatedFrom`));

const page2 = new Page();
page2.id = "Tab2 Frame2 Page2";
attachPageEventHandlers(page2);
const frame = item.frame;
frame.on(Frame.loadedEvent, () => actualEventsRaised[i].push(`${frame.id} loaded`));
frame.on(Frame.unloadedEvent, () => actualEventsRaised[i].push(`${frame.id} unloaded`));
}

const frame2 = new Frame();
frame2.navigate(() => page2);
frame2.id = "Tab2 Frame2";
attachFrameEventHandlers(frame2);
const items = createTabItemsWithFrames(3);

items.forEach((item, i) => {
attachEventHandlers(i, item);
});

const tabView = new TabView();
const tabEntry1 = new TabViewItem();
tabEntry1.title = "frame1";
tabEntry1.view = frame1;
const tabEntry2 = new TabViewItem();
tabEntry2.title = "frame2";
tabEntry2.view = frame2;
tabView.items = [tabEntry1, tabEntry2];
tabView.items = items.map(item => item.tabItem);

// iOS cannot preload tab items
// Android preloads 1 tab item to the sides by default
// set this to 0, so that both platforms behave the same.
tabView.androidOffscreenTabLimit = 0;

const entry: NavigationEntry = {
create: () => tabView
};

helper.waitUntilNavigatedTo(page1, () => _resetRootView(entry));
helper.waitUntilNavigatedTo(page2, () => tabView.selectedIndex = 1);
helper.waitUntilNavigatedTo(items[0].page, () => _resetRootView(entry));

const expectedEventsRaisedAfterTabCreated = [
[
"Tab0 Frame0 loaded",
"Tab0 Frame0 Page0 navigatingTo",
"Tab0 Frame0 Page0 loaded",
"Tab0 Frame0 Page0 navigatedTo",
],
[],
[]
];

TKUnit.assertDeepEqual(actualEventsRaised, expectedEventsRaisedAfterTabCreated);

resetActualEventsRaised();
helper.waitUntilNavigatedTo(items[2].page, () => tabView.selectedIndex = 2);

const expectedEventsRaisedAfterSelectThirdTab = [
[
"Tab0 Frame0 Page0 unloaded",
"Tab0 Frame0 unloaded",
],
[],
[
"Tab2 Frame2 loaded",
"Tab2 Frame2 Page2 navigatingTo",
"Tab2 Frame2 Page2 loaded",
"Tab2 Frame2 Page2 navigatedTo"
]
];

TKUnit.assertDeepEqual(actualEventsRaised, expectedEventsRaisedAfterSelectThirdTab);

resetActualEventsRaised();
tabView.selectedIndex = 0;
TKUnit.waitUntilReady(() => page1.isLoaded);

const expectedEventsRaised = [
"Tab1 Frame1 loaded",
"Tab1 Frame1 Page1 navigatingTo",
"Tab1 Frame1 Page1 loaded",
"Tab1 Frame1 Page1 navigatedTo",
"Tab1 Frame1 Page1 unloaded",
"Tab1 Frame1 unloaded",
"Tab2 Frame2 loaded",
"Tab2 Frame2 Page2 navigatingTo",
"Tab2 Frame2 Page2 loaded",
"Tab2 Frame2 Page2 navigatedTo",
"Tab2 Frame2 Page2 unloaded",
"Tab2 Frame2 unloaded",
"Tab1 Frame1 Page1 loaded",
"Tab1 Frame1 loaded"
TKUnit.waitUntilReady(() => items[0].page.isLoaded);

const expectedEventsRaisedAfterReturnToFirstTab = [
[
"Tab0 Frame0 Page0 loaded",
"Tab0 Frame0 loaded",
],
[],
[
"Tab2 Frame2 Page2 unloaded",
"Tab2 Frame2 unloaded",
]
];

TKUnit.arrayAssert(actualEventsRaised, expectedEventsRaised);
TKUnit.assertDeepEqual(actualEventsRaised, expectedEventsRaisedAfterReturnToFirstTab);
}

export function test_android_default_offset_should_preload_1_tab_on_each_side() {
let actualEventsRaised = [];

function resetActualEventsRaised() {
for (var i = 0; i < actualEventsRaised.length; i++) {
actualEventsRaised[i] = [];
}
}

function attachEventHandlers(i: number, item) {
actualEventsRaised.push([]);

const page = item.page;
page.on(Page.loadedEvent, () => actualEventsRaised[i].push(`${page.id} loaded`));
page.on(Page.unloadedEvent, () => actualEventsRaised[i].push(`${page.id} unloaded`));
page.on(Page.navigatingToEvent, () => actualEventsRaised[i].push(`${page.id} navigatingTo`));
page.on(Page.navigatingFromEvent, () => actualEventsRaised[i].push(`${page.id} navigatingFrom`));
page.on(Page.navigatedToEvent, () => actualEventsRaised[i].push(`${page.id} navigatedTo`));
page.on(Page.navigatedFromEvent, () => actualEventsRaised[i].push(`${page.id} navigatedFrom`));

const frame = item.frame;
frame.on(Frame.loadedEvent, () => actualEventsRaised[i].push(`${frame.id} loaded`));
frame.on(Frame.unloadedEvent, () => actualEventsRaised[i].push(`${frame.id} unloaded`));
}

if (isAndroid) {
const items = createTabItemsWithFrames(3);

items.forEach((item, i) => {
attachEventHandlers(i, item);
});

const tabView = new TabView();
tabView.items = items.map(item => item.tabItem);

const entry: NavigationEntry = {
create: () => tabView
};

waitUntilNavigatedTo([items[0].page, items[1].page], () => _resetRootView(entry));

const expectedEventsRaisedAfterTabCreated = [
[
"Tab0 Frame0 loaded",
"Tab0 Frame0 Page0 navigatingTo",
"Tab0 Frame0 Page0 loaded",
"Tab0 Frame0 Page0 navigatedTo",
],
[
"Tab1 Frame1 loaded",
"Tab1 Frame1 Page1 navigatingTo",
"Tab1 Frame1 Page1 loaded",
"Tab1 Frame1 Page1 navigatedTo",
],
[]
];

TKUnit.assertDeepEqual(actualEventsRaised, expectedEventsRaisedAfterTabCreated);

resetActualEventsRaised();
helper.waitUntilNavigatedTo(items[2].page, () => tabView.selectedIndex = 2);

const expectedEventsRaisedAfterSelectThirdTab = [
[
"Tab0 Frame0 Page0 unloaded",
"Tab0 Frame0 unloaded",
],
[],
[
"Tab2 Frame2 loaded",
"Tab2 Frame2 Page2 navigatingTo",
"Tab2 Frame2 Page2 loaded",
"Tab2 Frame2 Page2 navigatedTo"
]
];

TKUnit.assertDeepEqual(actualEventsRaised, expectedEventsRaisedAfterSelectThirdTab);

resetActualEventsRaised();
tabView.selectedIndex = 0;
TKUnit.waitUntilReady(() => items[0].page.isLoaded);

const expectedEventsRaisedAfterReturnToFirstTab = [
[
"Tab0 Frame0 Page0 loaded",
"Tab0 Frame0 loaded",
],
[],
[
"Tab2 Frame2 Page2 unloaded",
"Tab2 Frame2 unloaded",
]
];

TKUnit.assertDeepEqual(actualEventsRaised, expectedEventsRaisedAfterReturnToFirstTab);
}
}

export function tearDownModule() {
Expand Down
23 changes: 15 additions & 8 deletions tns-core-modules/ui/frame/frame.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ export class Frame extends FrameBase {
}
}

onUnloaded() {
if (this._currentEntry && this._currentEntry.fragment) {
const manager: android.app.FragmentManager = this._getFragmentManager();

const transaction = manager.beginTransaction();
transaction.remove(this._currentEntry.fragment);
transaction.commitAllowingStateLoss();
}

super.onUnloaded();
}

private createFragment(backstackEntry: BackstackEntry, fragmentTag: string): android.app.Fragment {
ensureFragmentClass();
const newFragment = new fragmentClass();
Expand Down Expand Up @@ -569,7 +581,8 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
callbacks.entry = entry;
entry.fragment = fragment;
_updateTransitions(entry);
} else {
}
else {
throw new Error(`Could not find a page for ${fragmentTag}.`);
}
}
Expand Down Expand Up @@ -697,13 +710,7 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
this.frame._addView(page);
}

// Load page here even if root view is not loaded yet.
// Otherwise it will show as blank,
// The case is Tab->Frame->Page activity recreated, fragments are
// created before Tab loads its items.
// TODO: addCheck if the fragment is visible so we don't load pages
// that are not in the selectedIndex of the Tab!!!!!!
if (!page.isLoaded) {
if (frame.isLoaded && !page.isLoaded) {
page.callLoaded();
}

Expand Down
Loading