diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json index 7242920be..f879943bc 100644 --- a/projects/aca-content/assets/app.extensions.json +++ b/projects/aca-content/assets/app.extensions.json @@ -566,7 +566,9 @@ "comment": "workaround for Recent Files and Search API issue", "type": "custom", "order": 400, - "data": "['/favorites', '/favorite/libraries']", + "data": { + "routes": ["/favorites", "/favorite/libraries"] + }, "component": "app.toolbar.toggleFavorite", "rules": { "visible": [ @@ -939,7 +941,10 @@ "comment": "workaround for Recent Files and Search API issue", "type": "custom", "order": 802, - "data": "['/favorites', '/favorite/libraries']", + "data": { + "routes": ["/favorites", "/favorite/libraries"], + "focusAfterClosed": ".adf-context-menu-source" + }, "component": "app.toolbar.toggleFavorite", "rules": { "visible": [ @@ -953,6 +958,9 @@ "type": "custom", "order": 803, "component": "app.toolbar.toggleFavoriteLibrary", + "data": { + "focusAfterClosed": ".adf-context-menu-source" + }, "rules": { "visible": [ "app.selection.library" diff --git a/projects/aca-content/src/lib/components/search/search-input/search-input.component.html b/projects/aca-content/src/lib/components/search/search-input/search-input.component.html index 1617b5f4a..4ff47724b 100644 --- a/projects/aca-content/src/lib/components/search/search-input/search-input.component.html +++ b/projects/aca-content/src/lib/components/search/search-input/search-input.component.html @@ -45,7 +45,7 @@ (validationError)="error = $event" [hasLibrariesConstraint]="hasLibrariesConstraint" /> - + {{ error | translate }}
diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.spec.ts b/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.spec.ts index 2fa18bdc2..637d9885b 100644 --- a/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.spec.ts +++ b/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.spec.ts @@ -22,7 +22,7 @@ * from Hyland Software. If not, see . */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { ToggleFavoriteLibraryComponent } from './toggle-favorite-library.component'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -30,12 +30,14 @@ import { AppTestingModule } from '../../../testing/app-testing.module'; import { of } from 'rxjs'; import { Router } from '@angular/router'; import { AppHookService, ContentApiService } from '@alfresco/aca-shared'; +import { UnitTestingUtils } from '@alfresco/adf-core'; describe('ToggleFavoriteLibraryComponent', () => { let fixture: ComponentFixture; let component: ToggleFavoriteLibraryComponent; let appHookService: AppHookService; let contentApiService: any; + let unitTestingUtils: UnitTestingUtils; const selection = { library: { entry: { id: 'libraryId' } } }; const mockRouter = { @@ -66,6 +68,7 @@ describe('ToggleFavoriteLibraryComponent', () => { fixture = TestBed.createComponent(ToggleFavoriteLibraryComponent); component = fixture.componentInstance; contentApiService = TestBed.inject(ContentApiService); + unitTestingUtils = new UnitTestingUtils(fixture.debugElement); appHookService = TestBed.inject(AppHookService); spyOn(contentApiService['favoritesApi'], 'getFavoriteSite').and.returnValue(Promise.resolve(null)); @@ -86,14 +89,24 @@ describe('ToggleFavoriteLibraryComponent', () => { expect(component.library.isFavorite).toBe(true); }); - it('should emit onToggleEvent() event', async () => { + it('should emit onToggleEvent() event', fakeAsync(() => { spyOn(appHookService.favoriteLibraryToggle, 'next'); - fixture.detectChanges(); - await fixture.whenStable(); - component.onToggleEvent(); + tick(100); expect(appHookService.favoriteLibraryToggle.next).toHaveBeenCalled(); + })); + + it('should focus element on toggle when focusAfterClosed is provided', () => { + const mockElement = jasmine.createSpyObj('HTMLElement', ['focus']); + spyOn(document, 'querySelector').and.returnValue(mockElement); + + component.data = { focusAfterClosed: '.adf-context-menu-source' }; + const button = unitTestingUtils.getByCSS('button'); + button.triggerEventHandler('toggle', new CustomEvent('toggle')); + + expect(document.querySelector).toHaveBeenCalledWith('.adf-context-menu-source'); + expect(mockElement.focus).toHaveBeenCalled(); }); }); diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.ts b/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.ts index a10a3cb35..d6c26873c 100644 --- a/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.ts +++ b/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.ts @@ -22,7 +22,7 @@ * from Hyland Software. If not, see . */ -import { Component, DestroyRef, inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, inject, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppHookService } from '@alfresco/aca-shared'; import { AppStore, getAppSelection } from '@alfresco/aca-shared/store'; @@ -56,6 +56,8 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; export class ToggleFavoriteLibraryComponent implements OnInit { library; + @Input() data: { focusAfterClosed?: string }; + @ViewChild(MatMenuItem) menuItem: MatMenuItem; @@ -84,6 +86,11 @@ export class ToggleFavoriteLibraryComponent implements OnInit { } onToggleEvent() { - this.appHookService.favoriteLibraryToggle.next(); + if (this.data?.focusAfterClosed) { + document.querySelector('.adf-context-menu-source')?.focus(); + } + setTimeout(() => { + this.appHookService.favoriteLibraryToggle.next(); + }, 100); } } diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.spec.ts b/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.spec.ts index a99dc37f5..209d1fcac 100644 --- a/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.spec.ts +++ b/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.spec.ts @@ -29,11 +29,14 @@ import { ExtensionService } from '@alfresco/adf-extensions'; import { Router } from '@angular/router'; import { of } from 'rxjs'; import { AppTestingModule } from '../../../testing/app-testing.module'; +import { UnitTestingUtils } from '@alfresco/adf-core'; describe('ToggleFavoriteComponent', () => { let component: ToggleFavoriteComponent; let fixture; let router; + let unitTestingUtils: UnitTestingUtils; + const mockRouter = { url: 'some-url' }; @@ -54,6 +57,7 @@ describe('ToggleFavoriteComponent', () => { fixture = TestBed.createComponent(ToggleFavoriteComponent); component = fixture.componentInstance; + unitTestingUtils = new UnitTestingUtils(fixture.debugElement); router = TestBed.inject(Router); }); @@ -66,7 +70,7 @@ describe('ToggleFavoriteComponent', () => { }); it('should not dispatch reload if route is not specified', () => { - component.data = '["/reload_on_this_route"]'; + component.data = { routes: ['/reload_on_this_route'] }; router.url = '/somewhere_over_the_rainbow'; fixture.detectChanges(); @@ -76,7 +80,7 @@ describe('ToggleFavoriteComponent', () => { }); it('should dispatch reload if route is specified', () => { - component.data = '["/reload_on_this_route"]'; + component.data = { routes: ['/reload_on_this_route'] }; router.url = '/reload_on_this_route'; fixture.detectChanges(); @@ -84,4 +88,16 @@ describe('ToggleFavoriteComponent', () => { expect(mockStore.dispatch).toHaveBeenCalled(); }); + + it('should focus element on toggle when focusAfterClosed is provided', () => { + const mockElement = jasmine.createSpyObj('HTMLElement', ['focus']); + spyOn(document, 'querySelector').and.returnValue(mockElement); + + component.data = { routes: [], focusAfterClosed: '.adf-context-menu-source' }; + const button = unitTestingUtils.getByCSS('button'); + button.triggerEventHandler('toggle', new CustomEvent('toggle')); + + expect(document.querySelector).toHaveBeenCalledWith('.adf-context-menu-source'); + expect(mockElement.focus).toHaveBeenCalled(); + }); }); diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.ts b/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.ts index 82e4e0ac1..3bdd2635b 100644 --- a/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.ts +++ b/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.ts @@ -64,12 +64,14 @@ export class ToggleFavoriteComponent implements OnInit { } ngOnInit() { - if (this.data) { - this.reloadOnRoutes = JSON.parse(this.data.replace(/'/g, '"')); - } + this.reloadOnRoutes = this.data?.routes ?? []; } onToggleEvent() { + const focusAfterClosed = this.data?.focusAfterClosed; + if (focusAfterClosed) { + document.querySelector(focusAfterClosed)?.focus(); + } if (this.reloadOnRoutes.includes(this.router.url)) { this.documentListService.reload(); } diff --git a/projects/aca-content/src/lib/services/content-management.service.spec.ts b/projects/aca-content/src/lib/services/content-management.service.spec.ts index 488f43507..f632934c2 100644 --- a/projects/aca-content/src/lib/services/content-management.service.spec.ts +++ b/projects/aca-content/src/lib/services/content-management.service.spec.ts @@ -902,6 +902,20 @@ describe('ContentManagementService', () => { store.dispatch(new DeleteNodesAction(selection)); expect(openSnackMessageActionSpy.calls.argsFor(0)[2].panelClass).toBe('adf-warning-snackbar'); }); + + it('should focus element when deleting nodes with focusAfterClosed selector', fakeAsync(() => { + const mockElement = jasmine.createSpyObj('HTMLElement', ['focus']); + spyOn(document, 'querySelector').and.returnValue(mockElement); + spyOn(contentApi, 'deleteNode').and.returnValue(of(null)); + + const nodes = [{ entry: { id: '1', name: 'name1' } } as NodeEntry]; + + contentManagementService.deleteNodes(nodes, false, '.some-button'); + tick(); + + expect(document.querySelector).toHaveBeenCalledWith('.some-button'); + expect(mockElement.focus).toHaveBeenCalled(); + })); }); describe('Permanent Delete', () => { @@ -927,6 +941,20 @@ describe('ContentManagementService', () => { expect(contentApi.purgeDeletedNode).toHaveBeenCalled(); }); + it('should focus element when purging nodes with focusAfterClosed selector', fakeAsync(() => { + const mockElement = jasmine.createSpyObj('HTMLElement', ['focus']); + spyOn(document, 'querySelector').and.returnValue(mockElement); + spyOn(contentApi, 'purgeDeletedNode').and.returnValue(of({})); + + const nodes = [{ entry: { id: '1', name: 'name1' } } as NodeEntry]; + + contentManagementService.purgeDeletedNodes(nodes, '.some-button'); + tick(); + + expect(document.querySelector).toHaveBeenCalledWith('.some-button'); + expect(mockElement.focus).toHaveBeenCalled(); + })); + describe('notification', () => { it('raises warning on multiple fail and one success', (done) => { spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { @@ -1307,6 +1335,28 @@ describe('ContentManagementService', () => { store.dispatch(new RestoreDeletedNodesAction(selection)); expect(openSnackMessageActionSpy.calls.argsFor(0)[2].panelClass).toBe('adf-info-snackbar'); }); + + it('should focus element when restoring nodes with focusAfterClosed selector', fakeAsync(() => { + const mockElement = jasmine.createSpyObj('HTMLElement', ['focus']); + spyOn(document, 'querySelector').and.returnValue(mockElement); + spyOn(contentApi, 'restoreNode').and.returnValue(of({} as NodeEntry)); + const path = { + elements: [ + { + id: '1-1', + name: 'Company Home' + } + ] + }; + + const nodes = [{ entry: { name: 'node1', id: '1', path } } as NodeEntry]; + + contentManagementService.restoreDeletedNodes(nodes, '.some-button'); + tick(100); + + expect(document.querySelector).toHaveBeenCalledWith('.some-button'); + expect(mockElement.focus).toHaveBeenCalled(); + })); }); }); @@ -1996,6 +2046,19 @@ describe('ContentManagementService', () => { expect(showInfoSpy).toHaveBeenCalledWith('APP.MESSAGES.INFO.FAVORITE_NODES_ADDED', null, { number: 2 }); }); + it('should focus element when adding favorite with focusAfterClosed selector', fakeAsync(() => { + const mockElement = jasmine.createSpyObj('HTMLElement', ['focus']); + + spyOn(document, 'querySelector').and.returnValue(mockElement); + spyOn(contentApi, 'addFavorite').and.returnValue(of({ entry: { targetGuid: '', target: '' } })); + + contentManagementService.addFavorite([fakeNode1, fakeNode2], '.some-button'); + tick(100); + + expect(document.querySelector).toHaveBeenCalledWith('.some-button'); + expect(mockElement.focus).toHaveBeenCalled(); + })); + it('should call proper content api and display proper snackbar message if one node is provided for removeFavorite', () => { spyOn(contentApi, 'removeFavorite').and.returnValue(of({})); @@ -2024,5 +2087,18 @@ describe('ContentManagementService', () => { expect(contentApi.removeFavorite).toHaveBeenCalledWith([fakeNode1, fakeNode2]); expect(showErrorSpy).toHaveBeenCalledWith('APP.MESSAGES.ERRORS.FAVORITE_NODE_NOT_FOUND', null, { name: 'mock-folder2-name' }); }); + + it('should focus element when removing favorite with focusAfterClosed selector', fakeAsync(() => { + const mockElement = jasmine.createSpyObj('HTMLElement', ['focus']); + + spyOn(document, 'querySelector').and.returnValue(mockElement); + spyOn(contentApi, 'removeFavorite').and.returnValue(of({})); + + contentManagementService.removeFavorite([fakeNode1, fakeNode2], '.some-button'); + tick(100); + + expect(document.querySelector).toHaveBeenCalledWith('.some-button'); + expect(mockElement.focus).toHaveBeenCalled(); + })); }); }); diff --git a/projects/aca-content/src/lib/services/content-management.service.ts b/projects/aca-content/src/lib/services/content-management.service.ts index b0a2ab08f..08ec47323 100644 --- a/projects/aca-content/src/lib/services/content-management.service.ts +++ b/projects/aca-content/src/lib/services/content-management.service.ts @@ -93,7 +93,8 @@ export class ContentManagementService { private readonly documentListService = inject(DocumentListService); private readonly createMenuButtonSelector = 'app-toolbar-menu button[id="app.toolbar.create"]'; - addFavorite(nodes: Array) { + addFavorite(nodes: Array, focusedElementOnCloseSelector?: string) { + this.focusAfterClose(focusedElementOnCloseSelector); if (nodes && nodes.length > 0) { this.contentApi.addFavorite(nodes).subscribe(() => { const favoriteNodes = nodes.map((node) => { @@ -112,7 +113,8 @@ export class ContentManagementService { } } - removeFavorite(nodes: Array) { + removeFavorite(nodes: Array, focusedElementOnCloseSelector?: string) { + this.focusAfterClose(focusedElementOnCloseSelector); if (nodes && nodes.length > 0) { this.contentApi.removeFavorite(nodes).subscribe({ next: () => { @@ -373,7 +375,7 @@ export class ContentManagementService { return this.permission.check(folderNode, ['create']); } - purgeDeletedNodes(nodes: NodeEntry[]) { + purgeDeletedNodes(nodes: NodeEntry[], focusedElementOnCloseSelector?: string) { if (!nodes || nodes.length === 0) { return; } @@ -389,6 +391,7 @@ export class ContentManagementService { }); dialogRef.afterClosed().subscribe((result) => { + this.focusAfterClose(focusedElementOnCloseSelector); if (result === true) { const nodesToDelete: NodeInfo[] = nodes.map((node) => { const { name } = node.entry; @@ -404,7 +407,7 @@ export class ContentManagementService { }); } - restoreDeletedNodes(selection: NodeEntry[] = []) { + restoreDeletedNodes(selection: NodeEntry[] = [], focusedElementOnCloseSelector?: string) { if (!selection.length) { return; } @@ -433,8 +436,11 @@ export class ContentManagementService { const remainingNodes = this.diff(selectedNodes, nodes.list.entries); if (!remainingNodes.length) { + this.focusAfterClose(focusedElementOnCloseSelector); this.showRestoreNotification(status); - this.documentListService.reload(); + setTimeout(() => { + this.documentListService.reload(); + }, 50); } else { this.restoreDeletedNodes(remainingNodes); } @@ -689,7 +695,8 @@ export class ContentManagementService { ); } - deleteNodes(items: NodeEntry[], allowUndo = true): void { + deleteNodes(items: NodeEntry[], allowUndo = true, focusedElementOnCloseSelector?: string): void { + this.focusAfterClose(focusedElementOnCloseSelector); const batch: Observable[] = []; items.forEach((node) => { diff --git a/projects/aca-content/src/lib/store/effects/favorite.effects.spec.ts b/projects/aca-content/src/lib/store/effects/favorite.effects.spec.ts new file mode 100644 index 000000000..4c03a1a50 --- /dev/null +++ b/projects/aca-content/src/lib/store/effects/favorite.effects.spec.ts @@ -0,0 +1,88 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { FavoriteEffects } from './favorite.effects'; +import { ContentManagementService } from '../../services/content-management.service'; +import { AddFavoriteAction, AppStore, RemoveFavoriteAction, SetSelectedNodesAction } from '@alfresco/aca-shared/store'; +import { Store } from '@ngrx/store'; +import { provideEffects } from '@ngrx/effects'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { NodeEntry } from '@alfresco/js-api'; + +describe('FavoriteEffects', () => { + let contentService: ContentManagementService; + let store: Store; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [AppTestingModule], + providers: [provideEffects([FavoriteEffects])] + }); + + store = TestBed.inject(Store); + contentService = TestBed.inject(ContentManagementService); + }); + + it('should call addFavorite with payload when AddFavoriteAction is dispatched', () => { + spyOn(contentService, 'addFavorite').and.stub(); + const payload = [{ entry: { id: 'node1' } }] as NodeEntry[]; + store.dispatch(new AddFavoriteAction(payload, { focusedElementOnCloseSelector: 'test-selector' })); + + expect(contentService.addFavorite).toHaveBeenCalledWith(payload, 'test-selector'); + }); + + it('should call addFavorite with selection when AddFavoriteAction is dispatched without payload', fakeAsync(() => { + spyOn(contentService, 'addFavorite').and.stub(); + + const selection = [{ entry: { isFile: true } }] as NodeEntry[]; + store.dispatch(new SetSelectedNodesAction(selection)); + + tick(100); + + store.dispatch(new AddFavoriteAction(null, { focusedElementOnCloseSelector: 'test-selector' })); + + expect(contentService.addFavorite).toHaveBeenCalledWith(selection, 'test-selector'); + })); + + it('should call removeFavorite with payload when RemoveFavoriteAction is dispatched', () => { + spyOn(contentService, 'removeFavorite').and.stub(); + const payload = [{ entry: { id: 'node1' } }] as NodeEntry[]; + + store.dispatch(new RemoveFavoriteAction(payload, { focusedElementOnCloseSelector: 'test-selector' })); + expect(contentService.removeFavorite).toHaveBeenCalledWith(payload, 'test-selector'); + }); + + it('should call removeFavorite with selection when RemoveFavoriteAction is dispatched without payload', fakeAsync(() => { + spyOn(contentService, 'removeFavorite').and.stub(); + + const selection: any = [{ entry: { isFile: true } }] as NodeEntry[]; + store.dispatch(new SetSelectedNodesAction(selection)); + + tick(100); + + store.dispatch(new RemoveFavoriteAction(null, { focusedElementOnCloseSelector: 'test-selector' })); + expect(contentService.removeFavorite).toHaveBeenCalledWith(selection, 'test-selector'); + })); +}); diff --git a/projects/aca-content/src/lib/store/effects/favorite.effects.ts b/projects/aca-content/src/lib/store/effects/favorite.effects.ts index 5e5a67396..ee1db6791 100644 --- a/projects/aca-content/src/lib/store/effects/favorite.effects.ts +++ b/projects/aca-content/src/lib/store/effects/favorite.effects.ts @@ -41,14 +41,14 @@ export class FavoriteEffects { ofType(NodeActionTypes.AddFavorite), map((action) => { if (action.payload && action.payload.length > 0) { - this.content.addFavorite(action.payload); + this.content.addFavorite(action.payload, action.configuration.focusedElementOnCloseSelector); } else { this.store .select(getAppSelection) .pipe(take(1)) .subscribe((selection) => { if (selection && !selection.isEmpty) { - this.content.addFavorite(selection.nodes); + this.content.addFavorite(selection.nodes, action.configuration.focusedElementOnCloseSelector); } }); } @@ -63,14 +63,14 @@ export class FavoriteEffects { ofType(NodeActionTypes.RemoveFavorite), map((action) => { if (action.payload && action.payload.length > 0) { - this.content.removeFavorite(action.payload); + this.content.removeFavorite(action.payload, action.configuration.focusedElementOnCloseSelector); } else { this.store .select(getAppSelection) .pipe(take(1)) .subscribe((selection) => { if (selection && !selection.isEmpty) { - this.content.removeFavorite(selection.nodes); + this.content.removeFavorite(selection.nodes, action.configuration.focusedElementOnCloseSelector); } }); } 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 a6c459f22..bcb45182d 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 @@ -156,7 +156,7 @@ describe('NodeEffects', () => { const node: any = {}; store.dispatch(new PurgeDeletedNodesAction([node])); - expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node]); + expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node], undefined); }); it('should purge nodes from the active selection', fakeAsync(() => { @@ -167,8 +167,8 @@ describe('NodeEffects', () => { tick(100); - store.dispatch(new PurgeDeletedNodesAction(null)); - expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node]); + store.dispatch(new PurgeDeletedNodesAction([node], { focusedElementOnCloseSelector: '.test-selector' })); + expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node], '.test-selector'); })); it('should do nothing if invoking purge with no data', () => { @@ -187,7 +187,7 @@ describe('NodeEffects', () => { const node: any = {}; store.dispatch(new RestoreDeletedNodesAction([node])); - expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node]); + expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node], undefined); }); it('should restore deleted nodes from the active selection', fakeAsync(() => { @@ -198,8 +198,8 @@ describe('NodeEffects', () => { tick(100); - store.dispatch(new RestoreDeletedNodesAction(null)); - expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node]); + store.dispatch(new RestoreDeletedNodesAction(null, { focusedElementOnCloseSelector: '.test-selector' })); + expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node], '.test-selector'); })); it('should do nothing if invoking restore with no data', () => { @@ -220,7 +220,7 @@ describe('NodeEffects', () => { expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new DeleteNodesAction([node], true) })); expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new ShowLoaderAction(true) })); - expect(contentService.deleteNodes).toHaveBeenCalledWith([node], true); + expect(contentService.deleteNodes).toHaveBeenCalledWith([node], true, undefined); }); it('should delete nodes from the active selection', fakeAsync(() => { @@ -231,11 +231,13 @@ describe('NodeEffects', () => { tick(100); - store.dispatch(new DeleteNodesAction(null)); + store.dispatch(new DeleteNodesAction(null, true, { focusedElementOnCloseSelector: '.test-selector' })); - expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new DeleteNodesAction(null, true) })); + expect(store.dispatch).toHaveBeenCalledWith( + jasmine.objectContaining({ ...new DeleteNodesAction(null, true, { focusedElementOnCloseSelector: '.test-selector' }) }) + ); expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new ShowLoaderAction(true) })); - expect(contentService.deleteNodes).toHaveBeenCalledWith([node], true); + expect(contentService.deleteNodes).toHaveBeenCalledWith([node], true, '.test-selector'); })); it('should do nothing if invoking delete with no data', () => { 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 a71de9ee7..649f048bf 100644 --- a/projects/aca-content/src/lib/store/effects/node.effects.ts +++ b/projects/aca-content/src/lib/store/effects/node.effects.ts @@ -119,14 +119,14 @@ export class NodeEffects { ofType(NodeActionTypes.PurgeDeleted), map((action) => { if (action?.payload?.length > 0) { - this.contentService.purgeDeletedNodes(action.payload); + this.contentService.purgeDeletedNodes(action.payload, action.configuration?.focusedElementOnCloseSelector); } else { this.store .select(getAppSelection) .pipe(take(1)) .subscribe((selection) => { if (selection && selection.count > 0) { - this.contentService.purgeDeletedNodes(selection.nodes); + this.contentService.purgeDeletedNodes(selection.nodes, action.configuration?.focusedElementOnCloseSelector); } }); } @@ -141,14 +141,14 @@ export class NodeEffects { ofType(NodeActionTypes.RestoreDeleted), map((action) => { if (action?.payload?.length > 0) { - this.contentService.restoreDeletedNodes(action.payload); + this.contentService.restoreDeletedNodes(action.payload, action.configuration?.focusedElementOnCloseSelector); } else { this.store .select(getAppSelection) .pipe(take(1)) .subscribe((selection) => { if (selection && selection.count > 0) { - this.contentService.restoreDeletedNodes(selection.nodes); + this.contentService.restoreDeletedNodes(selection.nodes, action.configuration?.focusedElementOnCloseSelector); } }); } @@ -164,14 +164,14 @@ export class NodeEffects { map((action) => { this.store.dispatch(new ShowLoaderAction(true)); if (action?.payload?.length > 0) { - this.contentService.deleteNodes(action.payload, action.allowUndo); + this.contentService.deleteNodes(action.payload, action.allowUndo, action.configuration?.focusedElementOnCloseSelector); } else { this.store .select(getAppSelection) .pipe(take(1)) .subscribe((selection) => { if (selection && selection.count > 0) { - this.contentService.deleteNodes(selection.nodes, action.allowUndo); + this.contentService.deleteNodes(selection.nodes, action.allowUndo, action.configuration?.focusedElementOnCloseSelector); } }); } diff --git a/projects/aca-shared/store/src/actions/node.actions.ts b/projects/aca-shared/store/src/actions/node.actions.ts index 571e35213..eb2733eb5 100644 --- a/projects/aca-shared/store/src/actions/node.actions.ts +++ b/projects/aca-shared/store/src/actions/node.actions.ts @@ -63,7 +63,8 @@ export class DeleteNodesAction implements Action { constructor( public payload: NodeEntry[] = [], - public allowUndo = true + public allowUndo = true, + public configuration?: ModalConfiguration ) {} } @@ -76,13 +77,19 @@ export class UndoDeleteNodesAction implements Action { export class RestoreDeletedNodesAction implements Action { readonly type = NodeActionTypes.RestoreDeleted; - constructor(public payload: Array) {} + constructor( + public payload: Array, + public configuration?: ModalConfiguration + ) {} } export class PurgeDeletedNodesAction implements Action { readonly type = NodeActionTypes.PurgeDeleted; - constructor(public payload: Array) {} + constructor( + public payload: Array, + public configuration?: ModalConfiguration + ) {} } export class DownloadNodesAction implements Action { @@ -183,13 +190,19 @@ export class UnlockWriteAction implements Action { export class AddFavoriteAction implements Action { readonly type = NodeActionTypes.AddFavorite; - constructor(public payload: Array) {} + constructor( + public payload: Array, + public configuration?: ModalConfiguration + ) {} } export class RemoveFavoriteAction implements Action { readonly type = NodeActionTypes.RemoveFavorite; - constructor(public payload: Array) {} + constructor( + public payload: Array, + public configuration?: ModalConfiguration + ) {} } export class ManageAspectsAction implements Action { readonly type = NodeActionTypes.ChangeAspects;