Skip to content

Commit

Permalink
fix: show view tab title of inactive views when reloading the applica…
Browse files Browse the repository at this point in the history
…tion

fixes #121
  • Loading branch information
danielwiehl authored and ReToCode committed Mar 18, 2019
1 parent 65ba4f0 commit f011b5b
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 30 deletions.
16 changes: 14 additions & 2 deletions projects/e2e/workbench/src/page-object/app.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* SPDX-License-Identifier: EPL-2.0
*/

import { $, $$, browser, ElementFinder, protractor } from 'protractor';
import { $, $$, browser, ElementFinder, Key, protractor } from 'protractor';
import { getCssClasses } from '../util/testing.util';
import { Duration, Severity } from '@scion/workbench-application-platform.api';
import { ISize } from 'selenium-webdriver';
Expand Down Expand Up @@ -65,8 +65,13 @@ export class AppPO {

/**
* Returns the number of view tabs.
*
* Optionally, provide a CSS class to only count view tabs of given view.
*/
public async getViewTabCount(): Promise<number> {
public async getViewTabCount(viewCssClass?: string): Promise<number> {
if (viewCssClass) {
return $$(`wb-view-tab.${viewCssClass}`).count();
}
return $$('wb-view-tab').count();
}

Expand Down Expand Up @@ -248,6 +253,13 @@ export class AppPO {
return new WelcomePagePO();
}

/**
* Closes all views of all viewparts.
*/
public async closeAllViewTabs(): Promise<void> {
return $$('wb-view-part').sendKeys(Key.chord(Key.CONTROL, Key.SHIFT, 'k'));
}

/**
* Returns a handle representing the viewpart action which has given CSS class set.
* This call does not send a command to the browser. Use 'isPresent()' to test its presence.
Expand Down
74 changes: 74 additions & 0 deletions projects/e2e/workbench/src/router.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { AppPO } from './page-object/app.po';
import { ViewNavigationPO } from './page-object/view-navigation.po';
import { browser } from 'protractor';

describe('Workbench Router', () => {

Expand Down Expand Up @@ -112,4 +113,77 @@ describe('Workbench Router', () => {
await expect(viewNavigationPO.isActiveViewTab).toBeTruthy();
await expect(appPO.getViewTabCount()).toEqual(1);
});

it('should show title of inactive views when reloading the application', async () => {
await viewNavigationPO.navigateTo();

// open view-1
await viewNavigationPO.enterPath('view');
await viewNavigationPO.enterMatrixParams({viewCssClass: 'e2e-view-1', viewTitle: 'view-1-title'});
await viewNavigationPO.navigate();

// open view-2
await viewNavigationPO.activateViewTab();
await viewNavigationPO.enterPath('view');
await viewNavigationPO.enterMatrixParams({viewCssClass: 'e2e-view-2', viewTitle: 'view-2-title'});
await viewNavigationPO.navigate();

// reload the application
await browser.refresh();

await expect(appPO.findViewTab('e2e-view-1').isActive()).toBeFalsy();
await expect(appPO.findViewTab('e2e-view-1').getTitle()).toEqual('view-1-title');

await expect(appPO.findViewTab('e2e-view-2').isActive()).toBeTruthy();
await expect(appPO.findViewTab('e2e-view-2').getTitle()).toEqual('view-2-title');

await expect((await browser.manage().logs().get('browser')).length).toEqual(0);
});

it('should not throw outlet activation error when opening a new view tab once a view tab was closed', async () => {
await browser.get('/');

// open view tab
await appPO.openNewViewTab();
await expect(appPO.getViewTabCount('e2e-welcome-page')).toEqual(1);

// close view tab
await appPO.findViewTab('e2e-welcome-page').close();
await expect(appPO.getViewTabCount('e2e-welcome-page')).toEqual(0);

// open view tab
await appPO.openNewViewTab();
await expect(appPO.getViewTabCount('e2e-welcome-page')).toEqual(1);
// expect no error to be thrown
await expect(browser.manage().logs().get('browser')).toEqual([]);

// open view tab
await appPO.openNewViewTab();
await expect(appPO.getViewTabCount('e2e-welcome-page')).toEqual(2);
// expect no error to be thrown
await expect(browser.manage().logs().get('browser')).toEqual([]);
});

it('should close all views in a row', async () => {
await browser.get('/');

// open multiple view tabs
await appPO.openNewViewTab();
await appPO.openNewViewTab();
await appPO.openNewViewTab();
await appPO.openNewViewTab();
await appPO.openNewViewTab();
await appPO.openNewViewTab();
await appPO.openNewViewTab();
await appPO.openNewViewTab();
await appPO.openNewViewTab();

// close all view tabs
await appPO.closeAllViewTabs();

await expect(appPO.getViewTabCount()).toEqual(0);

// expect no error to be thrown
await expect(browser.manage().logs().get('browser')).toEqual([]);
});
});
75 changes: 47 additions & 28 deletions projects/scion/workbench/src/lib/workbench-url-observer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
* SPDX-License-Identifier: EPL-2.0
*/

import { GuardsCheckEnd, NavigationEnd, NavigationStart, Route, Router } from '@angular/router';
import { filter, takeUntil } from 'rxjs/operators';
import { Injectable, IterableDiffers, OnDestroy } from '@angular/core';
import { GuardsCheckEnd, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Route, Router } from '@angular/router';
import { filter, take, takeUntil } from 'rxjs/operators';
import { Injectable, IterableChanges, IterableDiffers, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { ViewOutletDiffer } from './routing/view-outlet-differ';
import { WorkbenchAuxiliaryRoutesRegistrator } from './routing/workbench-auxiliary-routes-registrator.service';
Expand Down Expand Up @@ -41,7 +41,6 @@ export class WorkbenchUrlObserver implements OnDestroy {
differs: IterableDiffers) {
this.installNavigationStartRoutingListener(differs);
this.installGuardsCheckEndRoutingListener(differs);
this.installNavigationEndRoutingListener(differs);
}

/**
Expand Down Expand Up @@ -78,38 +77,56 @@ export class WorkbenchUrlObserver implements OnDestroy {
return;
}

// Discard routes of closed views.
const viewOutletChanges = differ.diff(routerEvent.url);
if (viewOutletChanges) {
const routes = this._router.config;
const discardedRoutes: Route[] = [];

viewOutletChanges.forEachRemovedItem(({item}) => {
discardedRoutes.push(...routes.filter(route => route.outlet === item));
});
this.discardRoutesOfClosedViews(viewOutletChanges);

if (discardedRoutes.length > 0) {
this._auxRoutesRegistrator.replaceRouterConfig(routes.filter(route => !discardedRoutes.includes(route)));
}
}
}

private onNavigationEnd(routerEvent: NavigationEnd, differ: ViewOutletDiffer): void {
// Update viewpart registry.
// Parse the ViewPartGrid from the URL.
const serializedViewPartGrid = this._router.parseUrl(routerEvent.url).queryParamMap.get(VIEW_GRID_QUERY_PARAM);
const viewPartGrid = new ViewPartGrid(serializedViewPartGrid, this._viewPartGridSerializer);

// Update view registry.
const viewOutletChanges = differ.diff(routerEvent.url);
// Update the view registry with added or removed view outlets.
//
// Note:
// Must be done after 'GuardsCheckEnd' and not after 'NavigationEnd' for the following reason:
// When registering new view outlets, respective view router outlets are instantiated. Later in the routing process,
// while Angular router activates routes, already instantiated router outlets are activated and their components mounted.
// If the outlets would not be instantiated yet, they would get activated only once attached to the DOM.
//
// Early activation is important for inactive views to set their view title, e.g. when reloading the application.
this.updateViewRegistry(viewOutletChanges, viewPartGrid);

// Update the grid after routing completed to not run into following error: 'Cannot activate an already activated outlet'.
this.whenNavigatedThen(() => this._viewPartRegistry.setGrid(viewPartGrid));
}

private discardRoutesOfClosedViews(viewOutletChanges: IterableChanges<string>): void {
if (!viewOutletChanges) {
return;
}

const routes = this._router.config;
const discardedRoutes: Route[] = [];

viewOutletChanges.forEachRemovedItem(({item}) => {
discardedRoutes.push(...routes.filter(route => route.outlet === item));
});

if (discardedRoutes.length > 0) {
this._auxRoutesRegistrator.replaceRouterConfig(routes.filter(route => !discardedRoutes.includes(route)));
}
}

private updateViewRegistry(viewOutletChanges: IterableChanges<string>, viewPartGrid: ViewPartGrid): void {
if (viewOutletChanges) {
viewOutletChanges.forEachAddedItem(({item}) => {
// Note: registering a view outlet instantiates a new 'ViewComponent' with the view's named router outlet
this._viewRegistry.addViewOutlet(item, viewPartGrid.isViewActive(item));
});
viewOutletChanges.forEachRemovedItem(({item}) => {
this._viewRegistry.removeViewOutlet(item);
});
}
this._viewPartRegistry.setGrid(viewPartGrid);
}

/**
Expand Down Expand Up @@ -143,17 +160,19 @@ export class WorkbenchUrlObserver implements OnDestroy {
}

/**
* Delegates `NavigationEnd` events to `onNavigationEnd` method.
* Runs given function once navigation completed successfully.
*/
private installNavigationEndRoutingListener(differs: IterableDiffers): void {
const outletDiffer = new ViewOutletDiffer(differs, this._router);
private whenNavigatedThen(thenFn: () => void): void {
this._router.events
.pipe(
filter(event => event instanceof NavigationEnd),
filter(event => event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError),
take(1),
takeUntil(this._destroy$),
)
.subscribe((routerEvent: NavigationEnd) => {
this.onNavigationEnd(routerEvent, outletDiffer);
.subscribe(event => {
if (event instanceof NavigationEnd) {
thenFn();
}
});
}

Expand Down

0 comments on commit f011b5b

Please sign in to comment.