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) { +
+
Route Params
+ +
+} + +@if (route?.snapshot?.data | appNullIfEmpty; as data) { +
+
Route Data
+ +
+} + +@if (view.state | appNullIfEmpty; as state) { +
+
View 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}) => {