diff --git a/lib/content-services/src/lib/directives/library-favorite.directive.spec.ts b/lib/content-services/src/lib/directives/library-favorite.directive.spec.ts index 2b89a16b71..64be21d70e 100644 --- a/lib/content-services/src/lib/directives/library-favorite.directive.spec.ts +++ b/lib/content-services/src/lib/directives/library-favorite.directive.spec.ts @@ -20,6 +20,8 @@ import { LibraryFavoriteDirective } from './library-favorite.directive'; import { TestBed, ComponentFixture } from '@angular/core/testing'; import { LibraryEntity } from '../interfaces/library-entity.interface'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NotificationService } from '@alfresco/adf-core'; +import { ContentTestingModule } from '../testing/content.testing.module'; @Component({ standalone: true, @@ -38,11 +40,13 @@ describe('LibraryFavoriteDirective', () => { let fixture: ComponentFixture; let component: TestComponent; let selection: LibraryEntity; + let notificationService: NotificationService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, TestComponent, LibraryFavoriteDirective] + imports: [HttpClientTestingModule, TestComponent, LibraryFavoriteDirective, ContentTestingModule] }); + notificationService = TestBed.inject(NotificationService); fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; selection = { entry: { guid: 'guid', id: 'id', title: 'Site', visibility: 'PUBLIC' }, isLibrary: true, isFavorite: false }; @@ -80,9 +84,10 @@ describe('LibraryFavoriteDirective', () => { expect(component.directive.isFavorite()).toBe(false); }); - it('should call addFavorite() on click event when selection is not a favorite', async () => { + it('should call addFavorite() and display snackbar message on click event when selection is not a favorite', async () => { spyOn(component.directive.favoritesApi, 'getFavoriteSite').and.returnValue(Promise.reject(new Error('error'))); spyOn(component.directive.favoritesApi, 'createFavorite').and.returnValue(Promise.resolve(null)); + spyOn(notificationService, 'showInfo'); fixture.detectChanges(); await fixture.whenStable(); @@ -90,13 +95,18 @@ describe('LibraryFavoriteDirective', () => { expect(component.directive.isFavorite()).toBeFalsy(); fixture.nativeElement.querySelector('button').dispatchEvent(new MouseEvent('click')); + fixture.detectChanges(); + await fixture.whenStable(); + expect(component.directive.favoritesApi.createFavorite).toHaveBeenCalled(); + expect(notificationService.showInfo).toHaveBeenCalledWith('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_ADDED', null, { name: 'Site' }); }); - it('should call removeFavoriteSite() on click event when selection is favorite', async () => { + it('should call removeFavoriteSite() and display snackbar message on click event when selection is favorite', async () => { spyOn(component.directive.favoritesApi, 'getFavoriteSite').and.returnValue(Promise.resolve(null)); spyOn(component.directive.favoritesApi, 'deleteFavorite').and.returnValue(Promise.resolve()); + spyOn(notificationService, 'showInfo'); selection.isFavorite = true; @@ -111,5 +121,6 @@ describe('LibraryFavoriteDirective', () => { await fixture.whenStable(); expect(component.directive.favoritesApi.deleteFavorite).toHaveBeenCalled(); + expect(notificationService.showInfo).toHaveBeenCalledWith('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_REMOVED', null, { name: 'Site' }); }); }); diff --git a/lib/content-services/src/lib/directives/library-favorite.directive.ts b/lib/content-services/src/lib/directives/library-favorite.directive.ts index 6827d0c7d1..dc546f2b77 100644 --- a/lib/content-services/src/lib/directives/library-favorite.directive.ts +++ b/lib/content-services/src/lib/directives/library-favorite.directive.ts @@ -19,6 +19,7 @@ import { Directive, HostListener, Input, OnChanges, Output, EventEmitter, Simple import { FavoriteBodyCreate, FavoritesApi } from '@alfresco/js-api'; import { AlfrescoApiService } from '../services/alfresco-api.service'; import { LibraryEntity } from '../interfaces/library-entity.interface'; +import { NotificationService } from '@alfresco/adf-core'; @Directive({ standalone: true, @@ -58,7 +59,7 @@ export class LibraryFavoriteDirective implements OnChanges { } } - constructor(private alfrescoApiService: AlfrescoApiService) {} + constructor(private readonly alfrescoApiService: AlfrescoApiService, private readonly notificationService: NotificationService) {} ngOnChanges(changes: SimpleChanges) { if (!changes.library.currentValue) { @@ -92,6 +93,7 @@ export class LibraryFavoriteDirective implements OnChanges { .createFavorite('-me-', favoriteBody) .then((libraryEntry) => { this.targetLibrary.isFavorite = true; + this.notificationService.showInfo('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_ADDED', null, { name: this.library.entry.title }); this.toggle.emit(libraryEntry); }) .catch((error) => this.error.emit(error)); @@ -102,6 +104,7 @@ export class LibraryFavoriteDirective implements OnChanges { .deleteFavorite('-me-', favoriteId) .then((libraryBody) => { this.targetLibrary.isFavorite = false; + this.notificationService.showInfo('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_REMOVED', null, { name: this.library.entry.title }); this.toggle.emit(libraryBody); }) .catch((error) => this.error.emit(error)); diff --git a/lib/content-services/src/lib/directives/node-favorite.directive.spec.ts b/lib/content-services/src/lib/directives/node-favorite.directive.spec.ts index c6d6c3739e..7a014ad95a 100644 --- a/lib/content-services/src/lib/directives/node-favorite.directive.spec.ts +++ b/lib/content-services/src/lib/directives/node-favorite.directive.spec.ts @@ -18,25 +18,28 @@ import { SimpleChange } from '@angular/core'; import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NodeFavoriteDirective } from './node-favorite.directive'; -import { AppConfigService, AppConfigServiceMock } from '@alfresco/adf-core'; +import { AppConfigService, AppConfigServiceMock, NotificationService } from '@alfresco/adf-core'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { AlfrescoApiService } from '../services'; import { AlfrescoApiServiceMock } from '../mock'; +import { ContentTestingModule } from '../testing/content.testing.module'; describe('NodeFavoriteDirective', () => { let directive: NodeFavoriteDirective; let alfrescoApiService: AlfrescoApiService; + let notificationService: NotificationService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], + imports: [HttpClientTestingModule, ContentTestingModule], providers: [ { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }, { provide: AppConfigService, useClass: AppConfigServiceMock } ] }); alfrescoApiService = TestBed.inject(AlfrescoApiService); - directive = new NodeFavoriteDirective(alfrescoApiService); + notificationService = TestBed.inject(NotificationService); + directive = new NodeFavoriteDirective(alfrescoApiService, notificationService); }); describe('selection input change event', () => { @@ -174,7 +177,8 @@ describe('NodeFavoriteDirective', () => { expect(addFavoriteSpy).not.toHaveBeenCalled(); })); - it('should call addFavorite() if none is a favorite', () => { + it('should call addFavorite() and display snackbar message if none is a favorite', (done) => { + spyOn(notificationService, 'showInfo'); addFavoriteSpy.and.returnValue(Promise.resolve()); directive.favorites = [ @@ -182,12 +186,16 @@ describe('NodeFavoriteDirective', () => { { entry: { id: '2', name: 'name2', isFavorite: false } } ]; + directive.toggle.subscribe(() => { + expect(addFavoriteSpy.calls.argsFor(0)[1].length).toBe(2); + expect(notificationService.showInfo).toHaveBeenCalledWith('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODES_ADDED', null, { number: 2 }); + done(); + }); directive.toggleFavorite(); - - expect(addFavoriteSpy.calls.argsFor(0)[1].length).toBe(2); }); - it('should call addFavorite() on node that is not a favorite in selection', () => { + it('should call addFavorite() and display snackbar message on node that is not a favorite in selection', (done) => { + spyOn(notificationService, 'showInfo'); addFavoriteSpy.and.returnValue(Promise.resolve()); directive.favorites = [ @@ -195,96 +203,111 @@ describe('NodeFavoriteDirective', () => { { entry: { id: '2', name: 'name2', isFile: true, isFolder: false, isFavorite: true } } ]; + directive.toggle.subscribe(() => { + const callArgs = addFavoriteSpy.calls.argsFor(0)[1]; + const callParameter = callArgs[0]; + + expect(callArgs.length).toBe(1); + expect(callParameter.target.file.guid).toBe('1'); + expect(notificationService.showInfo).toHaveBeenCalledWith('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_ADDED', null, { name: 'name1' }); + done(); + }); directive.toggleFavorite(); - - const callArgs = addFavoriteSpy.calls.argsFor(0)[1]; - const callParameter = callArgs[0]; - - expect(callArgs.length).toBe(1); - expect(callParameter.target.file.guid).toBe('1'); }); - it('should call removeFavoriteSite() if all are favorites', () => { + it('should call removeFavoriteSite() and display snackbar message if all are favorites', (done) => { + spyOn(notificationService, 'showInfo'); removeFavoriteSpy.and.returnValue(Promise.resolve()); directive.favorites = [{ entry: { id: '1', name: 'name1', isFavorite: true } }, { entry: { id: '2', name: 'name2', isFavorite: true } }]; + directive.toggle.subscribe(() => { + expect(removeFavoriteSpy.calls.count()).toBe(2); + expect(notificationService.showInfo).toHaveBeenCalledWith('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODES_REMOVED', null, { number: 2 }); + done(); + }); directive.toggleFavorite(); - - expect(removeFavoriteSpy.calls.count()).toBe(2); }); - it('should emit event when removeFavoriteSite() is done', fakeAsync(() => { + it('should emit event and display snackbar message when removeFavoriteSite() is done', (done) => { + spyOn(notificationService, 'showInfo'); removeFavoriteSpy.and.returnValue(Promise.resolve()); - spyOn(directive.toggle, 'emit'); + spyOn(directive.toggle, 'emit').and.callThrough(); directive.favorites = [{ entry: { id: '1', name: 'name1', isFavorite: true } }]; + directive.toggle.subscribe(() => { + expect(directive.toggle.emit).toHaveBeenCalled(); + expect(notificationService.showInfo).toHaveBeenCalledWith('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_REMOVED', null, { name: 'name1' }); + done(); + }); directive.toggleFavorite(); - tick(); + }); - expect(directive.toggle.emit).toHaveBeenCalled(); - })); - - it('should emit event when addFavorite() is done', fakeAsync(() => { + it('should emit event when addFavorite() is done', (done) => { addFavoriteSpy.and.returnValue(Promise.resolve()); - spyOn(directive.toggle, 'emit'); + spyOn(directive.toggle, 'emit').and.callThrough(); directive.favorites = [{ entry: { id: '1', name: 'name1', isFavorite: false } }]; + directive.toggle.subscribe(() => { + expect(directive.toggle.emit).toHaveBeenCalled(); + done(); + }); directive.toggleFavorite(); - tick(); + }); - expect(directive.toggle.emit).toHaveBeenCalled(); - })); - - it('should emit error event when removeFavoriteSite() fails', fakeAsync(() => { + it('should emit error event when removeFavoriteSite() fails', (done) => { const error = new Error('error'); removeFavoriteSpy.and.returnValue(Promise.reject(error)); - spyOn(directive.error, 'emit'); + spyOn(directive.error, 'emit').and.callThrough(); directive.favorites = [{ entry: { id: '1', name: 'name1', isFavorite: true } }]; + directive.error.subscribe(() => { + expect(directive.error.emit).toHaveBeenCalledWith(error); + done(); + }); directive.toggleFavorite(); - tick(); + }); - expect(directive.error.emit).toHaveBeenCalledWith(error); - })); - - it('should emit error event when addFavorite() fails', fakeAsync(() => { + it('should emit error event when addFavorite() fails', (done) => { const error = new Error('error'); addFavoriteSpy.and.returnValue(Promise.reject(error)); - spyOn(directive.error, 'emit'); + spyOn(directive.error, 'emit').and.callThrough(); directive.favorites = [{ entry: { id: '1', name: 'name1', isFavorite: false } }]; + directive.error.subscribe(() => { + expect(directive.error.emit).toHaveBeenCalledWith(error); + done(); + }); directive.toggleFavorite(); - tick(); + }); - expect(directive.error.emit).toHaveBeenCalledWith(error); - })); - - it('should set isFavorites items to false', fakeAsync(() => { + it('should set isFavorites items to false', (done) => { removeFavoriteSpy.and.returnValue(Promise.resolve()); directive.favorites = [{ entry: { id: '1', name: 'name1', isFavorite: true } }]; + directive.toggle.subscribe(() => { + expect(directive.hasFavorites()).toBe(false); + done(); + }); directive.toggleFavorite(); - tick(); + }); - expect(directive.hasFavorites()).toBe(false); - })); - - it('should set isFavorites items to true', fakeAsync(() => { + it('should set isFavorites items to true', (done) => { addFavoriteSpy.and.returnValue(Promise.resolve()); directive.favorites = [{ entry: { id: '1', name: 'name1', isFavorite: false } }]; + directive.toggle.subscribe(() => { + expect(directive.hasFavorites()).toBe(true); + done(); + }); directive.toggleFavorite(); - tick(); - - expect(directive.hasFavorites()).toBe(true); - })); + }); }); describe('getFavorite()', () => { diff --git a/lib/content-services/src/lib/directives/node-favorite.directive.ts b/lib/content-services/src/lib/directives/node-favorite.directive.ts index 6d01edc02d..548030045b 100644 --- a/lib/content-services/src/lib/directives/node-favorite.directive.ts +++ b/lib/content-services/src/lib/directives/node-favorite.directive.ts @@ -22,6 +22,7 @@ import { FavoriteBodyCreate, NodeEntry, SharedLinkEntry, Node, SharedLink, Favor import { Observable, from, forkJoin, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { AlfrescoApiService } from '../services/alfresco-api.service'; +import { NotificationService } from '@alfresco/adf-core'; @Directive({ standalone: true, @@ -52,7 +53,7 @@ export class NodeFavoriteDirective implements OnChanges { this.toggleFavorite(); } - constructor(private alfrescoApiService: AlfrescoApiService) {} + constructor(private readonly alfrescoApiService: AlfrescoApiService, private readonly notificationService: NotificationService) {} ngOnChanges(changes: SimpleChanges) { if (!changes.selection.currentValue.length) { @@ -79,26 +80,38 @@ export class NodeFavoriteDirective implements OnChanges { return from(this.favoritesApi.deleteFavorite('-me-', id)); }); - forkJoin(batch).subscribe( - () => { + forkJoin(batch).subscribe({ + next: () => { this.favorites.forEach((selected) => (selected.entry.isFavorite = false)); + if (this.favorites.length > 1) { + this.notificationService.showInfo('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODES_REMOVED', null, { number: this.favorites.length }); + } else { + this.notificationService.showInfo('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_REMOVED', null, { + name: this.favorites[0].entry.name + }); + } this.toggle.emit(); }, - (error) => this.error.emit(error) - ); + error: (error) => this.error.emit(error) + }); } if (!every) { const notFavorite = this.favorites.filter((node) => !node.entry.isFavorite); const body = notFavorite.map((node) => this.createFavoriteBody(node)); - from(this.favoritesApi.createFavorite('-me-', body as any)).subscribe( - () => { + from(this.favoritesApi.createFavorite('-me-', body as any)).subscribe({ + next: () => { notFavorite.forEach((selected) => (selected.entry.isFavorite = true)); + if (notFavorite.length > 1) { + this.notificationService.showInfo('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODES_ADDED', null, { number: notFavorite.length }); + } else { + this.notificationService.showInfo('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_ADDED', null, { name: notFavorite[0].entry.name }); + } this.toggle.emit(); }, - (error) => this.error.emit(error) - ); + error: (error) => this.error.emit(error) + }); } } diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json index f4af2c839c..292f5178ef 100644 --- a/lib/content-services/src/lib/i18n/en.json +++ b/lib/content-services/src/lib/i18n/en.json @@ -708,5 +708,13 @@ "FOLDER_SELECTED": "Folders are not compatible with AI Agents." } } + }, + "NODE_FAVORITE_DIRECTIVE": { + "MESSAGES": { + "NODE_ADDED": "Added {{ name }} to favorites", + "NODES_ADDED": "Added {{ number }} items to favorites", + "NODE_REMOVED": "Removed {{ name }} from favorites", + "NODES_REMOVED": "Removed {{ number }} items from favorites" + } } }