[ACS-12391] Clicking the reduce panel displays reduced details view (#5333)

* [ACS-12391] Clicking the reduce panel displays reduced details view

* [ACS-12391] Add lifecycle teardown to activatedRoute

* [ACS-12391] Fix unit test

* [ACS-12391] File remains selected when user clicks on reduce panel button

* [ACS-12391] Fix review issues
This commit is contained in:
Shivangi Shree
2026-08-10 11:57:00 +05:30
committed by GitHub
parent c696975bf4
commit ba21c7d70b
4 changed files with 100 additions and 8 deletions
@@ -25,16 +25,17 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../../testing/app-testing.module';
import { DetailsComponent } from './details.component';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, NavigationEnd, Params, Router } from '@angular/router';
import { BehaviorSubject, of, Subject } from 'rxjs';
import { Store } from '@ngrx/store';
import { AppExtensionService, AppHookService, ContentApiService } from '@alfresco/aca-shared';
import { NavigateToFolder, SetSelectedNodesAction } from '@alfresco/aca-shared/store';
import { NavigateToFolder, NavigateToPreviousPage, SetInfoDrawerStateAction, SetSelectedNodesAction } from '@alfresco/aca-shared/store';
import { Node, NodeEntry, PathElement } from '@alfresco/js-api';
import { BreadcrumbComponent, ContentService, NodesApiService, SearchQueryBuilderService } from '@alfresco/adf-content-services';
import { By } from '@angular/platform-browser';
import { ContentActionRef } from '@alfresco/adf-extensions';
import { Location } from '@angular/common';
import { UnitTestingUtils } from '@alfresco/adf-core';
describe('DetailsComponent', () => {
let component: DetailsComponent;
@@ -45,7 +46,10 @@ describe('DetailsComponent', () => {
let appHookService: AppHookService;
let location: Location;
let store: Store;
let router: Router;
let node: NodeEntry;
let queryParamsSubject: BehaviorSubject<Params>;
let testingUtils: UnitTestingUtils;
const mockStream = new Subject();
const storeMock = {
@@ -58,6 +62,7 @@ describe('DetailsComponent', () => {
const getBreadcrumb = (): BreadcrumbComponent => fixture.debugElement.query(By.directive(BreadcrumbComponent)).componentInstance;
beforeEach(() => {
queryParamsSubject = new BehaviorSubject<Params>({});
TestBed.configureTestingModule({
imports: [AppTestingModule, DetailsComponent],
providers: [
@@ -67,7 +72,8 @@ describe('DetailsComponent', () => {
provide: ActivatedRoute,
useValue: {
snapshot: { data: { preferencePrefix: 'prefix' } },
params: of({ nodeId: 'someId', activeTab: 'permissions' })
params: of({ nodeId: 'someId', activeTab: 'permissions' }),
queryParams: queryParamsSubject
}
}
]
@@ -78,12 +84,14 @@ describe('DetailsComponent', () => {
fixture = TestBed.createComponent(DetailsComponent);
component = fixture.componentInstance;
testingUtils = new UnitTestingUtils(fixture.debugElement);
contentApiService = TestBed.inject(ContentApiService);
contentService = TestBed.inject(ContentService);
nodesApiService = TestBed.inject(NodesApiService);
appHookService = TestBed.inject(AppHookService);
location = TestBed.inject(Location);
store = TestBed.inject(Store);
router = TestBed.inject(Router);
storeMock.dispatch.calls.reset();
node = {
@@ -243,4 +251,46 @@ describe('DetailsComponent', () => {
expect(locationSpy).toHaveBeenCalled();
});
describe('Reduce Panel', () => {
const getReducePanelButton = (): HTMLButtonElement => testingUtils.getByDataAutomationId('close-library').nativeElement;
const clickReducePanelButton = (): void => getReducePanelButton().click();
it('should navigate to the file list page, keep the node selected and the info drawer open', () => {
queryParamsSubject.next({ location: '/personal-files' });
fixture.detectChanges();
const navigateSpy = spyOn(router, 'navigateByUrl').and.stub();
Object.defineProperty(router, 'events', { value: of(new NavigationEnd(1, '', '')) });
const nodeToSelectSpy = spyOn(appHookService.nodeToSelect$, 'next');
clickReducePanelButton();
expect(navigateSpy).toHaveBeenCalledWith('/personal-files');
expect(nodeToSelectSpy).toHaveBeenCalledWith({ entry: node.entry });
expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(SetInfoDrawerStateAction));
expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ payload: true }));
});
it('should navigate to viewer page and keep the info drawer open', () => {
queryParamsSubject.next({ location: 'personal-files/(viewer:view/nodeId)?location=personal-files%2Fdetails%2Fabc123%2Fpermissions' });
fixture.detectChanges();
Object.defineProperty(router, 'events', { value: of(new NavigationEnd(1, '', '')) });
const navigateSpy = spyOn(router, 'navigateByUrl').and.stub();
clickReducePanelButton();
expect(navigateSpy).toHaveBeenCalledWith('personal-files/(viewer:view/nodeId)?location=personal-files%2Fdetails%2Fabc123%2Fpermissions');
expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(SetInfoDrawerStateAction));
expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ payload: true }));
});
it('should dispatch NavigateToPreviousPage when there is no previous route', () => {
fixture.detectChanges();
Object.defineProperty(router, 'events', { value: of(new NavigationEnd(1, '', '')) });
clickReducePanelButton();
expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(NavigateToPreviousPage));
});
});
});
@@ -23,9 +23,9 @@
*/
import { Component, OnDestroy, OnInit, ViewEncapsulation, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, NavigationEnd } from '@angular/router';
import { AppHookService, ContentApiService, PageComponent, PageLayoutComponent, ToolbarComponent } from '@alfresco/aca-shared';
import { NavigateToFolder, NavigateToPreviousPage, SetSelectedNodesAction } from '@alfresco/aca-shared/store';
import { NavigateToFolder, NavigateToPreviousPage, SetInfoDrawerStateAction, SetSelectedNodesAction } from '@alfresco/aca-shared/store';
import { BreadcrumbComponent, ContentService, NodesApiService, PermissionListComponent } from '@alfresco/adf-content-services';
import { CommonModule, Location } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
@@ -70,6 +70,8 @@ export class DetailsComponent extends PageComponent implements OnInit, OnDestroy
private readonly appHookService = inject(AppHookService);
private readonly location = inject(Location);
private previousRoute = '';
nodeId: string;
isLoading: boolean;
activeTab = 1;
@@ -85,7 +87,12 @@ export class DetailsComponent extends PageComponent implements OnInit, OnDestroy
this.title = data.title;
this.nodesApiService.nodeUpdated.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((node) => (this.node = { ...node }));
this.appHookService.nodesDeleted.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.location.back());
this.route.params.subscribe((params) => {
this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
if (params.location) {
this.saveRoute(params.location);
}
});
this.route.params.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
this.isLoading = true;
this.setActiveTab(params.activeTab);
this.nodeId = params.nodeId;
@@ -124,8 +131,23 @@ export class DetailsComponent extends PageComponent implements OnInit, OnDestroy
}
}
saveRoute(route: string): void {
this.previousRoute = route;
}
goBack() {
this.store.dispatch(new NavigateToPreviousPage());
this.router.events
.pipe(first((event) => event instanceof NavigationEnd))
.subscribe(() => this.store.dispatch(new SetInfoDrawerStateAction(true)));
if (this.previousRoute) {
if (!this.previousRoute.includes('viewer:') && this.node) {
this.appHookService.nodeToSelect$.next({ entry: this.node });
}
this.router.navigateByUrl(this.previousRoute);
} else {
this.store.dispatch(new NavigateToPreviousPage());
}
}
onBreadcrumbNavigate(path: PathElement) {
@@ -678,6 +678,25 @@ describe('NodeEffects', () => {
jasmine.objectContaining({ ...new NavigateUrlAction('repository/details/node-id?location=test-page') })
);
});
it('should use the current url as location when opened from the viewer', () => {
spyOn(store, 'dispatch').and.callThrough();
spyOnProperty(router, 'url', 'get').and.returnValue('personal-files/(viewer:view/node-id)');
Object.defineProperties(router, {
events: {
value: of(new NavigationEnd(1, 'personal-files/(viewer:view/node-id)', ''))
},
navigateByUrl: {
value: jasmine.createSpy('navigateByUrl')
}
});
const node = { entry: { isFile: true, id: 'node-id' } } as NodeEntry;
store.dispatch(new ExpandInfoDrawerAction(node));
expect(store.dispatch).toHaveBeenCalledWith(
jasmine.objectContaining({ ...new NavigateUrlAction('personal-files/details/node-id?location=personal-files/(viewer:view/node-id)') })
);
});
});
describe('nodeInformation$', () => {
@@ -364,7 +364,8 @@ export class NodeEffects {
.subscribe(() => this.store.dispatch(new SetInfoDrawerStateAction(true)));
this.activatedRoute.queryParams.pipe(take(1)).subscribe((params) => {
const location = params.location || this.router.url;
const inViewer = this.router.url.includes('viewer:');
const location = inViewer ? this.router.url : params.location || this.router.url;
const sanitizedLocation = this.sanitizer.sanitize(SecurityContext.URL, location);
if (action?.payload) {