[ACS-9398] Add snackbar notifications for favorite node directive (#10714)

* [ACS-9398] Add snackbar notifications for favorite node directive

* [ACS-9398] Fix sonarqube issues

* [ACS-9398] Add snackbar notifications for library favorite directive
This commit is contained in:
MichalKinas 2025-03-18 08:33:22 +01:00 committed by GitHub
parent e8d1328244
commit 838b3e78b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 121 additions and 63 deletions

View File

@ -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<TestComponent>;
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' });
});
});

View File

@ -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));

View File

@ -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.toggleFavorite();
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();
});
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.toggleFavorite();
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();
});
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.toggleFavorite();
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();
});
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.toggleFavorite();
tick();
directive.toggle.subscribe(() => {
expect(directive.toggle.emit).toHaveBeenCalled();
}));
expect(notificationService.showInfo).toHaveBeenCalledWith('NODE_FAVORITE_DIRECTIVE.MESSAGES.NODE_REMOVED', null, { name: 'name1' });
done();
});
directive.toggleFavorite();
});
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.toggleFavorite();
tick();
directive.toggle.subscribe(() => {
expect(directive.toggle.emit).toHaveBeenCalled();
}));
done();
});
directive.toggleFavorite();
});
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.toggleFavorite();
tick();
directive.error.subscribe(() => {
expect(directive.error.emit).toHaveBeenCalledWith(error);
}));
done();
});
directive.toggleFavorite();
});
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.toggleFavorite();
tick();
directive.error.subscribe(() => {
expect(directive.error.emit).toHaveBeenCalledWith(error);
}));
done();
});
directive.toggleFavorite();
});
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.toggleFavorite();
tick();
directive.toggle.subscribe(() => {
expect(directive.hasFavorites()).toBe(false);
}));
done();
});
directive.toggleFavorite();
});
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.toggleFavorite();
tick();
directive.toggle.subscribe(() => {
expect(directive.hasFavorites()).toBe(true);
}));
done();
});
directive.toggleFavorite();
});
});
describe('getFavorite()', () => {

View File

@ -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)
});
}
}

View File

@ -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"
}
}
}