[ACS-8746] use notification service instead of store, yellow warnings, warning on no library permission (#4577)

* [ACS-8746] use notification service instead of store, yellow warnings, warning on no library permissions

* [ACS-8746] move snackbar bg styling to app level, review comments

* [ACS-8746] change warning color for improved accessibility

* [ACS-8746] fix wrong css class for copy and move notifications

* [ACS-8746] completely replace SnackbarActions with notifications service, update warning color

* [ACS-8746] improve copy and move nodes notifications

* ACS-8746 fix undo delete notification action

* ACS-8746 review comments

* ACS-8746 review comments
This commit is contained in:
Grzegorz Jaśkowski
2025-06-16 10:14:27 +02:00
committed by GitHub
parent 1072c7d2f3
commit f48fc8c2d7
45 changed files with 476 additions and 786 deletions

View File

@@ -548,7 +548,7 @@ describe('FilesComponent', () => {
);
fixture.detectChanges();
expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS');
expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING');
});
it('should have set text to library not found error if library does not exist and actual url is libraries', () => {
@@ -638,7 +638,7 @@ describe('FilesComponent', () => {
);
fixture.detectChanges();
expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS');
expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING');
});
it('should have set text to library not found error if library does not exist issue and actual url is libraries', () => {

View File

@@ -409,7 +409,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
if (this.router.url.includes('libraries')) {
switch (error.status) {
case 403:
this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS';
this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING';
break;
case 404:
this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND';

View File

@@ -25,28 +25,30 @@
import { LibraryMetadataFormComponent } from './library-metadata-form.component';
import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing';
import { Store } from '@ngrx/store';
import { SnackbarAction, SnackbarErrorAction, SnackbarInfoAction, UpdateLibraryAction } from '@alfresco/aca-shared/store';
import { UpdateLibraryAction } from '@alfresco/aca-shared/store';
import { AppHookService } from '@alfresco/aca-shared';
import { AppTestingModule } from '../../../testing/app-testing.module';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Site, SiteBodyCreate, SitePaging } from '@alfresco/js-api';
import { Actions } from '@ngrx/effects';
import { Site, SiteBodyCreate, SiteEntry, SitePaging } from '@alfresco/js-api';
import { of, Subject } from 'rxjs';
describe('LibraryMetadataFormComponent', () => {
let fixture: ComponentFixture<LibraryMetadataFormComponent>;
let component: LibraryMetadataFormComponent;
let store: Store<any>;
let actions$: Subject<SnackbarAction>;
let siteEntryModel: SiteBodyCreate;
let appHookService: AppHookService;
beforeEach(() => {
actions$ = new Subject<SnackbarAction>();
TestBed.configureTestingModule({
imports: [AppTestingModule, LibraryMetadataFormComponent],
providers: [
{
provide: Actions,
useValue: actions$
provide: AppHookService,
useValue: {
libraryUpdated: new Subject<SiteEntry>(),
libraryUpdateFailed: new Subject<void>()
}
},
{
provide: Store,
@@ -59,6 +61,7 @@ describe('LibraryMetadataFormComponent', () => {
schemas: [NO_ERRORS_SCHEMA]
});
appHookService = TestBed.inject(AppHookService);
store = TestBed.inject(Store);
fixture = TestBed.createComponent(LibraryMetadataFormComponent);
@@ -120,24 +123,10 @@ describe('LibraryMetadataFormComponent', () => {
component.ngOnInit();
component.form.setValue(entry);
actions$.next(new SnackbarInfoAction('LIBRARY.SUCCESS.LIBRARY_UPDATED'));
appHookService.libraryUpdated.next({ entry: entry } as SiteEntry);
expect(component.node.entry).toEqual(jasmine.objectContaining(entry));
});
it('should not assign form value to node entry if info snackbar was displayed for different action than updating library', () => {
const entry = {
id: 'libraryId',
title: 'some different title',
description: 'some different description',
visibility: Site.VisibilityEnum.PUBLIC
} as Site;
component.ngOnInit();
component.form.setValue(entry);
actions$.next(new SnackbarInfoAction('Some different action'));
expect(component.node.entry).not.toEqual(jasmine.objectContaining(entry));
});
it('should call markAsDirty on form if updating of form is finished with error', () => {
component.node = {
entry: {
@@ -151,18 +140,10 @@ describe('LibraryMetadataFormComponent', () => {
component.ngOnInit();
spyOn(component.form, 'markAsDirty');
actions$.next(new SnackbarErrorAction('LIBRARY.ERRORS.LIBRARY_UPDATE_ERROR'));
appHookService.libraryUpdateFailed.next();
expect(component.form.markAsDirty).toHaveBeenCalled();
});
it('should not call markAsDirty on form if error snackbar was displayed for different action than updating library', () => {
component.ngOnInit();
spyOn(component.form, 'markAsDirty');
actions$.next(new SnackbarErrorAction('Some different action'));
expect(component.form.markAsDirty).not.toHaveBeenCalled();
});
it('should update library node if form is valid', () => {
component.node.entry.role = Site.RoleEnum.SiteManager;

View File

@@ -36,16 +36,9 @@ import {
} from '@angular/forms';
import { QueriesApi, SiteEntry, SitePaging } from '@alfresco/js-api';
import { Store } from '@ngrx/store';
import {
AppStore,
isAdmin,
SnackbarAction,
SnackbarActionTypes,
SnackbarErrorAction,
SnackbarInfoAction,
UpdateLibraryAction
} from '@alfresco/aca-shared/store';
import { debounceTime, filter, mergeMap } from 'rxjs/operators';
import { AppStore, isAdmin, UpdateLibraryAction } from '@alfresco/aca-shared/store';
import { AppHookService } from '@alfresco/aca-shared';
import { debounceTime, mergeMap } from 'rxjs/operators';
import { AlfrescoApiService } from '@alfresco/adf-content-services';
import { from, Observable } from 'rxjs';
import { ErrorStateMatcher, MatOptionModule } from '@angular/material/core';
@@ -57,7 +50,6 @@ import { MatSelectModule } from '@angular/material/select';
import { MatInputModule } from '@angular/material/input';
import { A11yModule } from '@angular/cdk/a11y';
import { MatButtonModule } from '@angular/material/button';
import { Actions, ofType } from '@ngrx/effects';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class InstantErrorStateMatcher implements ErrorStateMatcher {
@@ -127,7 +119,7 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges {
constructor(
private readonly alfrescoApiService: AlfrescoApiService,
protected readonly store: Store<AppStore>,
private readonly actions$: Actions
private readonly appHookService: AppHookService
) {}
toggleEdit() {
@@ -186,10 +178,12 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges {
this.isAdmin = value;
});
this.canUpdateLibrary = this.node?.entry?.role === 'SiteManager' || this.isAdmin;
this.handleUpdatingEvent<SnackbarInfoAction>(SnackbarActionTypes.Info, 'LIBRARY.SUCCESS.LIBRARY_UPDATED', () =>
Object.assign(this.node.entry, this.form.value)
);
this.handleUpdatingEvent<SnackbarErrorAction>(SnackbarActionTypes.Error, 'LIBRARY.ERRORS.LIBRARY_UPDATE_ERROR', () => this.form.markAsDirty());
this.appHookService.libraryUpdated.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
Object.assign(this.node.entry, this.form.value);
});
this.appHookService.libraryUpdateFailed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.form.markAsDirty();
});
}
ngOnChanges() {
@@ -231,16 +225,6 @@ export class LibraryMetadataFormComponent implements OnInit, OnChanges {
);
}
private handleUpdatingEvent<T extends SnackbarAction>(actionType: SnackbarActionTypes, payload: string, handle: () => void): void {
this.actions$
.pipe(
ofType<T>(actionType),
filter((action) => action.payload === payload),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(handle);
}
private validateEmptyName(control: FormControl<string>): ValidationErrors {
return control.value.length && !control.value.trim() ? { empty: true } : null;
}

View File

@@ -26,14 +26,14 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { SearchResultsComponent } from './search-results.component';
import { AppConfigService, NotificationService, TranslationService } from '@alfresco/adf-core';
import { Store } from '@ngrx/store';
import { NavigateToFolder, SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store';
import { NavigateToFolder } from '@alfresco/aca-shared/store';
import { Pagination, SearchRequest } from '@alfresco/js-api';
import { SavedSearchesService, SearchQueryBuilderService } from '@alfresco/adf-content-services';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, of, Subject, throwError } from 'rxjs';
import { AppTestingModule } from '../../../testing/app-testing.module';
import { AppService } from '@alfresco/aca-shared';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSnackBarModule, MatSnackBarRef } from '@angular/material/snack-bar';
import { Buffer } from 'buffer';
import { testHeader } from '../../../testing/document-base-page-utils';
import { HarnessLoader } from '@angular/cdk/testing';
@@ -53,7 +53,8 @@ describe('SearchComponent', () => {
let route: ActivatedRoute;
const searchRequest = {} as SearchRequest;
let params: BehaviorSubject<any>;
let showErrorSpy: jasmine.Spy;
let showErrorSpy: jasmine.Spy<(message: string, action?: string, interpolateArgs?: any, showAction?: boolean) => MatSnackBarRef<any>>;
let showInfoSpy: jasmine.Spy<(message: string, action?: string, interpolateArgs?: any, showAction?: boolean) => MatSnackBarRef<any>>;
let loader: HarnessLoader;
const editSavedSearchesSpy = jasmine.createSpy('editSavedSearch');
@@ -109,6 +110,7 @@ describe('SearchComponent', () => {
const notificationService = TestBed.inject(NotificationService);
showErrorSpy = spyOn(notificationService, 'showError');
showInfoSpy = spyOn(notificationService, 'showInfo');
config.config = {
search: {}
@@ -290,19 +292,17 @@ describe('SearchComponent', () => {
}));
it('should dispatch success snackbar action when editing saved search is successful', fakeAsync(() => {
spyOn(store, 'dispatch').and.stub();
editSavedSearchesSpy.and.returnValue(of({}));
component.editSavedSearch({ name: 'test', encodedUrl: 'test', order: 0 });
tick();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.SUCCESS_MESSAGE'));
expect(showInfoSpy).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.SUCCESS_MESSAGE');
}));
it('should dispatch error snackbar action when editing saved search failed', fakeAsync(() => {
spyOn(store, 'dispatch').and.stub();
editSavedSearchesSpy.and.returnValue(throwError(() => new Error('')));
component.editSavedSearch({ name: 'test', encodedUrl: 'test', order: 0 });
tick();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.ERROR_MESSAGE'));
expect(showErrorSpy).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.ERROR_MESSAGE');
}));
testHeader(SearchResultsComponent, false);

View File

@@ -43,9 +43,7 @@ import {
SetInfoDrawerPreviewStateAction,
SetInfoDrawerStateAction,
SetSearchItemsTotalCountAction,
ShowInfoDrawerPreviewAction,
SnackbarErrorAction,
SnackbarInfoAction
ShowInfoDrawerPreviewAction
} from '@alfresco/aca-shared/store';
import {
CustomEmptyContentTemplateDirective,
@@ -333,10 +331,10 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
.pipe(take(1))
.subscribe({
next: () => {
this.store.dispatch(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.SUCCESS_MESSAGE'));
this.notificationService.showInfo('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.SUCCESS_MESSAGE');
},
error: () => {
this.store.dispatch(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.ERROR_MESSAGE'));
this.notificationService.showError('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.ERROR_MESSAGE');
}
});
}

View File

@@ -24,18 +24,16 @@
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { of } from 'rxjs';
import { of, throwError } from 'rxjs';
import { SavedSearchDeleteDialogComponent } from './saved-search-delete-dialog.component';
import { ContentTestingModule, SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services';
import { provideMockStore } from '@ngrx/store/testing';
import { Store } from '@ngrx/store';
import { SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store';
import { NotificationService } from '@alfresco/adf-core';
import { AppTestingModule } from '../../../../../testing/app-testing.module';
describe('SaveSearchDeleteDialogComponent', () => {
let fixture: ComponentFixture<SavedSearchDeleteDialogComponent>;
let notificationService: NotificationService;
let savedSearchesService: SavedSearchesService;
let store: Store;
let submitButton: HTMLButtonElement;
let cancelButton: HTMLButtonElement;
@@ -54,15 +52,14 @@ describe('SaveSearchDeleteDialogComponent', () => {
imports: [ContentTestingModule, AppTestingModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
provideMockStore(),
{ provide: SavedSearchesService, useValue: { deleteSavedSearch: () => of() } },
{ provide: SavedSearchesService, useValue: { deleteSavedSearch: () => of({}) } },
{ provide: MAT_DIALOG_DATA, useValue: savedSearchToDelete }
]
});
dialogRef.close.calls.reset();
fixture = TestBed.createComponent(SavedSearchDeleteDialogComponent);
savedSearchesService = TestBed.inject(SavedSearchesService);
store = TestBed.inject(Store);
notificationService = TestBed.inject(NotificationService);
submitButton = fixture.nativeElement.querySelector('#aca-save-search-delete-dialog-submit-button');
cancelButton = fixture.nativeElement.querySelector('#aca-save-search-delete-dialog-cancel-button');
@@ -79,17 +76,19 @@ describe('SaveSearchDeleteDialogComponent', () => {
expect(dialogRef.close).toHaveBeenCalled();
});
it('should delete search, show snackbar message and close modal if submit button clicked', fakeAsync(() => () => {
it('should delete search, show snackbar message and close modal if submit button clicked', fakeAsync(() => {
spyOn(savedSearchesService, 'deleteSavedSearch').and.callThrough();
spyOn(notificationService, 'showInfo');
clickSubmitButton();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.DELETE_DIALOG.SUCCESS_MESSAGE'));
expect(notificationService.showInfo).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.DELETE_DIALOG.SUCCESS_MESSAGE');
expect(dialogRef.close).toHaveBeenCalled();
}));
it('should show snackbar error if there is delete error', fakeAsync(() => () => {
spyOn(savedSearchesService, 'deleteSavedSearch').and.throwError('');
it('should show snackbar error if there is delete error', fakeAsync(() => {
spyOn(savedSearchesService, 'deleteSavedSearch').and.returnValue(throwError(() => new Error('Error')));
spyOn(notificationService, 'showError');
clickSubmitButton();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.DELETE_DIALOG.SUCCESS_MESSAGE'));
expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.DELETE_DIALOG.ERROR_MESSAGE');
expect(dialogRef.close).not.toHaveBeenCalled();
}));

View File

@@ -26,9 +26,7 @@ import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { take } from 'rxjs/operators';
import { AppStore, SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store';
import { Store } from '@ngrx/store';
import { CoreModule } from '@alfresco/adf-core';
import { CoreModule, NotificationService } from '@alfresco/adf-core';
@Component({
standalone: true,
@@ -44,8 +42,8 @@ export class SavedSearchDeleteDialogComponent {
constructor(
private readonly dialog: MatDialogRef<SavedSearchDeleteDialogComponent>,
private readonly notificationService: NotificationService,
private readonly savedSearchesService: SavedSearchesService,
private readonly store: Store<AppStore>,
@Inject(MAT_DIALOG_DATA) private readonly data: SavedSearch
) {}
@@ -60,11 +58,11 @@ export class SavedSearchDeleteDialogComponent {
.subscribe({
next: () => {
this.dialog.close(this.data);
this.store.dispatch(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.DELETE_DIALOG.SUCCESS_MESSAGE'));
this.notificationService.showInfo('APP.BROWSE.SEARCH.SAVE_SEARCH.DELETE_DIALOG.SUCCESS_MESSAGE');
this.isLoading = false;
},
error: () => {
this.store.dispatch(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.DELETE_DIALOG.ERROR_MESSAGE'));
this.notificationService.showError('APP.BROWSE.SEARCH.SAVE_SEARCH.DELETE_DIALOG.ERROR_MESSAGE');
this.isLoading = false;
}
});

View File

@@ -24,19 +24,18 @@
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { of } from 'rxjs';
import { of, throwError } from 'rxjs';
import { SavedSearchEditDialogComponent } from './saved-search-edit-dialog.component';
import { ContentTestingModule, SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services';
import { provideMockStore } from '@ngrx/store/testing';
import { Store } from '@ngrx/store';
import { SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store';
import { NotificationService } from '@alfresco/adf-core';
import { AppTestingModule } from '../../../../../testing/app-testing.module';
describe('SaveSearchEditDialogComponent', () => {
let fixture: ComponentFixture<SavedSearchEditDialogComponent>;
let component: SavedSearchEditDialogComponent;
let notificationService: NotificationService;
let savedSearchesService: SavedSearchesService;
let store: Store;
let submitButton: HTMLButtonElement;
const savedSearchToDelete: SavedSearch = {
@@ -55,15 +54,15 @@ describe('SaveSearchEditDialogComponent', () => {
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
provideMockStore(),
{ provide: SavedSearchesService, useValue: { editSavedSearch: () => of(), getSavedSearches: () => of([]) } },
{ provide: SavedSearchesService, useValue: { editSavedSearch: () => of({}), getSavedSearches: () => of([]) } },
{ provide: MAT_DIALOG_DATA, useValue: savedSearchToDelete }
]
});
dialogRef.close.calls.reset();
fixture = TestBed.createComponent(SavedSearchEditDialogComponent);
component = fixture.componentInstance;
notificationService = TestBed.inject(NotificationService);
savedSearchesService = TestBed.inject(SavedSearchesService);
store = TestBed.inject(Store);
submitButton = fixture.nativeElement.querySelector('#aca-saved-search-edit-dialog-submit-button');
});
@@ -80,22 +79,23 @@ describe('SaveSearchEditDialogComponent', () => {
expect(savedSearchesService.editSavedSearch).not.toHaveBeenCalled();
}));
it('should save search, show snackbar message and close modal if form is valid', fakeAsync(() => () => {
it('should save search, show snackbar message and close modal if form is valid', fakeAsync(() => {
spyOn(savedSearchesService, 'editSavedSearch').and.callThrough();
spyOn(notificationService, 'showInfo');
setFormValuesAndSubmit();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.SUCCESS_MESSAGE'));
expect(notificationService.showInfo).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.SUCCESS_MESSAGE');
expect(dialogRef.close).toHaveBeenCalled();
}));
it('should show snackbar error if there is save error', fakeAsync(() => () => {
spyOn(savedSearchesService, 'editSavedSearch').and.throwError('');
it('should show snackbar error if there is save error', fakeAsync(() => {
spyOn(savedSearchesService, 'editSavedSearch').and.returnValue(throwError(() => new Error('Error')));
spyOn(notificationService, 'showError');
setFormValuesAndSubmit();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.ERROR_MESSAGE'));
expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.ERROR_MESSAGE');
expect(dialogRef.close).not.toHaveBeenCalled();
}));
function setFormValuesAndSubmit() {
spyOn(store, 'dispatch');
component.form.controls['name'].setValue('ABCDEF');
component.form.controls['description'].setValue('TEST');
submitButton.click();

View File

@@ -26,9 +26,7 @@ import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { AutoFocusDirective, forbidOnlySpaces, SavedSearch, SavedSearchesService } from '@alfresco/adf-content-services';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { take } from 'rxjs/operators';
import { AppStore, SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store';
import { Store } from '@ngrx/store';
import { CoreModule } from '@alfresco/adf-core';
import { CoreModule, NotificationService } from '@alfresco/adf-core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { UniqueSearchNameValidator } from '../unique-search-name-validator';
import { SavedSearchForm } from '../saved-search-form.interface';
@@ -48,7 +46,7 @@ export class SavedSearchEditDialogComponent {
constructor(
private readonly dialog: MatDialogRef<SavedSearchEditDialogComponent>,
private readonly store: Store<AppStore>,
private readonly notificationService: NotificationService,
private readonly savedSearchesService: SavedSearchesService,
private readonly uniqueSearchNameValidator: UniqueSearchNameValidator,
@Inject(MAT_DIALOG_DATA) private readonly data: SavedSearch
@@ -92,7 +90,7 @@ export class SavedSearchEditDialogComponent {
this.isLoading = false;
},
error: () => {
this.store.dispatch(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.ERROR_MESSAGE'));
this.notificationService.showError('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.ERROR_MESSAGE');
this.isLoading = false;
}
});
@@ -100,6 +98,6 @@ export class SavedSearchEditDialogComponent {
private onEditSuccess(): void {
this.dialog.close();
this.store.dispatch(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.SUCCESS_MESSAGE'));
this.notificationService.showInfo('APP.BROWSE.SEARCH.SAVE_SEARCH.EDIT_DIALOG.SUCCESS_MESSAGE');
}
}

View File

@@ -24,19 +24,18 @@
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { of } from 'rxjs';
import { of, throwError } from 'rxjs';
import { SaveSearchDialogComponent } from './save-search-dialog.component';
import { ContentTestingModule, SavedSearchesService } from '@alfresco/adf-content-services';
import { provideMockStore } from '@ngrx/store/testing';
import { AppTestingModule } from '../../../../testing/app-testing.module';
import { Store } from '@ngrx/store';
import { SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store';
import { NotificationService } from '@alfresco/adf-core';
describe('SaveSearchDialogComponent', () => {
let fixture: ComponentFixture<SaveSearchDialogComponent>;
let component: SaveSearchDialogComponent;
let notificationService: NotificationService;
let savedSearchesService: SavedSearchesService;
let store: Store;
let submitButton: HTMLButtonElement;
const dialogRef = {
@@ -49,15 +48,15 @@ describe('SaveSearchDialogComponent', () => {
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
provideMockStore(),
{ provide: SavedSearchesService, useValue: { saveSearch: () => of(), getSavedSearches: () => of([]) } },
{ provide: SavedSearchesService, useValue: { saveSearch: () => of({}), getSavedSearches: () => of([]) } },
{ provide: MAT_DIALOG_DATA, useValue: { searchUrl: 'abcdef' } }
]
});
dialogRef.close.calls.reset();
fixture = TestBed.createComponent(SaveSearchDialogComponent);
component = fixture.componentInstance;
notificationService = TestBed.inject(NotificationService);
savedSearchesService = TestBed.inject(SavedSearchesService);
store = TestBed.inject(Store);
submitButton = fixture.nativeElement.querySelector('#aca-save-search-dialog-save-button');
});
@@ -75,22 +74,22 @@ describe('SaveSearchDialogComponent', () => {
expect(savedSearchesService.saveSearch).not.toHaveBeenCalled();
});
it('should save search, show snackbar message and close modal if form is valid', fakeAsync(() => () => {
it('should save search, show snackbar message and close modal if form is valid', fakeAsync(() => {
spyOn(savedSearchesService, 'saveSearch').and.callThrough();
spyOn(notificationService, 'showInfo');
setFormValuesAndSubmit();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_SUCCESS'));
expect(notificationService.showInfo).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_SUCCESS');
expect(dialogRef.close).toHaveBeenCalled();
}));
it('should show snackbar error if there is save error', fakeAsync(() => () => {
spyOn(savedSearchesService, 'saveSearch').and.throwError('');
it('should show snackbar error if there is save error', fakeAsync(() => {
spyOn(savedSearchesService, 'saveSearch').and.returnValue(throwError(() => new Error('Error')));
spyOn(notificationService, 'showError');
setFormValuesAndSubmit();
expect(store.dispatch).toHaveBeenCalledWith(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_ERROR'));
expect(dialogRef.close).not.toHaveBeenCalled();
expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_ERROR');
}));
function setFormValuesAndSubmit() {
spyOn(store, 'dispatch');
component.form.controls['name'].setValue('ABCDEF');
component.form.controls['description'].setValue('TEST');
submitButton.click();

View File

@@ -34,12 +34,10 @@ import { MatInputModule } from '@angular/material/input';
import { A11yModule } from '@angular/cdk/a11y';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { FormControl, FormGroup, FormsModule, Validators } from '@angular/forms';
import { CoreModule } from '@alfresco/adf-core';
import { CoreModule, NotificationService } from '@alfresco/adf-core';
import { AutoFocusDirective, forbidOnlySpaces, SavedSearchesService } from '@alfresco/adf-content-services';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppStore, SnackbarErrorAction, SnackbarInfoAction } from '@alfresco/aca-shared/store';
import { UniqueSearchNameValidator } from './unique-search-name-validator';
import { SavedSearchForm } from './saved-search-form.interface';
@@ -72,7 +70,7 @@ export class SaveSearchDialogComponent {
constructor(
private readonly dialog: MatDialogRef<SaveSearchDialogComponent>,
private readonly store: Store<AppStore>,
private readonly notificationService: NotificationService,
private readonly savedSearchesService: SavedSearchesService,
private readonly uniqueSearchNameValidator: UniqueSearchNameValidator,
@Inject(MAT_DIALOG_DATA) private readonly data: { searchUrl: string }
@@ -100,11 +98,11 @@ export class SaveSearchDialogComponent {
.subscribe({
next: () => {
this.dialog.close();
this.store.dispatch(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_SUCCESS'));
this.notificationService.showInfo('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_SUCCESS');
this.disableSubmitButton = false;
},
error: () => {
this.store.dispatch(new SnackbarErrorAction('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_ERROR'));
this.notificationService.showError('APP.BROWSE.SEARCH.SAVE_SEARCH.SAVE_ERROR');
this.disableSubmitButton = false;
}
});

View File

@@ -23,12 +23,11 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule, DataCellEvent, DataTableComponent } from '@alfresco/adf-core';
import { CoreTestingModule, DataCellEvent, DataTableComponent, NotificationService } from '@alfresco/adf-core';
import { SavedSearchesListUiComponent } from './saved-searches-list.ui-component';
import { SavedSearchesListUiService } from '../saved-searches-list-ui.service';
import { By } from '@angular/platform-browser';
import { SavedSearch } from '@alfresco/adf-content-services';
import { provideMockStore } from '@ngrx/store/testing';
import { Subject } from 'rxjs';
import { Clipboard } from '@angular/cdk/clipboard';
import { Router } from '@angular/router';
@@ -36,6 +35,7 @@ import { Router } from '@angular/router';
describe('SavedSearchesListUiComponent ', () => {
let fixture: ComponentFixture<SavedSearchesListUiComponent>;
let component: SavedSearchesListUiComponent;
let notificationService: NotificationService;
let savedSearchesListUiService: SavedSearchesListUiService;
let clipboard: Clipboard;
let router: Router;
@@ -43,9 +43,10 @@ describe('SavedSearchesListUiComponent ', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule, SavedSearchesListUiComponent],
providers: [SavedSearchesListUiService, provideMockStore()]
providers: [SavedSearchesListUiService]
});
notificationService = TestBed.inject(NotificationService);
savedSearchesListUiService = TestBed.inject(SavedSearchesListUiService);
clipboard = TestBed.inject(Clipboard);
router = TestBed.inject(Router);
@@ -196,6 +197,7 @@ describe('SavedSearchesListUiComponent ', () => {
let actionData: any;
beforeEach(() => {
spyOn(notificationService, 'showInfo');
actionData = dataCellEvent.value.actions[0];
actionData.subject.next(actionData);
});
@@ -203,6 +205,10 @@ describe('SavedSearchesListUiComponent ', () => {
it('should call copy to clipboard when selected delete option', () => {
expect(clipboard.copy).toHaveBeenCalled();
});
it('should show snackbar message when selected delete option', () => {
expect(notificationService.showInfo).toHaveBeenCalledWith('APP.BROWSE.SEARCH.SAVE_SEARCH.LIST.COPY_TO_CLIPBOARD_SUCCESS');
});
});
describe('Execute search', () => {

View File

@@ -29,6 +29,7 @@ import {
DATATABLE_DIRECTIVES,
DataTableComponent,
DataTableSchema,
NotificationService,
ShowHeaderMode,
TEMPLATE_DIRECTIVES
} from '@alfresco/adf-core';
@@ -39,8 +40,6 @@ import { SavedSearchesListUiService } from '../saved-searches-list-ui.service';
import { savedSearchesListSchema } from '../smart-list/saved-searches-list-schema';
import { SavedSearch } from '@alfresco/adf-content-services';
import { Clipboard } from '@angular/cdk/clipboard';
import { Store } from '@ngrx/store';
import { SnackbarInfoAction } from '@alfresco/aca-shared/store';
import { Router } from '@angular/router';
@Component({
@@ -61,6 +60,7 @@ export class SavedSearchesListUiComponent extends DataTableSchema implements Aft
readonly ShowHeaderMode = ShowHeaderMode;
private readonly notificationService = inject(NotificationService);
private readonly savedSearchesListUiService = inject(SavedSearchesListUiService);
private readonly destroyRef = inject(DestroyRef);
private readonly contextMenuAction$ = new Subject<any>();
@@ -94,7 +94,6 @@ export class SavedSearchesListUiComponent extends DataTableSchema implements Aft
constructor(
protected appConfig: AppConfigService,
private readonly clipboard: Clipboard,
private readonly store: Store,
private readonly router: Router
) {
super(appConfig, '', savedSearchesListSchema);
@@ -140,7 +139,7 @@ export class SavedSearchesListUiComponent extends DataTableSchema implements Aft
copyToClipboard(savedSearch: SavedSearch): void {
this.clipboard.copy(this.getFullUrl(savedSearch.encodedUrl));
this.store.dispatch(new SnackbarInfoAction('APP.BROWSE.SEARCH.SAVE_SEARCH.LIST.COPY_TO_CLIPBOARD_SUCCESS'));
this.notificationService.showInfo('APP.BROWSE.SEARCH.SAVE_SEARCH.LIST.COPY_TO_CLIPBOARD_SUCCESS');
}
executeSearch(savedSearch: SavedSearch): void {