diff --git a/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.html b/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.html
new file mode 100644
index 000000000..017b0457e
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.html
@@ -0,0 +1,47 @@
+
+
+ {{view.id}}
+
+
+
+ {{view.part.id}}
+
+
+
+ {{view.title}}
+
+
+
+ {{view.heading}}
+
+
+
+ {{view.urlSegments | appJoin:'/'}}
+
+
+
+@if (route?.snapshot?.params | appNullIfEmpty; as params) {
+
+}
+
+@if (route?.snapshot?.data | appNullIfEmpty; as data) {
+
+}
+
+@if (view.state | appNullIfEmpty; as state) {
+
+}
+
+
+
+
+
diff --git a/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.scss b/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.scss
new file mode 100644
index 000000000..a4e96b29e
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.scss
@@ -0,0 +1,16 @@
+:host {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5em;
+
+ > section {
+ display: flex;
+ flex-direction: column;
+ gap: .5em;
+
+ > header {
+ font-weight: bold;
+ margin-bottom: .25em;
+ }
+ }
+}
diff --git a/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.ts b/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.ts
new file mode 100644
index 000000000..2c5507071
--- /dev/null
+++ b/apps/workbench-testing-app/src/app/view-info-dialog/view-info-dialog.component.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2024 Swiss Federal Railways
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+import {Component, Input, OnInit} from '@angular/core';
+import {WorkbenchDialog, WorkbenchDialogActionDirective, WorkbenchView} from '@scion/workbench';
+import {ActivatedRoute, Router} from '@angular/router';
+import {SciFormFieldComponent} from '@scion/components.internal/form-field';
+import {JoinPipe} from '../common/join.pipe';
+import {SciKeyValueComponent} from '@scion/components.internal/key-value';
+import {NullIfEmptyPipe} from '../common/null-if-empty.pipe';
+
+@Component({
+ selector: 'app-view-info-dialog',
+ templateUrl: './view-info-dialog.component.html',
+ styleUrls: ['./view-info-dialog.component.scss'],
+ standalone: true,
+ imports: [
+ SciFormFieldComponent,
+ JoinPipe,
+ WorkbenchDialogActionDirective,
+ SciKeyValueComponent,
+ NullIfEmptyPipe,
+ ],
+})
+export class ViewInfoDialogComponent implements OnInit {
+
+ /**
+ * Activated route, or `undefined` if not navigated yet.
+ */
+ protected route: ActivatedRoute | undefined;
+
+ @Input({required: true})
+ public view!: WorkbenchView;
+
+ constructor(private _router: Router, private _dialog: WorkbenchDialog) {
+ this._dialog.title = 'View Info';
+ this._dialog.size.minWidth = '32em';
+ }
+
+ public ngOnInit(): void {
+ const route = this._router.routerState.root.children.find(route => route.outlet === this.view.id);
+ this.route = route && resolveActualRoute(route);
+ }
+
+ public onClose(): void {
+ this._dialog.close();
+ }
+}
+
+function resolveActualRoute(route: ActivatedRoute): ActivatedRoute {
+ return route.firstChild ? resolveActualRoute(route.firstChild) : route;
+}
diff --git a/apps/workbench-testing-app/src/app/workbench/workbench.component.html b/apps/workbench-testing-app/src/app/workbench/workbench.component.html
index e47767979..b642395b6 100644
--- a/apps/workbench-testing-app/src/app/workbench/workbench.component.html
+++ b/apps/workbench-testing-app/src/app/workbench/workbench.component.html
@@ -8,4 +8,8 @@
Move view...
+
+
+ Show view info
+
diff --git a/apps/workbench-testing-app/src/app/workbench/workbench.component.ts b/apps/workbench-testing-app/src/app/workbench/workbench.component.ts
index 16c675ded..79b62307b 100644
--- a/apps/workbench-testing-app/src/app/workbench/workbench.component.ts
+++ b/apps/workbench-testing-app/src/app/workbench/workbench.component.ts
@@ -18,6 +18,7 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {WorkbenchDialogService, WorkbenchModule, WorkbenchPart, WorkbenchRouter, WorkbenchService, WorkbenchView} from '@scion/workbench';
import {SciMaterialIconDirective} from '@scion/components.internal/material-icon';
import {ViewMoveDialogTestPageComponent} from '../test-pages/view-move-dialog-test-page/view-move-dialog-test-page.component';
+import {ViewInfoDialogComponent} from '../view-info-dialog/view-info-dialog.component';
@Component({
selector: 'app-workbench',
@@ -56,6 +57,13 @@ export class WorkbenchComponent implements OnDestroy {
});
}
+ protected onShowViewInfo(view: WorkbenchView): void {
+ this._dialogService.open(ViewInfoDialogComponent, {
+ inputs: {view},
+ cssClass: 'e2e-view-info',
+ });
+ }
+
/**
* If enabled, installs the handler to automatically open the start tab when the user closes the last tab.
*/
diff --git a/projects/scion/e2e-testing/src/view-tab-context-menu.po.ts b/projects/scion/e2e-testing/src/view-tab-context-menu.po.ts
index e4781fceb..4ce0db03f 100644
--- a/projects/scion/e2e-testing/src/view-tab-context-menu.po.ts
+++ b/projects/scion/e2e-testing/src/view-tab-context-menu.po.ts
@@ -20,10 +20,15 @@ export class ViewTabContextMenuPO {
closeAll: new ContextMenuItem(this.locator.locator('button.e2e-close-all-tabs')),
moveToNewWindow: new ContextMenuItem(this.locator.locator('button.e2e-move-to-new-window')),
moveView: new ContextMenuItem(this.locator.locator('button.e2e-move-view')),
+ showViewInfo: new ContextMenuItem(this.locator.locator('button.e2e-show-view-info')),
};
constructor(public locator: Locator) {
}
+
+ public async pressEscape(): Promise {
+ await this.locator.page().keyboard.press('Escape');
+ }
}
export class ContextMenuItem {
diff --git a/projects/scion/e2e-testing/src/view.po.ts b/projects/scion/e2e-testing/src/view.po.ts
index b54ff8903..16bd272bb 100644
--- a/projects/scion/e2e-testing/src/view.po.ts
+++ b/projects/scion/e2e-testing/src/view.po.ts
@@ -12,6 +12,8 @@ import {DomRect, fromRect, getCssClasses} from './helper/testing.util';
import {Locator} from '@playwright/test';
import {PartPO} from './part.po';
import {ViewTabPO} from './view-tab.po';
+import {ViewInfo, ViewInfoDialogPO} from './workbench/page-object/view-info-dialog.po';
+import {AppPO} from './app.po';
/**
* Handle for interacting with a workbench view.
@@ -31,6 +33,21 @@ export class ViewPO {
return this.tab.getViewId();
}
+ public async getInfo(): Promise {
+ const contextMenu = await this.tab.openContextMenu();
+ await contextMenu.menuItems.showViewInfo.click();
+
+ const dialog = new AppPO(this.locator.page()).dialog({cssClass: 'e2e-view-info'});
+ const dialogPage = new ViewInfoDialogPO(dialog);
+
+ try {
+ return await dialogPage.getInfo();
+ }
+ finally {
+ await dialog.close();
+ }
+ }
+
/**
* Handle to the part in which this view is contained.
*/
diff --git a/projects/scion/e2e-testing/src/workbench/dialog.e2e-spec.ts b/projects/scion/e2e-testing/src/workbench/dialog.e2e-spec.ts
index aca10025e..c19fb973c 100644
--- a/projects/scion/e2e-testing/src/workbench/dialog.e2e-spec.ts
+++ b/projects/scion/e2e-testing/src/workbench/dialog.e2e-spec.ts
@@ -157,6 +157,7 @@ test.describe('Workbench Dialog', () => {
// Expect context menu item to be disabled.
const viewTabContextMenu = await dialogOpenerPage.view.tab.openContextMenu();
await expect(viewTabContextMenu.menuItems.closeTab.locator).toBeDisabled();
+ await viewTabContextMenu.pressEscape();
// Expect closing the view via keystroke not to close the view.
await page.keyboard.press('Control+K');
diff --git a/projects/scion/e2e-testing/src/workbench/page-object/view-info-dialog.po.ts b/projects/scion/e2e-testing/src/workbench/page-object/view-info-dialog.po.ts
new file mode 100644
index 000000000..3dda1df1d
--- /dev/null
+++ b/projects/scion/e2e-testing/src/workbench/page-object/view-info-dialog.po.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-2024 Swiss Federal Railways
+ *
+ * This program and the accompanying materials are made
+ * available under the terms from the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+import {Locator} from '@playwright/test';
+import {SciKeyValuePO} from '../../@scion/components.internal/key-value.po';
+import {DialogPO} from '../../dialog.po';
+import {WorkbenchDialogPagePO} from './workbench-dialog-page.po';
+import {ViewState} from '@scion/workbench';
+import {Data, Params} from '@angular/router';
+
+/**
+ * Page object to interact with {@link ViewInfoDialogComponent}.
+ */
+export class ViewInfoDialogPO implements WorkbenchDialogPagePO {
+
+ public readonly locator: Locator;
+
+ constructor(public dialog: DialogPO) {
+ this.locator = this.dialog.locator.locator('app-view-info-dialog');
+ }
+
+ public async getInfo(): Promise {
+ const routeParams = this.locator.locator('sci-key-value.e2e-route-params');
+ const routeData = this.locator.locator('sci-key-value.e2e-route-data');
+ const state = this.locator.locator('sci-key-value.e2e-state');
+
+ return {
+ viewId: await this.locator.locator('span.e2e-view-id').innerText(),
+ partId: await this.locator.locator('span.e2e-part-id').innerText(),
+ title: await this.locator.locator('span.e2e-title').innerText(),
+ heading: await this.locator.locator('span.e2e-heading').innerText(),
+ urlSegments: await this.locator.locator('span.e2e-url-segments').innerText(),
+ routeParams: await routeParams.isVisible() ? await new SciKeyValuePO(routeParams).readEntries() : {},
+ routeData: await routeData.isVisible() ? await new SciKeyValuePO(routeData).readEntries() : {},
+ state: await state.isVisible() ? await new SciKeyValuePO(state).readEntries() : {},
+ };
+ }
+}
+
+export interface ViewInfo {
+ viewId: string;
+ partId: string;
+ title: string;
+ heading: string;
+ urlSegments: string;
+ routeParams: Params;
+ routeData: Data;
+ state: ViewState;
+}
diff --git a/projects/scion/e2e-testing/src/workbench/view.e2e-spec.ts b/projects/scion/e2e-testing/src/workbench/view.e2e-spec.ts
index a1d36f041..9e7234eae 100644
--- a/projects/scion/e2e-testing/src/workbench/view.e2e-spec.ts
+++ b/projects/scion/e2e-testing/src/workbench/view.e2e-spec.ts
@@ -289,11 +289,13 @@ test.describe('Workbench View', () => {
const contextMenu1 = await viewPage.view.tab.openContextMenu();
// Expect menu item to be enabled.
await expect(contextMenu1.menuItems.closeTab.locator).not.toBeDisabled();
+ await contextMenu1.pressEscape();
await viewPage.checkClosable(false);
const contextMenu2 = await viewPage.view.tab.openContextMenu();
// Expect menu item to be disabled.
await expect(contextMenu2.menuItems.closeTab.locator).toBeDisabled();
+ await contextMenu2.pressEscape();
});
test(`should not close 'non-closable' views via context menu 'Close all tabs'`, async ({appPO, workbenchNavigator}) => {