Skip to content

Commit 072133c

Browse files
FrancescoMolinaroatarix83
authored andcommitted
Merged in task/main-cris/DSC-2318 (pull request DSpace#3121)
Task/main cris/DSC-2318 Approved-by: Giuseppe Digilio
2 parents 29f6cf2 + 0d181a9 commit 072133c

6 files changed

Lines changed: 160 additions & 37 deletions

File tree

src/app/shared/loading/loading.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { AlertComponent } from '../alert/alert.component';
2222
import { AlertType } from '../alert/alert-type';
2323
import { hasValue } from '../empty.util';
2424

25-
enum MessageType {
25+
export enum MessageType {
2626
LOADING = 'loading',
2727
WARNING = 'warning',
2828
ERROR = 'error'
Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,50 @@
1-
<div class="row flex-nowrap">
2-
<div [class.mb-2]="(viewMode$ | async) === ViewMode.ListElement" class="info-skeleton col-12">
3-
<ngx-skeleton-loader/>
1+
@if (showFallbackMessages) {
2+
<ng-container *ngVar="(delayTimer$ | async) as timer">
3+
@defer (when timer >= errorMessageDelay) {
4+
<ds-alert [type]="AlertTypeEnum.Error" [content]="errorMessage"></ds-alert>
5+
}
6+
@defer (when timer >= warningMessageDelay && timer < errorMessageDelay) {
7+
<label aria-live="polite">{{ warningMessage }}</label>
8+
}
9+
</ng-container>
10+
}
11+
12+
@if (!showFallbackMessages || ((delayTimer$ | async) < errorMessageDelay)) {
13+
<div class="row flex-nowrap">
14+
<div [class.mb-2]="(viewMode$ | async) === ViewMode.ListElement" class="info-skeleton col-12">
15+
<ngx-skeleton-loader/>
16+
</div>
417
</div>
5-
</div>
618

7-
@if((viewMode$ | async) === ViewMode.ListElement) {
8-
@for (result of loadingResults; track result; let first = $first) {
19+
@if((viewMode$ | async) === ViewMode.ListElement) {
20+
@for (result of loadingResults; track result; let first = $first) {
921
<div [class.my-4]="!first" class="row">
10-
@if(showThumbnails) {
11-
<div class="col-3 col-md-2">
12-
<div class="thumbnail-skeleton position-relative">
22+
@if(showThumbnails) {
23+
<div class="col-3 col-md-2">
24+
<div class="thumbnail-skeleton position-relative">
25+
<ngx-skeleton-loader/>
26+
</div>
27+
</div>
28+
}
29+
<div [class.col-9]="showThumbnails" [class.col-md-10]="showThumbnails" [class.col-md-12]="!showThumbnails">
30+
<div class="badge-skeleton">
1331
<ngx-skeleton-loader/>
1432
</div>
15-
</div>
16-
}
17-
<div [class.col-9]="showThumbnails" [class.col-md-10]="showThumbnails" [class.col-md-12]="!showThumbnails">
18-
<div class="badge-skeleton">
19-
<ngx-skeleton-loader/>
20-
</div>
21-
<div class="text-skeleton">
22-
<ngx-skeleton-loader [count]="textLineCount"/>
33+
<div class="text-skeleton">
34+
<ngx-skeleton-loader [count]="textLineCount"/>
35+
</div>
2336
</div>
2437
</div>
38+
}
39+
} @else if ((viewMode$ | async) === ViewMode.GridElement) {
40+
<div class="card-columns row">
41+
@for (result of loadingResults; track result) {
42+
<div class="card-column col col-sm-6 col-lg-4">
43+
<div class="card-skeleton">
44+
<ngx-skeleton-loader/>
45+
</div>
46+
</div>
47+
}
2548
</div>
2649
}
27-
} @else if ((viewMode$ | async) === ViewMode.GridElement) {
28-
<div class="card-columns row">
29-
@for (result of loadingResults; track result) {
30-
<div class="card-column col col-sm-6 col-lg-4">
31-
<div class="card-skeleton">
32-
<ngx-skeleton-loader/>
33-
</div>
34-
</div>
35-
}
36-
37-
</div>
3850
}
Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import {
22
ComponentFixture,
3+
fakeAsync,
34
TestBed,
5+
tick,
46
} from '@angular/core/testing';
7+
import { By } from '@angular/platform-browser';
8+
import {
9+
TranslateLoader,
10+
TranslateModule,
11+
} from '@ngx-translate/core';
512
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
613

14+
import { APP_CONFIG } from '../../../../../config/app-config.interface';
15+
import { environment } from '../../../../../environments/environment';
716
import { SearchService } from '../../../../core/shared/search/search.service';
17+
import { AlertComponent } from '../../../alert/alert.component';
18+
import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock';
819
import { SearchServiceStub } from '../../../testing/search-service.stub';
20+
import { VarDirective } from '../../../utils/var.directive';
921
import { SearchResultsSkeletonComponent } from './search-results-skeleton.component';
1022

1123
describe('SearchResultsSkeletonComponent', () => {
@@ -14,19 +26,57 @@ describe('SearchResultsSkeletonComponent', () => {
1426

1527
beforeEach(async () => {
1628
await TestBed.configureTestingModule({
17-
imports: [SearchResultsSkeletonComponent, NgxSkeletonLoaderModule],
29+
imports: [
30+
SearchResultsSkeletonComponent,
31+
AlertComponent,
32+
NgxSkeletonLoaderModule,
33+
VarDirective,
34+
TranslateModule.forRoot({
35+
loader: {
36+
provide: TranslateLoader,
37+
useClass: TranslateLoaderMock,
38+
},
39+
}),
40+
],
1841
providers: [
1942
{ provide: SearchService, useValue: new SearchServiceStub() },
43+
{ provide: APP_CONFIG, useValue: environment },
2044
],
45+
}).overrideComponent(SearchResultsSkeletonComponent, {
46+
remove: {
47+
imports: [
48+
AlertComponent,
49+
],
50+
},
2151
})
2252
.compileComponents();
2353

2454
fixture = TestBed.createComponent(SearchResultsSkeletonComponent);
2555
component = fixture.componentInstance;
56+
component.warningMessage = 'test warning message';
57+
component.errorMessage = 'test error message';
2658
fixture.detectChanges();
2759
});
2860

2961
it('should create', () => {
3062
expect(component).toBeTruthy();
3163
});
64+
65+
it('should display warning message', fakeAsync(() => {
66+
component.warningMessageDelay = 0;
67+
fixture.detectChanges();
68+
tick(100);
69+
fixture.detectChanges();
70+
const label = fixture.debugElement.query(By.css('label')).nativeElement;
71+
expect(label.textContent).toContain(component.warningMessage);
72+
}));
73+
74+
it('should display error message', fakeAsync(() => {
75+
component.errorMessageDelay = 0;
76+
fixture.detectChanges();
77+
tick(100);
78+
fixture.detectChanges();
79+
const alert = fixture.debugElement.query(By.css('ds-alert'));
80+
expect(alert).toBeTruthy();
81+
}));
3282
});

src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
1-
import {
2-
AsyncPipe,
3-
NgForOf,
4-
} from '@angular/common';
1+
import { AsyncPipe } from '@angular/common';
52
import {
63
Component,
4+
Inject,
75
Input,
86
OnInit,
97
} from '@angular/core';
8+
import { TranslateService } from '@ngx-translate/core';
109
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
11-
import { Observable } from 'rxjs';
10+
import {
11+
interval,
12+
Observable,
13+
} from 'rxjs';
1214

15+
import {
16+
APP_CONFIG,
17+
AppConfig,
18+
} from '../../../../../config/app-config.interface';
1319
import { SearchService } from '../../../../core/shared/search/search.service';
1420
import { ViewMode } from '../../../../core/shared/view-mode.model';
21+
import { AlertComponent } from '../../../alert/alert.component';
22+
import { AlertType } from '../../../alert/alert-type';
1523
import { hasValue } from '../../../empty.util';
24+
import { VarDirective } from '../../../utils/var.directive';
1625

1726
@Component({
1827
selector: 'ds-search-results-skeleton',
1928
standalone: true,
2029
imports: [
2130
NgxSkeletonLoaderModule,
2231
AsyncPipe,
23-
NgForOf,
32+
AlertComponent,
33+
VarDirective,
2434
],
2535
templateUrl: './search-results-skeleton.component.html',
2636
styleUrl: './search-results-skeleton.component.scss',
@@ -44,6 +54,26 @@ export class SearchResultsSkeletonComponent implements OnInit {
4454
*/
4555
@Input()
4656
textLineCount = 2;
57+
/**
58+
* Whether to show fallback messages after a certain loading time
59+
*/
60+
@Input() showFallbackMessages: boolean;
61+
/**
62+
* The message text for a warning
63+
*/
64+
@Input() warningMessage: string;
65+
/**
66+
* The amount of time to wait for the warning message to be visible
67+
*/
68+
@Input() warningMessageDelay: number;
69+
/**
70+
* The message text for an error
71+
*/
72+
@Input() errorMessage: string;
73+
/**
74+
* The amount of time to wait for the error message to be visible
75+
*/
76+
@Input() errorMessageDelay: number;
4777
/**
4878
* The view mode of the search page
4979
*/
@@ -53,10 +83,26 @@ export class SearchResultsSkeletonComponent implements OnInit {
5383
*/
5484
public loadingResults: number[];
5585

86+
/**
87+
* Timer for fallback messages visualization
88+
*/
89+
delayTimer$: Observable<any>;
90+
91+
5692
protected readonly ViewMode = ViewMode;
5793

58-
constructor(private searchService: SearchService) {
94+
95+
readonly AlertTypeEnum = AlertType;
96+
97+
constructor(
98+
private searchService: SearchService,
99+
private translate: TranslateService,
100+
@Inject(APP_CONFIG) private appConfig: AppConfig,
101+
) {
59102
this.viewMode$ = this.searchService.getViewMode();
103+
this.showFallbackMessages = this.showFallbackMessages ?? this.appConfig.loader.showFallbackMessagesByDefault;
104+
this.warningMessageDelay = this.warningMessageDelay ?? this.appConfig.loader.warningMessageDelay;
105+
this.errorMessageDelay = this.errorMessageDelay ?? this.appConfig.loader.errorMessageDelay;
60106
}
61107

62108
ngOnInit() {
@@ -66,5 +112,16 @@ export class SearchResultsSkeletonComponent implements OnInit {
66112
// this is needed as the default value of show thumbnails is true but set in lower levels of the DOM.
67113
this.showThumbnails = true;
68114
}
115+
116+
if (this.showFallbackMessages) {
117+
this.setFallBackMessages();
118+
}
119+
}
120+
121+
setFallBackMessages(): void {
122+
this.warningMessage = this.warningMessage || this.translate.instant('loading.warning');
123+
this.errorMessage = this.errorMessage || this.translate.instant('loading.error');
124+
125+
this.delayTimer$ = interval(1);
69126
}
70127
}

src/app/shared/search/search-results/search-results.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ <h1 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.
4040

4141
<ds-search-results-skeleton
4242
*ngIf="isLoading()"
43+
[showFallbackMessages]="true"
4344
[showThumbnails]="showThumbnails"
4445
[numberOfResults]="searchConfig.pagination.pageSize"
4546
></ds-search-results-skeleton>

src/app/shared/search/search-results/search-results.component.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
1212
import { ActivatedRoute } from '@angular/router';
1313
import { TranslateModule } from '@ngx-translate/core';
1414

15+
import { APP_CONFIG } from '../../../../config/app-config.interface';
16+
import { environment } from '../../../../environments/environment';
1517
import { Community } from '../../../core/shared/community.model';
1618
import { SearchService } from '../../../core/shared/search/search.service';
1719
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
@@ -44,6 +46,7 @@ describe('SearchResultsComponent', () => {
4446
provide: SearchConfigurationService,
4547
useValue: new SearchConfigurationServiceStub(),
4648
},
49+
{ provide: APP_CONFIG, useValue: environment },
4750
],
4851
imports: [
4952
TranslateModule.forRoot(),

0 commit comments

Comments
 (0)