From ba21c7d70b75ab1c634ca007a0b6819d990f9d02 Mon Sep 17 00:00:00 2001 From: Shivangi Shree Date: Mon, 10 Aug 2026 11:57:00 +0530 Subject: [PATCH] [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 --- .../details/details.component.spec.ts | 56 ++++++++++++++++++- .../components/details/details.component.ts | 30 ++++++++-- .../lib/store/effects/node.effects.spec.ts | 19 +++++++ .../src/lib/store/effects/node.effects.ts | 3 +- 4 files changed, 100 insertions(+), 8 deletions(-) diff --git a/projects/aca-content/src/lib/components/details/details.component.spec.ts b/projects/aca-content/src/lib/components/details/details.component.spec.ts index e70be9a16..983f35e2c 100644 --- a/projects/aca-content/src/lib/components/details/details.component.spec.ts +++ b/projects/aca-content/src/lib/components/details/details.component.spec.ts @@ -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; + 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({}); 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)); + }); + }); }); diff --git a/projects/aca-content/src/lib/components/details/details.component.ts b/projects/aca-content/src/lib/components/details/details.component.ts index 816980f90..7c2cf8b52 100644 --- a/projects/aca-content/src/lib/components/details/details.component.ts +++ b/projects/aca-content/src/lib/components/details/details.component.ts @@ -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) { diff --git a/projects/aca-content/src/lib/store/effects/node.effects.spec.ts b/projects/aca-content/src/lib/store/effects/node.effects.spec.ts index 52ccfc934..d58644917 100644 --- a/projects/aca-content/src/lib/store/effects/node.effects.spec.ts +++ b/projects/aca-content/src/lib/store/effects/node.effects.spec.ts @@ -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$', () => { diff --git a/projects/aca-content/src/lib/store/effects/node.effects.ts b/projects/aca-content/src/lib/store/effects/node.effects.ts index 7bbb96a53..70d992bb8 100644 --- a/projects/aca-content/src/lib/store/effects/node.effects.ts +++ b/projects/aca-content/src/lib/store/effects/node.effects.ts @@ -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) {