ACS-8251: migrate Extensions library to Standalone (#9842)

This commit is contained in:
Denys Vuika
2024-06-20 08:52:30 -04:00
committed by GitHub
parent 45e921a382
commit 9a544307d4
32 changed files with 465 additions and 572 deletions

View File

@@ -29,154 +29,154 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
class MockRouter {
private url = 'some-url';
private subject = new Subject();
events = this.subject.asObservable();
routerState = { snapshot: { url: this.url } };
private url = 'some-url';
private subject = new Subject();
events = this.subject.asObservable();
routerState = { snapshot: { url: this.url } };
navigateByUrl(url: string) {
const navigationStart = new NavigationStart(0, url);
this.subject.next(navigationStart);
}
navigateByUrl(url: string) {
const navigationStart = new NavigationStart(0, url);
this.subject.next(navigationStart);
}
}
describe('AppLayoutComponent', () => {
let fixture: ComponentFixture<ShellLayoutComponent>;
let component: ShellLayoutComponent;
let appConfig: AppConfigService;
let shellAppService: ShellAppService;
let fixture: ComponentFixture<ShellLayoutComponent>;
let component: ShellLayoutComponent;
let appConfig: AppConfigService;
let shellAppService: ShellAppService;
beforeEach(() => {
const shellService: ShellAppService = {
pageHeading$: of('Title'),
hideSidenavConditions: [],
minimizeSidenavConditions: [],
preferencesService: {
get: () => 'true',
set: () => {}
}
};
beforeEach(() => {
const shellService: ShellAppService = {
pageHeading$: of('Title'),
hideSidenavConditions: [],
minimizeSidenavConditions: [],
preferencesService: {
get: () => 'true',
set: () => {}
}
};
TestBed.configureTestingModule({
imports: [
CommonModule,
NoopAnimationsModule,
HttpClientModule,
SidenavLayoutModule,
ExtensionsModule,
RouterModule.forChild([]),
TranslateModule.forRoot()
],
providers: [
{
provide: Router,
useClass: MockRouter
},
{
provide: SHELL_APP_SERVICE,
useValue: shellService
}
],
declarations: [ShellLayoutComponent],
schemas: [NO_ERRORS_SCHEMA]
TestBed.configureTestingModule({
imports: [
CommonModule,
NoopAnimationsModule,
HttpClientModule,
SidenavLayoutModule,
ExtensionsModule,
RouterModule.forChild([]),
TranslateModule.forRoot(),
ShellLayoutComponent
],
providers: [
{
provide: Router,
useClass: MockRouter
},
{
provide: SHELL_APP_SERVICE,
useValue: shellService
}
],
schemas: [NO_ERRORS_SCHEMA]
});
fixture = TestBed.createComponent(ShellLayoutComponent);
component = fixture.componentInstance;
appConfig = TestBed.inject(AppConfigService);
shellAppService = TestBed.inject(SHELL_APP_SERVICE);
});
fixture = TestBed.createComponent(ShellLayoutComponent);
component = fixture.componentInstance;
appConfig = TestBed.inject(AppConfigService);
shellAppService = TestBed.inject(SHELL_APP_SERVICE);
});
beforeEach(() => {
appConfig.config.languages = [];
appConfig.config.locale = 'en';
});
describe('sidenav state', () => {
it('should get state from configuration', () => {
appConfig.config.sideNav = {
expandedSidenav: false,
preserveState: false
};
fixture.detectChanges();
expect(component.expandedSidenav).toBe(false);
beforeEach(() => {
appConfig.config.languages = [];
appConfig.config.locale = 'en';
});
it('should resolve state to true is no configuration', () => {
appConfig.config.sidenav = {};
describe('sidenav state', () => {
it('should get state from configuration', () => {
appConfig.config.sideNav = {
expandedSidenav: false,
preserveState: false
};
fixture.detectChanges();
fixture.detectChanges();
expect(component.expandedSidenav).toBe(true);
expect(component.expandedSidenav).toBe(false);
});
it('should resolve state to true is no configuration', () => {
appConfig.config.sidenav = {};
fixture.detectChanges();
expect(component.expandedSidenav).toBe(true);
});
it('should get state from user settings as true', () => {
appConfig.config.sideNav = {
expandedSidenav: false,
preserveState: true
};
spyOn(shellAppService.preferencesService, 'get').and.callFake((key) => {
if (key === 'expandedSidenav') {
return 'true';
}
return 'false';
});
fixture.detectChanges();
expect(component.expandedSidenav).toBe(true);
});
it('should get state from user settings as false', () => {
appConfig.config.sidenav = {
expandedSidenav: false,
preserveState: true
};
spyOn(shellAppService.preferencesService, 'get').and.callFake((key) => {
if (key === 'expandedSidenav') {
return 'false';
}
return 'true';
});
fixture.detectChanges();
expect(component.expandedSidenav).toBe(false);
});
});
it('should get state from user settings as true', () => {
appConfig.config.sideNav = {
expandedSidenav: false,
preserveState: true
};
it('should close menu on mobile screen size', () => {
component.minimizeSidenav = false;
component.layout.container = {
isMobileScreenSize: true,
toggleMenu: () => {}
};
spyOn(shellAppService.preferencesService, 'get').and.callFake((key) => {
if (key === 'expandedSidenav') {
return 'true';
}
return 'false';
});
spyOn(component.layout.container, 'toggleMenu');
fixture.detectChanges();
fixture.detectChanges();
component.hideMenu({ preventDefault: () => {} } as any);
expect(component.expandedSidenav).toBe(true);
expect(component.layout.container.toggleMenu).toHaveBeenCalled();
});
it('should get state from user settings as false', () => {
appConfig.config.sidenav = {
expandedSidenav: false,
preserveState: true
};
it('should close menu on mobile screen size also when minimizeSidenav true', () => {
fixture.detectChanges();
component.minimizeSidenav = true;
component.layout.container = {
isMobileScreenSize: true,
toggleMenu: () => {}
};
spyOn(shellAppService.preferencesService, 'get').and.callFake((key) => {
if (key === 'expandedSidenav') {
return 'false';
}
return 'true';
});
spyOn(component.layout.container, 'toggleMenu');
fixture.detectChanges();
fixture.detectChanges();
component.hideMenu({ preventDefault: () => {} } as any);
expect(component.expandedSidenav).toBe(false);
expect(component.layout.container.toggleMenu).toHaveBeenCalled();
});
});
it('should close menu on mobile screen size', () => {
component.minimizeSidenav = false;
component.layout.container = {
isMobileScreenSize: true,
toggleMenu: () => {}
};
spyOn(component.layout.container, 'toggleMenu');
fixture.detectChanges();
component.hideMenu({ preventDefault: () => {} } as any);
expect(component.layout.container.toggleMenu).toHaveBeenCalled();
});
it('should close menu on mobile screen size also when minimizeSidenav true', () => {
fixture.detectChanges();
component.minimizeSidenav = true;
component.layout.container = {
isMobileScreenSize: true,
toggleMenu: () => {}
};
spyOn(component.layout.container, 'toggleMenu');
fixture.detectChanges();
component.hideMenu({ preventDefault: () => {} } as any);
expect(component.layout.container.toggleMenu).toHaveBeenCalled();
});
});

View File

@@ -15,125 +15,129 @@
* limitations under the License.
*/
import { AppConfigService, SidenavLayoutComponent } from '@alfresco/adf-core';
import { AppConfigService, SidenavLayoutComponent, SidenavLayoutModule } from '@alfresco/adf-core';
import { DynamicExtensionComponent } from '@alfresco/adf-extensions';
import { Component, Inject, OnDestroy, OnInit, Optional, ViewChild, ViewEncapsulation } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { Subject, Observable } from 'rxjs';
import { filter, takeUntil, map, withLatestFrom } from 'rxjs/operators';
import { BreakpointObserver } from '@angular/cdk/layout';
import { Directionality } from '@angular/cdk/bidi';
import { SHELL_APP_SERVICE, ShellAppService, SHELL_NAVBAR_MIN_WIDTH, SHELL_NAVBAR_MAX_WIDTH } from '../../services/shell-app.service';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-shell',
templateUrl: './shell.component.html',
styleUrls: ['./shell.component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'app-shell' }
selector: 'app-shell',
standalone: true,
imports: [CommonModule, SidenavLayoutModule, RouterModule, DynamicExtensionComponent],
templateUrl: './shell.component.html',
styleUrls: ['./shell.component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'app-shell' }
})
export class ShellLayoutComponent implements OnInit, OnDestroy {
@ViewChild('layout', { static: true })
layout: SidenavLayoutComponent;
@ViewChild('layout', { static: true })
layout: SidenavLayoutComponent;
onDestroy$: Subject<boolean> = new Subject<boolean>();
isSmallScreen$: Observable<boolean>;
onDestroy$: Subject<boolean> = new Subject<boolean>();
isSmallScreen$: Observable<boolean>;
expandedSidenav: boolean;
minimizeSidenav = false;
hideSidenav = false;
sidenavMin: number;
sidenavMax: number;
direction: Directionality;
expandedSidenav: boolean;
minimizeSidenav = false;
hideSidenav = false;
sidenavMin: number;
sidenavMax: number;
direction: Directionality;
constructor(
private router: Router,
private appConfigService: AppConfigService,
private breakpointObserver: BreakpointObserver,
@Inject(SHELL_APP_SERVICE) private shellService: ShellAppService,
@Optional() @Inject(SHELL_NAVBAR_MIN_WIDTH) navBarMinWidth: number,
@Optional() @Inject(SHELL_NAVBAR_MAX_WIDTH) navbarMaxWidth: number
) {
this.sidenavMin = navBarMinWidth ?? 70;
this.sidenavMax = navbarMaxWidth ?? 320;
}
ngOnInit() {
this.isSmallScreen$ = this.breakpointObserver.observe(['(max-width: 600px)']).pipe(map((result) => result.matches));
this.hideSidenav = this.shellService.hideSidenavConditions.some((el) => this.router.routerState.snapshot.url.includes(el));
this.minimizeSidenav = this.shellService.minimizeSidenavConditions.some((el) => this.router.routerState.snapshot.url.includes(el));
if (!this.minimizeSidenav) {
this.expandedSidenav = this.getSidenavState();
} else {
this.expandedSidenav = false;
constructor(
private router: Router,
private appConfigService: AppConfigService,
private breakpointObserver: BreakpointObserver,
@Inject(SHELL_APP_SERVICE) private shellService: ShellAppService,
@Optional() @Inject(SHELL_NAVBAR_MIN_WIDTH) navBarMinWidth: number,
@Optional() @Inject(SHELL_NAVBAR_MAX_WIDTH) navbarMaxWidth: number
) {
this.sidenavMin = navBarMinWidth ?? 70;
this.sidenavMax = navbarMaxWidth ?? 320;
}
this.router.events
.pipe(
withLatestFrom(this.isSmallScreen$),
filter(([event, isSmallScreen]) => isSmallScreen && event instanceof NavigationEnd),
takeUntil(this.onDestroy$)
)
.subscribe(() => {
this.layout.container.sidenav.close();
});
ngOnInit() {
this.isSmallScreen$ = this.breakpointObserver.observe(['(max-width: 600px)']).pipe(map((result) => result.matches));
this.router.events
.pipe(
filter((event) => event instanceof NavigationEnd),
takeUntil(this.onDestroy$)
)
.subscribe((event: NavigationEnd) => {
this.minimizeSidenav = this.shellService.minimizeSidenavConditions.some((el) => event.urlAfterRedirects.includes(el));
this.hideSidenav = this.shellService.hideSidenavConditions.some((el) => event.urlAfterRedirects.includes(el));
this.hideSidenav = this.shellService.hideSidenavConditions.some((el) => this.router.routerState.snapshot.url.includes(el));
this.minimizeSidenav = this.shellService.minimizeSidenavConditions.some((el) => this.router.routerState.snapshot.url.includes(el));
this.updateState();
});
}
if (!this.minimizeSidenav) {
this.expandedSidenav = this.getSidenavState();
} else {
this.expandedSidenav = false;
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
this.router.events
.pipe(
withLatestFrom(this.isSmallScreen$),
filter(([event, isSmallScreen]) => isSmallScreen && event instanceof NavigationEnd),
takeUntil(this.onDestroy$)
)
.subscribe(() => {
this.layout.container.sidenav.close();
});
hideMenu(event: Event) {
if (this.layout.container.isMobileScreenSize) {
event.preventDefault();
this.layout.container.toggleMenu();
}
}
this.router.events
.pipe(
filter((event) => event instanceof NavigationEnd),
takeUntil(this.onDestroy$)
)
.subscribe((event: NavigationEnd) => {
this.minimizeSidenav = this.shellService.minimizeSidenavConditions.some((el) => event.urlAfterRedirects.includes(el));
this.hideSidenav = this.shellService.hideSidenavConditions.some((el) => event.urlAfterRedirects.includes(el));
private updateState() {
if (this.minimizeSidenav && !this.layout.isMenuMinimized) {
this.layout.isMenuMinimized = true;
if (!this.layout.container.isMobileScreenSize) {
this.layout.container.toggleMenu();
}
this.updateState();
});
}
if (!this.minimizeSidenav) {
if (this.getSidenavState() && this.layout.isMenuMinimized) {
this.layout.isMenuMinimized = false;
this.layout.container.toggleMenu();
}
}
}
onExpanded(state: boolean) {
if (!this.minimizeSidenav && this.appConfigService.get('sideNav.preserveState')) {
this.shellService.preferencesService.set('expandedSidenav', state);
}
}
private getSidenavState(): boolean {
const expand = this.appConfigService.get<boolean>('sideNav.expandedSidenav', true);
const preserveState = this.appConfigService.get<boolean>('sideNav.preserveState', true);
if (preserveState) {
return this.shellService.preferencesService.get('expandedSidenav', expand.toString()) === 'true';
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
return expand;
}
hideMenu(event: Event) {
if (this.layout.container.isMobileScreenSize) {
event.preventDefault();
this.layout.container.toggleMenu();
}
}
private updateState() {
if (this.minimizeSidenav && !this.layout.isMenuMinimized) {
this.layout.isMenuMinimized = true;
if (!this.layout.container.isMobileScreenSize) {
this.layout.container.toggleMenu();
}
}
if (!this.minimizeSidenav) {
if (this.getSidenavState() && this.layout.isMenuMinimized) {
this.layout.isMenuMinimized = false;
this.layout.container.toggleMenu();
}
}
}
onExpanded(state: boolean) {
if (!this.minimizeSidenav && this.appConfigService.get('sideNav.preserveState')) {
this.shellService.preferencesService.set('expandedSidenav', state);
}
}
private getSidenavState(): boolean {
const expand = this.appConfigService.get<boolean>('sideNav.expandedSidenav', true);
const preserveState = this.appConfigService.get<boolean>('sideNav.preserveState', true);
if (preserveState) {
return this.shellService.preferencesService.get('expandedSidenav', expand.toString()) === 'true';
}
return expand;
}
}

View File

@@ -15,32 +15,28 @@
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { Routes, provideRoutes, RouterModule, Route } from '@angular/router';
import { Routes, provideRoutes, Route } from '@angular/router';
import { SHELL_LAYOUT_ROUTE } from './shell.routes';
import { SidenavLayoutModule } from '@alfresco/adf-core';
import { ExtensionsModule } from '@alfresco/adf-extensions';
import { ShellLayoutComponent } from './components/shell/shell.component';
export interface AppShellRoutesConfig {
shellParentRoute?: Route;
shellChildren: Routes;
shellParentRoute?: Route;
shellChildren: Routes;
}
@NgModule({
imports: [SidenavLayoutModule, ExtensionsModule, RouterModule.forChild([]), CommonModule],
exports: [ShellLayoutComponent],
declarations: [ShellLayoutComponent]
imports: [ShellLayoutComponent],
exports: [ShellLayoutComponent]
})
export class ShellModule {
static withRoutes(routes: Routes | AppShellRoutesConfig): ModuleWithProviders<ShellModule> {
if (Array.isArray(routes)) {
return getModuleForRoutes(routes);
}
static withRoutes(routes: Routes | AppShellRoutesConfig): ModuleWithProviders<ShellModule> {
if (Array.isArray(routes)) {
return getModuleForRoutes(routes);
}
return getModuleForRouteConfig(routes);
}
return getModuleForRouteConfig(routes);
}
}
/**
@@ -50,16 +46,16 @@ export class ShellModule {
* @returns module with providers
*/
function getModuleForRoutes(routes: Routes): ModuleWithProviders<ShellModule> {
const shellLayoutRoute = SHELL_LAYOUT_ROUTE;
const shellLayoutRoute = SHELL_LAYOUT_ROUTE;
routes.forEach((childRoute) => {
shellLayoutRoute.children.push(childRoute);
});
routes.forEach((childRoute) => {
shellLayoutRoute.children.push(childRoute);
});
return {
ngModule: ShellModule,
providers: provideRoutes([shellLayoutRoute])
};
return {
ngModule: ShellModule,
providers: provideRoutes([shellLayoutRoute])
};
}
/**
@@ -69,25 +65,25 @@ function getModuleForRoutes(routes: Routes): ModuleWithProviders<ShellModule> {
* @returns module with providers
*/
function getModuleForRouteConfig(config: AppShellRoutesConfig): ModuleWithProviders<ShellModule> {
const shellLayoutRoute = SHELL_LAYOUT_ROUTE;
const shellLayoutRoute = SHELL_LAYOUT_ROUTE;
const shellParentRoute = config.shellParentRoute;
const shellChildrenRoutes = config.shellChildren;
const shellParentRoute = config.shellParentRoute;
const shellChildrenRoutes = config.shellChildren;
shellLayoutRoute.children.push(...shellChildrenRoutes);
shellLayoutRoute.children.push(...shellChildrenRoutes);
const rootRoute = shellParentRoute ? shellParentRoute : shellLayoutRoute;
const rootRoute = shellParentRoute ? shellParentRoute : shellLayoutRoute;
if (config.shellParentRoute) {
if (rootRoute.children === undefined) {
rootRoute.children = [];
if (config.shellParentRoute) {
if (rootRoute.children === undefined) {
rootRoute.children = [];
}
rootRoute.children.push(shellLayoutRoute);
}
rootRoute.children.push(shellLayoutRoute);
}
return {
ngModule: ShellModule,
providers: provideRoutes([rootRoute])
};
return {
ngModule: ShellModule,
providers: provideRoutes([rootRoute])
};
}