Skip to content

Commit

Permalink
fix(workbench/view): initialize microfrontend loaded into inactive view
Browse files Browse the repository at this point in the history
Navigating an inactive view to a microfrontend did not load the microfrontend, preventing the microfrontend from setting view properties such as title or CSS class(es).
  • Loading branch information
danielwiehl committed Jun 14, 2024
1 parent 64a2b16 commit 4a87f8d
Show file tree
Hide file tree
Showing 33 changed files with 2,139 additions and 230 deletions.
117 changes: 115 additions & 2 deletions projects/scion/e2e-testing/src/workbench-client/view.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import {expect} from '@playwright/test';
import {test} from '../fixtures';
import {ViewPagePO} from './page-object/view-page.po';
import {RouterPagePO} from './page-object/router-page.po';
import {RouterPagePO as StandaloneRouterPagePO} from '../workbench/page-object/router-page.po';
import {expectView} from '../matcher/view-matcher';
import {TextMessageBoxPagePO} from '../text-message-box-page.po';
import {expectMessageBox} from '../matcher/message-box-matcher';
import {ViewInfo} from '../workbench/page-object/view-info-dialog.po';

test.describe('Workbench View', () => {

Expand Down Expand Up @@ -830,7 +832,7 @@ test.describe('Workbench View', () => {
expect(view2ComponentId).toEqual(await viewPage2.getComponentInstanceId());
});

test('should update Angular bindings for active views', async ({appPO, microfrontendNavigator}) => {
test('should change detect active views after construction', async ({appPO, microfrontendNavigator}) => {
await appPO.navigateTo({microfrontendSupport: true});

const capability = await microfrontendNavigator.registerCapability('app1', {
Expand All @@ -855,9 +857,10 @@ test.describe('Workbench View', () => {
await expect.poll(() => viewPage.outlet.getCssClasses()).toContain('testee');
await expect.poll(() => viewPage.outlet.getCapabilityId()).toEqual(capability.metadata!.id);
await expect.poll(() => viewPage.outlet.getAppSymbolicName()).toEqual(capability.metadata!.appSymbolicName);
await expect(viewPage.view.tab.title).toHaveText('Testee');
});

test('should update Angular bindings for inactive views', async ({appPO, microfrontendNavigator}) => {
test('should change detect inactive views after construction', async ({appPO, microfrontendNavigator}) => {
await appPO.navigateTo({microfrontendSupport: true});

const capability = await microfrontendNavigator.registerCapability('app1', {
Expand All @@ -882,5 +885,115 @@ test.describe('Workbench View', () => {
await expect.poll(() => viewPage.outlet.getCssClasses()).toContain('testee');
await expect.poll(() => viewPage.outlet.getCapabilityId()).toEqual(capability.metadata!.id);
await expect.poll(() => viewPage.outlet.getAppSymbolicName()).toEqual(capability.metadata!.appSymbolicName);
await expect(viewPage.view.tab.title).toHaveText('Testee');
});

/**
* In this test, we have an inactive view which is navigated to a non-microfrontend component.
* This test verifies that when this view is navigated to a microfrontend, the microfrontend is loaded, e.g., to set the title of the view.
*/
test('should change detect view when navigating from inactive "standalone workbench" view to inactive microfrontend view', async ({appPO, workbenchNavigator, microfrontendNavigator}) => {
await appPO.navigateTo({microfrontendSupport: true});

// Register view 1.
await microfrontendNavigator.registerCapability('app1', {
type: 'view',
qualifier: {view: 'view'},
properties: {
path: 'test-view',
title: 'Microfrontend View',
},
});

const standaloneRouter = await workbenchNavigator.openInNewTab(StandaloneRouterPagePO);
const microfrontendRouter = await microfrontendNavigator.openInNewTab(RouterPagePO, 'app1');
const view = appPO.view({viewId: 'view.100'});

// Navigate to standalone page.
await standaloneRouter.view.tab.click();
await standaloneRouter.navigate(['test-pages/navigation-test-page', {title: 'Standalone View'}], {target: 'view.100', activate: false});
await expect.poll(() => view.getInfo()).toMatchObject(
{
viewId: 'view.100',
title: 'Standalone View',
} satisfies Partial<ViewInfo>,
);

// Navigate to microfrontend.
await microfrontendRouter.view.tab.click();
await microfrontendRouter.navigate({view: 'view'}, {target: 'view.100', activate: false});
await expect.poll(() => view.getInfo()).toMatchObject(
{
viewId: 'view.100',
title: 'Microfrontend View',
} satisfies Partial<ViewInfo>,
);

// Navigate to standalone page.
await standaloneRouter.view.tab.click();
await standaloneRouter.navigate(['test-pages/navigation-test-page', {title: 'Standalone View'}], {target: 'view.100', activate: false});
await expect.poll(() => view.getInfo()).toMatchObject(
{
viewId: 'view.100',
title: 'Standalone View',
} satisfies Partial<ViewInfo>,
);
});

/**
* In this test, we have an active view which is navigated to a non-microfrontend component.
* This test verifies that when this view is navigated to a microfrontend, the microfrontend is loaded, e.g., to set the title of the view.
*/
test('should change detect view when navigating from active "standalone workbench" view to active microfrontend view', async ({appPO, workbenchNavigator, microfrontendNavigator}) => {
await appPO.navigateTo({microfrontendSupport: true});

await workbenchNavigator.modifyLayout(layout => layout
.addPart('right', {align: 'right'})
.addView('view.100', {partId: 'right', activateView: true})
);

// Register view 1.
await microfrontendNavigator.registerCapability('app1', {
type: 'view',
qualifier: {view: 'view'},
properties: {
path: 'test-view',
title: 'Microfrontend View',
},
});

const standaloneRouter = await workbenchNavigator.openInNewTab(StandaloneRouterPagePO);
const microfrontendRouter = await microfrontendNavigator.openInNewTab(RouterPagePO, 'app1');
const view = appPO.view({viewId: 'view.100'});

// Navigate to standalone page.
await standaloneRouter.view.tab.click();
await standaloneRouter.navigate(['test-pages/navigation-test-page', {title: 'Standalone View'}], {target: 'view.100'});
await expect.poll(() => view.getInfo()).toMatchObject(
{
viewId: 'view.100',
title: 'Standalone View',
} satisfies Partial<ViewInfo>,
);

// Navigate to microfrontend.
await microfrontendRouter.view.tab.click();
await microfrontendRouter.navigate({view: 'view'}, {target: 'view.100'});
await expect.poll(() => view.getInfo()).toMatchObject(
{
viewId: 'view.100',
title: 'Microfrontend View',
} satisfies Partial<ViewInfo>,
);

// Navigate to standalone page.
await standaloneRouter.view.tab.click();
await standaloneRouter.navigate(['test-pages/navigation-test-page', {title: 'Standalone View'}], {target: 'view.100'});
await expect.poll(() => view.getInfo()).toMatchObject(
{
viewId: 'view.100',
title: 'Standalone View',
} satisfies Partial<ViewInfo>,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ test.describe('View Tabbar', () => {

await workbenchNavigator.createPerspective(factory => factory
.addPart('left')
.addPart('right', {align: 'right'}, {activate: true})
.addPart('right', {align: 'right'})
.addView('view.1', {partId: 'left'})
.addView('view.2', {partId: 'left', activateView: true})
.addView('view.3', {partId: 'left'})
Expand Down Expand Up @@ -165,7 +165,7 @@ test.describe('View Tabbar', () => {
direction: 'row',
ratio: .5,
}),
activePartId: 'left',
activePartId: 'right',
},
});
});
Expand Down
30 changes: 30 additions & 0 deletions projects/scion/e2e-testing/src/workbench/view.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,4 +776,34 @@ test.describe('Workbench View', () => {
// expect the component not to be constructed anew
await expect.poll(() => viewPage.getComponentInstanceId()).toEqual(componentInstanceId);
});

test('should change detect active views after construction', async ({appPO, workbenchNavigator}) => {
await appPO.navigateTo({microfrontendSupport: false});

const routerPage = await workbenchNavigator.openInNewTab(RouterPagePO);
await routerPage.navigate(['test-pages/navigation-test-page', {title: 'View Title'}], {
target: 'view.100',
activate: true,
cssClass: 'testee'
});

const viewPage = new ViewPagePO(appPO, {viewId: 'view.100'});
await expect.poll(() => viewPage.view.tab.getCssClasses()).toContain('testee');
await expect(viewPage.view.tab.title).toHaveText('View Title');
});

test('should change detect inactive views after construction', async ({appPO, workbenchNavigator}) => {
await appPO.navigateTo({microfrontendSupport: false});

const routerPage = await workbenchNavigator.openInNewTab(RouterPagePO);
await routerPage.navigate(['test-pages/navigation-test-page', {title: 'View Title'}], {
target: 'view.100',
activate: false,
cssClass: 'testee'
});

const viewPage = new ViewPagePO(appPO, {viewId: 'view.100'});
await expect.poll(() => viewPage.view.tab.getCssClasses()).toContain('testee');
await expect(viewPage.view.tab.title).toHaveText('View Title');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,128 @@ test.describe('Workbench Layout Migration', () => {
} satisfies Partial<ViewInfo>,
);
});

/**
* ## Given layout in version 4:
*
* PERIPHERAL AREA MAIN AREA PERIPHERAL AREA
* +-------------------------------+ +--------------------------------------------+ +-------------------------------+
* | Part: left | | Part: 6f09e6e2-b63a-4f0d-9ae1-06624fdb37c7 | | Part: right |
* | Views: [view.2, view.3] | | Views: [view.1] | | Views: [view.4] |
* | Active View: view.2 | | Active View: view.1 | | Active View: view.4 |
* | | +--------------------------------------------+ | |
* | | | Part: 1d94dcb6-76b6-47eb-b300-39448993d36b | | |
* | | | Views: [view.5] | | |
* | | | Active View: view.5 | | |
* +-------------------------------+ +--------------------------------------------+ +-------------------------------
* view.1: [path='test-view']
* view.2: [path='test-view']
* view.3: [path='', navigationHint='test-view']
* view.4: [path='test-view']
* view.5: [path='test-view']
*/
test('should migrate workbench layout v4 to the latest version', async ({appPO}) => {
await appPO.navigateTo({
url: '#/(view.1:test-view//view.2:test-view//view.4:test-view//view.5:test-view)?main_area=eyJyb290Ijp7InR5cGUiOiJNVHJlZU5vZGUiLCJjaGlsZDEiOnsidHlwZSI6Ik1QYXJ0IiwiaWQiOiI2ZjA5ZTZlMi1iNjNhLTRmMGQtOWFlMS0wNjYyNGZkYjM3YzciLCJzdHJ1Y3R1cmFsIjpmYWxzZSwidmlld3MiOlt7ImlkIjoidmlldy4xIiwibmF2aWdhdGlvbiI6e319XSwiYWN0aXZlVmlld0lkIjoidmlldy4xIn0sImNoaWxkMiI6eyJ0eXBlIjoiTVBhcnQiLCJpZCI6IjFkOTRkY2I2LTc2YjYtNDdlYi1iMzAwLTM5NDQ4OTkzZDM2YiIsInN0cnVjdHVyYWwiOmZhbHNlLCJ2aWV3cyI6W3siaWQiOiJ2aWV3LjUiLCJuYXZpZ2F0aW9uIjp7fX1dLCJhY3RpdmVWaWV3SWQiOiJ2aWV3LjUifSwiZGlyZWN0aW9uIjoiY29sdW1uIiwicmF0aW8iOjAuNX0sImFjdGl2ZVBhcnRJZCI6IjFkOTRkY2I2LTc2YjYtNDdlYi1iMzAwLTM5NDQ4OTkzZDM2YiJ9Ly80',
microfrontendSupport: false,
localStorage: {
'scion.workbench.perspective': 'empty',
'scion.workbench.perspectives.empty': 'eyJyZWZlcmVuY2VMYXlvdXQiOnsid29ya2JlbmNoR3JpZCI6ImV5SnliMjkwSWpwN0luUjVjR1VpT2lKTlVHRnlkQ0lzSW1sa0lqb2liV0ZwYmkxaGNtVmhJaXdpYzNSeWRXTjBkWEpoYkNJNmRISjFaU3dpZG1sbGQzTWlPbHRkZlN3aVlXTjBhWFpsVUdGeWRFbGtJam9pYldGcGJpMWhjbVZoSW4wdkx6UT0iLCJ2aWV3T3V0bGV0cyI6Int9In0sInVzZXJMYXlvdXQiOnsid29ya2JlbmNoR3JpZCI6ImV5SnliMjkwSWpwN0luUjVjR1VpT2lKTlZISmxaVTV2WkdVaUxDSmphR2xzWkRFaU9uc2lkSGx3WlNJNklrMVFZWEowSWl3aWFXUWlPaUpzWldaMElpd2ljM1J5ZFdOMGRYSmhiQ0k2ZEhKMVpTd2lkbWxsZDNNaU9sdDdJbWxrSWpvaWRtbGxkeTR5SWl3aWJtRjJhV2RoZEdsdmJpSTZlMzE5TEhzaWFXUWlPaUoyYVdWM0xqTWlMQ0p1WVhacFoyRjBhVzl1SWpwN0ltaHBiblFpT2lKMFpYTjBMWFpwWlhjaWZYMWRMQ0poWTNScGRtVldhV1YzU1dRaU9pSjJhV1YzTGpJaWZTd2lZMmhwYkdReUlqcDdJblI1Y0dVaU9pSk5WSEpsWlU1dlpHVWlMQ0pqYUdsc1pERWlPbnNpZEhsd1pTSTZJazFRWVhKMElpd2lhV1FpT2lKdFlXbHVMV0Z5WldFaUxDSnpkSEoxWTNSMWNtRnNJanAwY25WbExDSjJhV1YzY3lJNlcxMTlMQ0pqYUdsc1pESWlPbnNpZEhsd1pTSTZJazFRWVhKMElpd2lhV1FpT2lKeWFXZG9kQ0lzSW5OMGNuVmpkSFZ5WVd3aU9uUnlkV1VzSW5acFpYZHpJanBiZXlKcFpDSTZJblpwWlhjdU5DSXNJbTVoZG1sbllYUnBiMjRpT250OWZWMHNJbUZqZEdsMlpWWnBaWGRKWkNJNkluWnBaWGN1TkNKOUxDSmthWEpsWTNScGIyNGlPaUp5YjNjaUxDSnlZWFJwYnlJNk1DNDNOWDBzSW1ScGNtVmpkR2x2YmlJNkluSnZkeUlzSW5KaGRHbHZJam93TGpJMWZTd2lZV04wYVhabFVHRnlkRWxrSWpvaWJHVm1kQ0o5THk4MCIsInZpZXdPdXRsZXRzIjoie1widmlldy4yXCI6W3tcInBhdGhcIjpcInRlc3Qtdmlld1wiLFwicGFyYW1ldGVyc1wiOnt9fV0sXCJ2aWV3LjNcIjpbXSxcInZpZXcuNFwiOlt7XCJwYXRoXCI6XCJ0ZXN0LXZpZXdcIixcInBhcmFtZXRlcnNcIjp7fX1dfSJ9fS8vMg==',
},
});

await expect(appPO.workbench).toEqualWorkbenchLayout({
workbenchGrid: {
root: new MTreeNode({
direction: 'row',
ratio: .25,
child1: new MPart({
id: 'left',
views: [{id: 'view.2'}, {id: 'view.3'}],
activeViewId: 'view.2',
}),
child2: new MTreeNode({
direction: 'row',
ratio: .75,
child1: new MPart({
id: MAIN_AREA,
}),
child2: new MPart({
id: 'right',
views: [{id: 'view.4'}],
activeViewId: 'view.4',
}),
}),
}),
activePartId: 'left',
},
mainAreaGrid: {
root: new MTreeNode({
direction: 'column',
ratio: .5,
child1: new MPart({
id: '6f09e6e2-b63a-4f0d-9ae1-06624fdb37c7',
views: [{id: 'view.1'}],
activeViewId: 'view.1',
}),
child2: new MPart({
id: '1d94dcb6-76b6-47eb-b300-39448993d36b',
views: [{id: 'view.5'}],
activeViewId: 'view.5',
}),
}),
activePartId: '1d94dcb6-76b6-47eb-b300-39448993d36b',
},
});

const viewPage1 = new ViewPagePO(appPO, {viewId: 'view.1'});
await viewPage1.view.tab.click();
await expectView(viewPage1).toBeActive();
await expect.poll(() => viewPage1.view.getInfo()).toMatchObject(
{
routeData: {path: 'test-view', navigationHint: ''},
urlSegments: 'test-view',
} satisfies Partial<ViewInfo>,
);

const viewPage2 = new ViewPagePO(appPO, {viewId: 'view.2'});
await viewPage2.view.tab.click();
await expectView(viewPage2).toBeActive();
await expect.poll(() => viewPage2.view.getInfo()).toMatchObject(
{
routeData: {path: 'test-view', navigationHint: ''},
urlSegments: 'test-view',
} satisfies Partial<ViewInfo>,
);

const viewPage3 = new ViewPagePO(appPO, {viewId: 'view.3'});
await viewPage3.view.tab.click();
await expectView(viewPage3).toBeActive();
await expect.poll(() => viewPage3.view.getInfo()).toMatchObject(
{
routeData: {path: '', navigationHint: 'test-view'},
urlSegments: '',
} satisfies Partial<ViewInfo>,
);

const viewPage4 = new ViewPagePO(appPO, {viewId: 'view.4'});
await viewPage4.view.tab.click();
await expectView(viewPage4).toBeActive();
await expect.poll(() => viewPage4.view.getInfo()).toMatchObject(
{
routeData: {path: 'test-view', navigationHint: ''},
urlSegments: 'test-view',
} satisfies Partial<ViewInfo>,
);

const viewPage5 = new ViewPagePO(appPO, {viewId: 'view.5'});
await viewPage5.view.tab.click();
await expectView(viewPage5).toBeActive();
await expect.poll(() => viewPage5.view.getInfo()).toMatchObject(
{
routeData: {path: 'test-view', navigationHint: ''},
urlSegments: 'test-view',
} satisfies Partial<ViewInfo>,
);
});
});
25 changes: 25 additions & 0 deletions projects/scion/workbench/src/lib/common/uid.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 {UUID} from '@scion/toolkit/uuid';

/**
* Generates a UID (unique identifier).
*/
export const UID = {
/**
* Generates a UID (unique identifier) with length 8.
*/
randomUID: (): string => {
return UUID.randomUUID().substring(0, 8);
},
};


23 changes: 0 additions & 23 deletions projects/scion/workbench/src/lib/common/uuid.util.ts

This file was deleted.

4 changes: 2 additions & 2 deletions projects/scion/workbench/src/lib/dialog/ɵworkbench-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {WorkbenchDialogHeaderDirective} from './dialog-header/workbench-dialog-h
import {Disposable} from '../common/disposable';
import {Blockable} from '../glass-pane/blockable';
import {Blocking} from '../glass-pane/blocking';
import {randomUUID} from '../common/uuid.util';
import {UUID} from '@scion/toolkit/uuid';

/** @inheritDoc */
export class ɵWorkbenchDialog<R = unknown> implements WorkbenchDialog<R>, Blockable, Blocking {
Expand All @@ -57,7 +57,7 @@ export class ɵWorkbenchDialog<R = unknown> implements WorkbenchDialog<R>, Block
/**
* Unique identity of this dialog.
*/
public readonly id = randomUUID();
public readonly id = UUID.randomUUID();
/**
* Indicates whether this dialog is blocked by other dialog(s) that overlay this dialog.
*/
Expand Down

0 comments on commit 4a87f8d

Please sign in to comment.