diff --git a/projects/e2e/workbench/src/view-tab-bar.e2e-spec.ts b/projects/e2e/workbench/src/view-tab-bar.e2e-spec.ts index 7c0db8e46..106ed8e09 100644 --- a/projects/e2e/workbench/src/view-tab-bar.e2e-spec.ts +++ b/projects/e2e/workbench/src/view-tab-bar.e2e-spec.ts @@ -11,11 +11,13 @@ import { AppPO } from './page-object/app.po'; import { browser } from 'protractor'; import { WelcomePagePO } from './page-object/welcome-page.po'; +import { ViewNavigationPO } from './page-object/view-navigation.po'; describe('ViewTabBar', () => { const appPO = new AppPO(); const welcomePagePO = new WelcomePagePO(); + const viewNavigationPO = new ViewNavigationPO(); beforeEach(async () => { await browser.get('/'); @@ -48,4 +50,66 @@ describe('ViewTabBar', () => { await expect(appPO.getViewTabCount()).toEqual(0); await expect(appPO.isViewTabBarShowing()).toBeTruthy(); }); + + it('should activate the most recent view when closing a view', async () => { + await viewNavigationPO.navigateTo(); + await expect(appPO.getViewTabCount()).toEqual(1); + + // open view-1 + await viewNavigationPO.enterPath('view'); + await viewNavigationPO.enterMatrixParams({viewCssClass: 'e2e-view-1', viewTitle: 'view-1'}); + await viewNavigationPO.checkActivateIfPresent(true); + await viewNavigationPO.selectTarget('blank'); + await viewNavigationPO.navigate(); + + await expect(appPO.findViewTab('e2e-view-1').isActive()).toBeTruthy(); + await expect(appPO.getViewTabCount()).toEqual(2); + + // open view-2 + await viewNavigationPO.activateViewTab(); + await viewNavigationPO.enterPath('view'); + await viewNavigationPO.enterMatrixParams({viewCssClass: 'e2e-view-2', viewTitle: 'view-2'}); + await viewNavigationPO.checkActivateIfPresent(true); + await viewNavigationPO.selectTarget('blank'); + await viewNavigationPO.navigate(); + + await expect(appPO.findViewTab('e2e-view-2').isActive()).toBeTruthy(); + await expect(appPO.getViewTabCount()).toEqual(3); + + // open view-3 + await viewNavigationPO.activateViewTab(); + await viewNavigationPO.enterPath('view'); + await viewNavigationPO.enterMatrixParams({viewCssClass: 'e2e-view-3', viewTitle: 'view-3'}); + await viewNavigationPO.checkActivateIfPresent(true); + await viewNavigationPO.selectTarget('blank'); + await viewNavigationPO.navigate(); + + await expect(appPO.findViewTab('e2e-view-3').isActive()).toBeTruthy(); + await expect(appPO.getViewTabCount()).toEqual(4); + + // activate view-2 + await appPO.findViewTab('e2e-view-2').click(); + + await expect(appPO.findViewTab('e2e-view-2').isActive()).toBeTruthy(); + + // activate view-1 + await appPO.findViewTab('e2e-view-1').click(); + + await expect(appPO.findViewTab('e2e-view-1').isActive()).toBeTruthy(); + + // activate view-3 + await appPO.findViewTab('e2e-view-3').click(); + + await expect(appPO.findViewTab('e2e-view-3').isActive()).toBeTruthy(); + + // close view-3 + await appPO.findViewTab('e2e-view-3').close(); + + await expect(appPO.findViewTab('e2e-view-1').isActive()).toBeTruthy(); + + // close view-1 + await appPO.findViewTab('e2e-view-1').close(); + + await expect(appPO.findViewTab('e2e-view-2').isActive()).toBeTruthy(); + }); }); diff --git a/projects/scion/workbench-application-platform/src/lib/spec/manifest-collector.spec.ts b/projects/scion/workbench-application-platform/src/lib/spec/manifest-collector.spec.ts index e99f82ccd..43af75ab1 100644 --- a/projects/scion/workbench-application-platform/src/lib/spec/manifest-collector.spec.ts +++ b/projects/scion/workbench-application-platform/src/lib/spec/manifest-collector.spec.ts @@ -13,10 +13,10 @@ import { NgModule } from '@angular/core'; import { WorkbenchApplicationPlatformModule } from '../workbench-application-platform.module'; import { NullErrorHandler } from '../core/null-error-handler.service'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { WorkbenchModule } from '@scion/workbench'; import { RouterTestingModule } from '@angular/router/testing'; import { ApplicationRegistry } from '../core/application-registry.service'; import { ApplicationManifest } from '../core/metadata'; +import { WorkbenchTestingModule } from '@scion/workbench'; describe('ManifestCollector', () => { @@ -85,7 +85,7 @@ function createApplicationManifest(app: { appName: string }): ApplicationManifes imports: [ HttpClientTestingModule, RouterTestingModule.withRoutes([]), - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), WorkbenchApplicationPlatformModule.forRoot({ errorHandler: NullErrorHandler, applicationConfig: [ diff --git a/projects/scion/workbench-application-platform/src/lib/spec/workbench-application-platform.module.spec.ts b/projects/scion/workbench-application-platform/src/lib/spec/workbench-application-platform.module.spec.ts index 2debc1454..54ff63342 100644 --- a/projects/scion/workbench-application-platform/src/lib/spec/workbench-application-platform.module.spec.ts +++ b/projects/scion/workbench-application-platform/src/lib/spec/workbench-application-platform.module.spec.ts @@ -12,16 +12,16 @@ import { fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { NgModule, NgModuleFactoryLoader } from '@angular/core'; import { WorkbenchApplicationPlatformModule } from '../workbench-application-platform.module'; import { NullErrorHandler } from '../core/null-error-handler.service'; -import { WorkbenchModule } from '@scion/workbench'; import { RouterTestingModule, SpyNgModuleFactoryLoader } from '@angular/router/testing'; import { Router } from '@angular/router'; +import { WorkbenchTestingModule } from '@scion/workbench'; describe('WorkbenchApplicationPlatform', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), WorkbenchApplicationPlatformModule.forRoot({errorHandler: NullErrorHandler, applicationConfig: []}), RouterTestingModule.withRoutes([{path: 'lazy-module-forroot', loadChildren: './lazy-forroot.module'}]), ], diff --git a/projects/scion/workbench/src/lib/spec/activities-from-lazy-loaded-modules.spec.ts b/projects/scion/workbench/src/lib/spec/activities-from-lazy-loaded-modules.spec.ts index 2926c6e16..37679116f 100644 --- a/projects/scion/workbench/src/lib/spec/activities-from-lazy-loaded-modules.spec.ts +++ b/projects/scion/workbench/src/lib/spec/activities-from-lazy-loaded-modules.spec.ts @@ -10,7 +10,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { Component, NgModule, NgModuleFactoryLoader } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.spec'; import { RouterTestingModule, SpyNgModuleFactoryLoader } from '@angular/router/testing'; import { Router, RouteReuseStrategy, RouterModule } from '@angular/router'; @@ -18,6 +17,7 @@ import { CommonModule } from '@angular/common'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { advance, clickElement } from './util/util.spec'; import { ActivityPartComponent } from '../activity-part/activity-part.component'; +import { WorkbenchTestingModule } from './workbench-testing.module'; /** * @@ -117,7 +117,7 @@ class AppComponent { @NgModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), NoopAnimationsModule, RouterTestingModule.withRoutes([ {path: 'feature-a', loadChildren: './feature-a/feature-a.module'}, diff --git a/projects/scion/workbench/src/lib/spec/activity-action.spec.ts b/projects/scion/workbench/src/lib/spec/activity-action.spec.ts index e24815552..696378c7b 100644 --- a/projects/scion/workbench/src/lib/spec/activity-action.spec.ts +++ b/projects/scion/workbench/src/lib/spec/activity-action.spec.ts @@ -10,7 +10,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { Component, NgModule } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { RouterTestingModule } from '@angular/router/testing'; import { Router, RouteReuseStrategy } from '@angular/router'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -19,6 +18,7 @@ import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.sp import { ActivityPartComponent } from '../activity-part/activity-part.component'; import { By } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; +import { WorkbenchTestingModule } from './workbench-testing.module'; /** * @@ -129,7 +129,7 @@ class Activity3Component { @NgModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), NoopAnimationsModule, CommonModule, RouterTestingModule.withRoutes([ diff --git a/projects/scion/workbench/src/lib/spec/activity-guard.spec.ts b/projects/scion/workbench/src/lib/spec/activity-guard.spec.ts index 4737e7dc7..a3e49ce71 100644 --- a/projects/scion/workbench/src/lib/spec/activity-guard.spec.ts +++ b/projects/scion/workbench/src/lib/spec/activity-guard.spec.ts @@ -10,7 +10,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { Component, Injectable, NgModule } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { RouterTestingModule } from '@angular/router/testing'; import { ActivatedRouteSnapshot, CanActivate, Router, RouteReuseStrategy, RouterStateSnapshot } from '@angular/router'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -20,6 +19,7 @@ import { ActivityPartComponent } from '../activity-part/activity-part.component' import { By } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; import { Observable } from 'rxjs'; +import { WorkbenchTestingModule } from './workbench-testing.module'; /** * @@ -126,7 +126,7 @@ class Activity2CanActivate implements CanActivate { @NgModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), NoopAnimationsModule, CommonModule, RouterTestingModule.withRoutes([ diff --git a/projects/scion/workbench/src/lib/spec/activity.spec.ts b/projects/scion/workbench/src/lib/spec/activity.spec.ts index 460260ff6..ebb317357 100644 --- a/projects/scion/workbench/src/lib/spec/activity.spec.ts +++ b/projects/scion/workbench/src/lib/spec/activity.spec.ts @@ -10,7 +10,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { Component, NgModule, OnDestroy } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { RouterTestingModule } from '@angular/router/testing'; import { ActivatedRoute, ParamMap, Router, RouteReuseStrategy } from '@angular/router'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -20,6 +19,7 @@ import { Subject } from 'rxjs'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; import { ActivityPartComponent } from '../activity-part/activity-part.component'; +import { WorkbenchTestingModule } from './workbench-testing.module'; describe('Activity part', () => { @@ -146,7 +146,7 @@ class Activity2Component { @NgModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), NoopAnimationsModule, RouterTestingModule.withRoutes([ {path: 'activity-debug', component: ActivityDebugComponent}, diff --git a/projects/scion/workbench/src/lib/spec/lazy-loaded-view-injection.spec.ts b/projects/scion/workbench/src/lib/spec/lazy-loaded-view-injection.spec.ts index f20a8209a..a57c8c9bc 100644 --- a/projects/scion/workbench/src/lib/spec/lazy-loaded-view-injection.spec.ts +++ b/projects/scion/workbench/src/lib/spec/lazy-loaded-view-injection.spec.ts @@ -10,7 +10,6 @@ import { async, fakeAsync, inject, TestBed } from '@angular/core/testing'; import { Component, Inject, Injectable, InjectionToken, NgModule, NgModuleFactoryLoader, Optional } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.spec'; import { RouterTestingModule, SpyNgModuleFactoryLoader } from '@angular/router/testing'; import { Router, RouterModule } from '@angular/router'; @@ -20,6 +19,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { advance, clickElement } from './util/util.spec'; import { ActivityPartComponent } from '../activity-part/activity-part.component'; import { By } from '@angular/platform-browser'; +import { WorkbenchTestingModule } from './workbench-testing.module'; /** * @@ -144,7 +144,7 @@ export class FeatureService { @NgModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), NoopAnimationsModule, RouterTestingModule.withRoutes([ {path: 'feature', loadChildren: './feature/feature.module'}, diff --git a/projects/scion/workbench/src/lib/spec/route-resolve.spec.ts b/projects/scion/workbench/src/lib/spec/route-resolve.spec.ts index 2ad917809..161e6717c 100644 --- a/projects/scion/workbench/src/lib/spec/route-resolve.spec.ts +++ b/projects/scion/workbench/src/lib/spec/route-resolve.spec.ts @@ -11,7 +11,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { Component, NgModule } from '@angular/core'; import { ViewPartGridComponent } from '../view-part-grid/view-part-grid.component'; -import { WorkbenchModule } from '../workbench.module'; import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.spec'; import { RouterTestingModule } from '@angular/router/testing'; import { Router } from '@angular/router'; @@ -19,6 +18,7 @@ import { InternalWorkbenchRouter, WorkbenchRouter } from '../routing/workbench-r import { advance } from './util/util.spec'; import { WorkbenchViewRegistry } from '../workbench-view-registry.service'; import { WorkbenchView } from '../workbench.model'; +import { WorkbenchTestingModule } from './workbench-testing.module'; describe('WbRouter', () => { @@ -111,7 +111,7 @@ class ViewComponent { @NgModule({ declarations: [ViewComponent], imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), RouterTestingModule.withRoutes([ {path: 'path/to/view', component: ViewComponent}, {path: 'path/to/view-1', component: ViewComponent}, diff --git a/projects/scion/workbench/src/lib/spec/router-navigate.spec.ts b/projects/scion/workbench/src/lib/spec/router-navigate.spec.ts index 4d0cfc2e6..c2742a25b 100644 --- a/projects/scion/workbench/src/lib/spec/router-navigate.spec.ts +++ b/projects/scion/workbench/src/lib/spec/router-navigate.spec.ts @@ -10,7 +10,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { Component, NgModule, NgModuleFactoryLoader } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { RouterTestingModule, SpyNgModuleFactoryLoader } from '@angular/router/testing'; import { Router, RouterModule } from '@angular/router'; import { WorkbenchRouter } from '../routing/workbench-router.service'; @@ -18,6 +17,7 @@ import { CommonModule } from '@angular/common'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { advance, clickElement } from './util/util.spec'; import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.spec'; +import { WorkbenchTestingModule } from './workbench-testing.module'; /** * Testsetup: @@ -306,7 +306,7 @@ class AppComponent { @NgModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), NoopAnimationsModule, RouterTestingModule.withRoutes([ {path: 'feature-a', loadChildren: './feature-a/feature-a.module'}, @@ -376,7 +376,7 @@ class FeatureA_View2Component { @NgModule({ imports: [ CommonModule, - WorkbenchModule.forChild(), + WorkbenchTestingModule.forChild(), RouterModule.forChild([ {path: '', component: FeatureA_EntryComponent}, {path: 'view-1', component: FeatureA_View1Component}, @@ -442,7 +442,7 @@ class FeatureB_View2Component { @NgModule({ imports: [ CommonModule, - WorkbenchModule.forChild(), + WorkbenchTestingModule.forChild(), RouterModule.forChild([ {path: '', component: FeatureB_EntryComponent}, {path: 'view-1', component: FeatureB_View1Component}, diff --git a/projects/scion/workbench/src/lib/spec/view-part-grid.component.spec.ts b/projects/scion/workbench/src/lib/spec/view-part-grid.component.spec.ts index 0f44a68d5..67eda7605 100644 --- a/projects/scion/workbench/src/lib/spec/view-part-grid.component.spec.ts +++ b/projects/scion/workbench/src/lib/spec/view-part-grid.component.spec.ts @@ -11,13 +11,13 @@ import { async, fakeAsync, inject, TestBed } from '@angular/core/testing'; import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.spec'; import { Component, NgModule } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { ViewPartGridComponent } from '../view-part-grid/view-part-grid.component'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { WorkbenchViewPartRegistry } from '../view-part-grid/workbench-view-part-registry.service'; import { WorkbenchRouter } from '../routing/workbench-router.service'; import { advance } from './util/util.spec'; +import { WorkbenchTestingModule } from './workbench-testing.module'; describe('ViewPartGridComponent', () => { @@ -504,7 +504,7 @@ class View3Component { @NgModule({ declarations: [View1Component, View2Component, View3Component], imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), RouterTestingModule.withRoutes([ {path: 'view-1', component: View1Component}, {path: 'view-2', component: View2Component}, diff --git a/projects/scion/workbench/src/lib/spec/view-part-grid.spec.ts b/projects/scion/workbench/src/lib/spec/view-part-grid.spec.ts index cd53a0ade..9fee5fa6e 100644 --- a/projects/scion/workbench/src/lib/spec/view-part-grid.spec.ts +++ b/projects/scion/workbench/src/lib/spec/view-part-grid.spec.ts @@ -12,6 +12,9 @@ import { NgModule } from '@angular/core'; import { async, inject, TestBed } from '@angular/core/testing'; import { ViewPartGridSerializerService, ViewPartInfoArray } from '../view-part-grid/view-part-grid-serializer.service'; import { ViewPartGrid } from '../view-part-grid/view-part-grid.model'; +import { WorkbenchViewRegistry } from '../workbench-view-registry.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { WorkbenchTestingModule } from './workbench-testing.module'; describe('ViewPartGrid', () => { @@ -22,17 +25,17 @@ describe('ViewPartGrid', () => { }); })); - it('allows to set a root viewpart', inject([ViewPartGridSerializerService], (serializer: ViewPartGridSerializerService) => { + it('allows to set a root viewpart', inject([ViewPartGridSerializerService, WorkbenchViewRegistry], (serializer: ViewPartGridSerializerService, registry: WorkbenchViewRegistry) => { const rootViewPart: ViewPartInfoArray = ['viewPart-root']; - const testee = new ViewPartGrid(serializer.serializeGrid(rootViewPart), serializer); + const testee = new ViewPartGrid(serializer.serializeGrid(rootViewPart), serializer, registry); expect(testee.root).toEqual(rootViewPart); })); - it('allows to add a sibling viewpart', inject([ViewPartGridSerializerService], (serializer: ViewPartGridSerializerService) => { + it('allows to add a sibling viewpart', inject([ViewPartGridSerializerService, WorkbenchViewRegistry], (serializer: ViewPartGridSerializerService, registry: WorkbenchViewRegistry) => { const leftViewPart: ViewPartInfoArray = ['viewPart-left', 'view-2', 'view-1', 'view-2', 'view-3']; - const grid1 = new ViewPartGrid(serializer.serializeGrid(leftViewPart), serializer); + const grid1 = new ViewPartGrid(serializer.serializeGrid(leftViewPart), serializer, registry); const grid2 = grid1.addSiblingViewPart('east', 'viewPart-left', 'viewPart-right'); expect(grid1.root).toEqual(leftViewPart); // expect that grid1 did not change (immutable) @@ -45,7 +48,7 @@ describe('ViewPartGrid', () => { }); })); - it('allows to remove the right sibling viewpart', inject([ViewPartGridSerializerService], (serializer: ViewPartGridSerializerService) => { + it('allows to remove the right sibling viewpart', inject([ViewPartGridSerializerService, WorkbenchViewRegistry], (serializer: ViewPartGridSerializerService, registry: WorkbenchViewRegistry) => { const leftViewPart = ['viewPart-left', 'view-2', 'view-1', 'view-2', 'view-3']; const rightViewPart = ['viewPart-right', 'view-5', 'view-4', 'view-5', 'view-6']; @@ -55,7 +58,7 @@ describe('ViewPartGrid', () => { sash2: rightViewPart, splitter: .5, hsplit: false, - }), serializer); + }), serializer, registry); const grid2 = grid1.removeViewPart('viewPart-left'); expect(grid1.root).toEqual({ // expect that grid1 did not change (immutable) @@ -68,7 +71,7 @@ describe('ViewPartGrid', () => { expect(grid2.root).toEqual(rightViewPart); })); - it('allows to remove the left sibling viewpart', inject([ViewPartGridSerializerService], (serializer: ViewPartGridSerializerService) => { + it('allows to remove the left sibling viewpart', inject([ViewPartGridSerializerService, WorkbenchViewRegistry], (serializer: ViewPartGridSerializerService, registry: WorkbenchViewRegistry) => { const leftViewPart = ['viewPart-left', 'view-2', 'view-1', 'view-2', 'view-3']; const rightViewPart = ['viewPart-right', 'view-5', 'view-4', 'view-5', 'view-6']; @@ -78,7 +81,7 @@ describe('ViewPartGrid', () => { sash2: rightViewPart, splitter: .5, hsplit: false, - }), serializer); + }), serializer, registry); const grid2 = grid1.removeViewPart('viewPart-right'); @@ -92,9 +95,9 @@ describe('ViewPartGrid', () => { expect(grid2.root).toEqual(leftViewPart); })); - it('allows to remove the root viewpart', inject([ViewPartGridSerializerService], (serializer: ViewPartGridSerializerService) => { + it('allows to remove the root viewpart', inject([ViewPartGridSerializerService, WorkbenchViewRegistry], (serializer: ViewPartGridSerializerService, registry: WorkbenchViewRegistry) => { const rootViewPart = ['viewPart-root', 'view-2', 'view-1', 'view-2', 'view-3']; - const grid1 = new ViewPartGrid(serializer.serializeGrid(rootViewPart), serializer); + const grid1 = new ViewPartGrid(serializer.serializeGrid(rootViewPart), serializer, registry); const grid2 = grid1.removeViewPart('viewPart-root'); expect(grid1.root).toEqual(rootViewPart); // expect that grid1 did not change (immutable) @@ -114,7 +117,7 @@ describe('ViewPartGrid', () => { * +---+---+---+---+ * */ - it('allows to create a grid which consists of 6 viewparts', inject([ViewPartGridSerializerService], (serializer: ViewPartGridSerializerService) => { + it('allows to create a grid which consists of 6 viewparts', inject([ViewPartGridSerializerService, WorkbenchViewRegistry], (serializer: ViewPartGridSerializerService, registry: WorkbenchViewRegistry) => { const viewPart_1 = ['viewPart-1']; const viewPart_2 = ['viewPart-2']; const viewPart_3 = ['viewPart-3']; @@ -123,7 +126,7 @@ describe('ViewPartGrid', () => { const viewPart_6 = ['viewPart-6']; // Set ViewPart 1 as root viewpart - const grid1 = new ViewPartGrid(serializer.serializeGrid(viewPart_1), serializer); + const grid1 = new ViewPartGrid(serializer.serializeGrid(viewPart_1), serializer, registry); const expectedGrid1 = viewPart_1; // Add ViewPart 2 to the east of ViewPart 2 @@ -340,7 +343,10 @@ describe('ViewPartGrid', () => { ****************************************************************************************************/ @NgModule({ - providers: [ViewPartGridSerializerService], + imports: [ + WorkbenchTestingModule.forRoot(), + RouterTestingModule, + ], }) class AppTestModule { } diff --git a/projects/scion/workbench/src/lib/spec/view.component.spec.ts b/projects/scion/workbench/src/lib/spec/view.component.spec.ts index 212ba7408..3cd73cb88 100644 --- a/projects/scion/workbench/src/lib/spec/view.component.spec.ts +++ b/projects/scion/workbench/src/lib/spec/view.component.spec.ts @@ -11,7 +11,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { NgModule, Type } from '@angular/core'; import { ViewPartGridComponent } from '../view-part-grid/view-part-grid.component'; -import { WorkbenchModule } from '../workbench.module'; import { WorkbenchService } from '../workbench.service'; import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.spec'; import { By } from '@angular/platform-browser'; @@ -23,6 +22,7 @@ import { WorkbenchView } from '../workbench.model'; import { WorkbenchViewRegistry } from '../workbench-view-registry.service'; import { WorkbenchRouter } from '../routing/workbench-router.service'; import { advance } from './util/util.spec'; +import { WorkbenchTestingModule } from './workbench-testing.module'; describe('ViewComponent', () => { @@ -381,7 +381,7 @@ describe('ViewComponent', () => { @NgModule({ declarations: [SpecView1Component, SpecView2Component], imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), RouterTestingModule.withRoutes([ {path: 'view-1', component: SpecView1Component}, {path: 'view-2', component: SpecView2Component}, diff --git a/projects/scion/workbench/src/lib/spec/views-from-lazy-loaded-modules.spec.ts b/projects/scion/workbench/src/lib/spec/views-from-lazy-loaded-modules.spec.ts index f99887434..8be627ec7 100644 --- a/projects/scion/workbench/src/lib/spec/views-from-lazy-loaded-modules.spec.ts +++ b/projects/scion/workbench/src/lib/spec/views-from-lazy-loaded-modules.spec.ts @@ -10,7 +10,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { Component, NgModule, NgModuleFactoryLoader } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.spec'; import { RouterTestingModule, SpyNgModuleFactoryLoader } from '@angular/router/testing'; import { Router, RouterModule } from '@angular/router'; @@ -18,6 +17,7 @@ import { WorkbenchRouter } from '../routing/workbench-router.service'; import { CommonModule } from '@angular/common'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { advance } from './util/util.spec'; +import { WorkbenchTestingModule } from './workbench-testing.module'; /** * @@ -101,7 +101,7 @@ class AppComponent { @NgModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), NoopAnimationsModule, RouterTestingModule.withRoutes([ {path: 'feature-a', loadChildren: './feature-a/feature-a.module'}, diff --git a/projects/scion/workbench/src/lib/spec/wb-route-reuse-strategy.spec.ts b/projects/scion/workbench/src/lib/spec/wb-route-reuse-strategy.spec.ts index 7ae7f0bf9..fe0802d16 100644 --- a/projects/scion/workbench/src/lib/spec/wb-route-reuse-strategy.spec.ts +++ b/projects/scion/workbench/src/lib/spec/wb-route-reuse-strategy.spec.ts @@ -10,7 +10,6 @@ import { async, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { Component, NgModule, NgModuleFactoryLoader } from '@angular/core'; -import { WorkbenchModule } from '../workbench.module'; import { expect, jasmineCustomMatchers } from './util/jasmine-custom-matchers.spec'; import { RouterTestingModule, SpyNgModuleFactoryLoader } from '@angular/router/testing'; import { Router, RouteReuseStrategy, RouterModule } from '@angular/router'; @@ -19,6 +18,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { advance, clickElement } from './util/util.spec'; import { ActivityPartComponent } from '../activity-part/activity-part.component'; import { By } from '@angular/platform-browser'; +import { WorkbenchTestingModule } from './workbench-testing.module'; /** * @@ -174,7 +174,7 @@ class App_Activity2_Component { @NgModule({ imports: [ - WorkbenchModule.forRoot(), + WorkbenchTestingModule.forRoot(), NoopAnimationsModule, RouterTestingModule.withRoutes([ {path: 'app/activity-1', component: App_Activity1_Component}, diff --git a/projects/scion/workbench/src/lib/spec/workbench-testing.module.ts b/projects/scion/workbench/src/lib/spec/workbench-testing.module.ts new file mode 100644 index 000000000..71e466586 --- /dev/null +++ b/projects/scion/workbench/src/lib/spec/workbench-testing.module.ts @@ -0,0 +1,37 @@ +import { Injectable, ModuleWithProviders, NgModule } from '@angular/core'; +import { ViewActivationInstantProvider } from '../view-activation-instant-provider.service'; +import { WorkbenchModule } from '../workbench.module'; +import { WorkbenchConfig } from '../workbench.config'; + +@Injectable() +export class ViewActivationTestingInstantProvider implements ViewActivationInstantProvider { + + private _counter = 0; + + public get instant(): number { + return this._counter++; + } +} + +/** + * Sets up the workbench to be used for testing. + */ +@NgModule({ + exports: [WorkbenchModule], +}) +export class WorkbenchTestingModule { + + public static forRoot(config: WorkbenchConfig = {}): ModuleWithProviders { + return { + ngModule: WorkbenchTestingModule, + providers: [ + WorkbenchModule.forRoot(config).providers, + {provide: ViewActivationInstantProvider, useClass: ViewActivationTestingInstantProvider}, + ], + }; + } + + public static forChild(): ModuleWithProviders { + return WorkbenchModule.forChild(); + } +} diff --git a/projects/scion/workbench/src/lib/view-activation-instant-provider.service.ts b/projects/scion/workbench/src/lib/view-activation-instant-provider.service.ts new file mode 100644 index 000000000..974b54dad --- /dev/null +++ b/projects/scion/workbench/src/lib/view-activation-instant-provider.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; + +/** + * Provider for instants at which a {WorkbenchView} is activated. + */ +@Injectable() +export class ViewActivationInstantProvider { + + /** + * Returns the current instant. + */ + public get instant(): number { + return Date.now(); + } +} diff --git a/projects/scion/workbench/src/lib/view-part-grid/view-part-grid.model.ts b/projects/scion/workbench/src/lib/view-part-grid/view-part-grid.model.ts index 8098d785c..5bc69c301 100644 --- a/projects/scion/workbench/src/lib/view-part-grid/view-part-grid.model.ts +++ b/projects/scion/workbench/src/lib/view-part-grid/view-part-grid.model.ts @@ -11,6 +11,7 @@ import { Region } from '../view-part/view-drop-zone.directive'; import { VIEW_PART_REF_PREFIX } from '../workbench.constants'; import { ACTIVE_VIEW_REF_INDEX, VIEW_PART_REF_INDEX, VIEW_REFS_START_INDEX, ViewPartGridSerializerService, ViewPartInfoArray, ViewPartSashBox } from './view-part-grid-serializer.service'; +import { WorkbenchViewRegistry } from '../workbench-view-registry.service'; /** * Represents the arrangement of viewparts in a grid and provides methods to modify the grid. @@ -23,7 +24,7 @@ export class ViewPartGrid { private _root: ViewPartSashBox | ViewPartInfoArray; - constructor(serializedGrid: string, private _serializer: ViewPartGridSerializerService) { + constructor(serializedGrid: string, private _serializer: ViewPartGridSerializerService, private _viewRegistry: WorkbenchViewRegistry) { this._root = this._serializer.parseGrid(serializedGrid || this._serializer.emptySerializedGrid()); } @@ -39,7 +40,7 @@ export class ViewPartGrid { */ public clone(): ViewPartGrid { const serializedGrid = this._serializer.serializeGrid(this._root); - return new ViewPartGrid(serializedGrid, this._serializer); + return new ViewPartGrid(serializedGrid, this._serializer, this._viewRegistry); } /** @@ -130,11 +131,8 @@ export class ViewPartGrid { // Activate next view if this view was active if (viewPartInfoArray[ACTIVE_VIEW_REF_INDEX] === viewRef) { - viewPartInfoArray[ACTIVE_VIEW_REF_INDEX] = null; - - if (viewPartInfoArray.length > VIEW_REFS_START_INDEX) { - viewPartInfoArray[ACTIVE_VIEW_REF_INDEX] = viewPartInfoArray[viewIndex] || viewPartInfoArray[viewIndex - 1]; - } + const viewRefs = viewPartInfoArray.slice(VIEW_REFS_START_INDEX); + viewPartInfoArray[ACTIVE_VIEW_REF_INDEX] = this.findMostRecentView(viewRefs); } // Remove viewpart if its last view is closed. @@ -146,6 +144,14 @@ export class ViewPartGrid { return this; } + private findMostRecentView(viewRefs: string[]): string | null { + const viewsSorted = viewRefs + .map(viewRef => this._viewRegistry.getElseThrow(viewRef)) + .sort((view1, view2) => view2.activationInstant - view1.activationInstant); + + return viewsSorted.length > 0 ? viewsSorted[0].viewRef : null; + } + private _swapViews(viewPartRef: string, viewRef1: string, viewRef2: string): this { const viewPartInfoArray = this.getViewPartElseThrow(viewPartRef).viewPartInfoArray; diff --git a/projects/scion/workbench/src/lib/workbench-url-observer.service.ts b/projects/scion/workbench/src/lib/workbench-url-observer.service.ts index d63eed6c8..0faebdead 100644 --- a/projects/scion/workbench/src/lib/workbench-url-observer.service.ts +++ b/projects/scion/workbench/src/lib/workbench-url-observer.service.ts @@ -83,7 +83,7 @@ export class WorkbenchUrlObserver implements OnDestroy { // 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); + const viewPartGrid = new ViewPartGrid(serializedViewPartGrid, this._viewPartGridSerializer, this._viewRegistry); // Update the view registry with added or removed view outlets. // diff --git a/projects/scion/workbench/src/lib/workbench-view-registry.service.ts b/projects/scion/workbench/src/lib/workbench-view-registry.service.ts index c9bfa2765..e5bf82db3 100644 --- a/projects/scion/workbench/src/lib/workbench-view-registry.service.ts +++ b/projects/scion/workbench/src/lib/workbench-view-registry.service.ts @@ -17,6 +17,7 @@ import { asapScheduler, AsyncSubject, merge, Observable, Subject } from 'rxjs'; import { delay, filter, map, take, takeUntil } from 'rxjs/operators'; import { TaskScheduler } from './task-scheduler.service'; import { NavigationEnd, Router } from '@angular/router'; +import { ViewActivationInstantProvider } from './view-activation-instant-provider.service'; /** * Registry for {WorkbenchView} objects. @@ -31,7 +32,8 @@ export class WorkbenchViewRegistry implements OnDestroy { constructor(private _componentFactoryResolver: ComponentFactoryResolver, private _taskScheduler: TaskScheduler, private _router: Router, - private _injector: Injector) { + private _injector: Injector, + private _viewActivationInstantProvider: ViewActivationInstantProvider) { } /** @@ -99,7 +101,7 @@ export class WorkbenchViewRegistry implements OnDestroy { private createWorkbenchView(viewRef: string, active: boolean): InternalWorkbenchView { const portal = new WbComponentPortal(this._componentFactoryResolver, this._injector.get(VIEW_COMPONENT_TYPE)); - const view = new InternalWorkbenchView(viewRef, active, this._injector.get(WORKBENCH), portal); + const view = new InternalWorkbenchView(viewRef, active, this._injector.get(WORKBENCH), portal, this._viewActivationInstantProvider); const injectionTokens = new WeakMap(); injectionTokens.set(ROUTER_OUTLET_NAME, viewRef); diff --git a/projects/scion/workbench/src/lib/workbench.model.ts b/projects/scion/workbench/src/lib/workbench.model.ts index db4821e68..daf95e8ee 100644 --- a/projects/scion/workbench/src/lib/workbench.model.ts +++ b/projects/scion/workbench/src/lib/workbench.model.ts @@ -17,6 +17,7 @@ import { Arrays } from './array.util'; import { Injector, TemplateRef } from '@angular/core'; import { Disposable } from './disposable'; import { ComponentType } from '@angular/cdk/portal'; +import { ViewActivationInstantProvider } from './view-activation-instant-provider.service'; /** * A view is a visual component within the Workbench to present content, @@ -93,6 +94,7 @@ export class InternalWorkbenchView implements WorkbenchView { public disabled: boolean; public scrollTop: number | null; public scrollLeft: number | null; + public activationInstant: number; public readonly active$: BehaviorSubject; public readonly cssClasses$: BehaviorSubject; @@ -100,7 +102,8 @@ export class InternalWorkbenchView implements WorkbenchView { constructor(public readonly viewRef: string, active: boolean, public workbench: WorkbenchService, - public readonly portal: WbComponentPortal) { + public readonly portal: WbComponentPortal, + private _viewActivationInstantProvider: ViewActivationInstantProvider) { this.active$ = new BehaviorSubject(active); this.cssClasses$ = new BehaviorSubject([]); this.title = viewRef; @@ -120,6 +123,9 @@ export class InternalWorkbenchView implements WorkbenchView { } public activate(activate: boolean): void { + if (activate) { + this.activationInstant = this._viewActivationInstantProvider.instant; + } this.active$.next(activate); } diff --git a/projects/scion/workbench/src/lib/workbench.module.ts b/projects/scion/workbench/src/lib/workbench.module.ts index 7cc24bff1..4e3f039f5 100644 --- a/projects/scion/workbench/src/lib/workbench.module.ts +++ b/projects/scion/workbench/src/lib/workbench.module.ts @@ -67,6 +67,7 @@ import { ViewPartActionDirective } from './view-part/view-part-action-bar/view-p import { ViewPartActionBarComponent } from './view-part/view-part-action-bar/view-part-action-bar.component'; import { TaskScheduler } from './task-scheduler.service'; import { WorkbenchViewPartRegistry } from './view-part-grid/workbench-view-part-registry.service'; +import { ViewActivationInstantProvider } from './view-activation-instant-provider.service'; @NgModule({ imports: [ @@ -187,6 +188,7 @@ export class WorkbenchModule { InternalWorkbenchRouter, PopupService, TaskScheduler, + ViewActivationInstantProvider, { provide: WorkbenchRouter, useExisting: InternalWorkbenchRouter, }, diff --git a/projects/scion/workbench/src/public_api.ts b/projects/scion/workbench/src/public_api.ts index 293d36a2c..01bd6adbb 100644 --- a/projects/scion/workbench/src/public_api.ts +++ b/projects/scion/workbench/src/public_api.ts @@ -12,6 +12,7 @@ * Entry point for all public APIs of this package. */ export { WorkbenchModule } from './lib/workbench.module'; +export { WorkbenchTestingModule } from './lib/spec/workbench-testing.module'; export { WorkbenchService } from './lib/workbench.service'; export { WorkbenchView, WorkbenchViewPart, WbBeforeDestroy } from './lib/workbench.model'; export { WbRouterLinkDirective, WbRouterLinkWithHrefDirective } from './lib/routing/wb-router-link.directive';