diff --git a/src/app/shared/notifications/notification/notification.component.spec.ts b/src/app/shared/notifications/notification/notification.component.spec.ts index 7b7ee57d26..2bded57636 100644 --- a/src/app/shared/notifications/notification/notification.component.spec.ts +++ b/src/app/shared/notifications/notification/notification.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { BrowserModule, By } from '@angular/platform-browser'; import { ChangeDetectorRef, DebugElement } from '@angular/core'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -16,6 +16,7 @@ import { Notification } from '../models/notification.model'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; import { storeModuleConfig } from '../../../app.reducer'; +import { BehaviorSubject } from 'rxjs'; describe('NotificationComponent', () => { @@ -83,6 +84,8 @@ describe('NotificationComponent', () => { deContent = fixture.debugElement.query(By.css('.notification-content')); elContent = deContent.nativeElement; elType = fixture.debugElement.query(By.css('.notification-icon')).nativeElement; + + spyOn(comp, 'remove'); }); it('should create component', () => { @@ -124,4 +127,51 @@ describe('NotificationComponent', () => { expect(elContent.innerHTML).toEqual(htmlContent); }); + describe('dismiss countdown', () => { + const TIMEOUT = 5000; + let isPaused$: BehaviorSubject; + + beforeEach(() => { + isPaused$ = new BehaviorSubject(false); + comp.isPaused$ = isPaused$; + comp.notification = { + id: '1', + type: NotificationType.Info, + title: 'Notif. title', + content: 'test', + options: Object.assign( + new NotificationOptions(), + { timeout: TIMEOUT } + ), + html: true + }; + }); + + it('should remove notification after timeout', fakeAsync(() => { + comp.ngOnInit(); + tick(TIMEOUT); + expect(comp.remove).toHaveBeenCalled(); + })); + + describe('isPaused$', () => { + it('should pause countdown on true', fakeAsync(() => { + comp.ngOnInit(); + tick(TIMEOUT / 2); + isPaused$.next(true); + tick(TIMEOUT); + expect(comp.remove).not.toHaveBeenCalled(); + })); + + it('should resume paused countdown on false', fakeAsync(() => { + comp.ngOnInit(); + tick(TIMEOUT / 4); + isPaused$.next(true); + tick(TIMEOUT / 4); + isPaused$.next(false); + tick(TIMEOUT); + expect(comp.remove).toHaveBeenCalled(); + })); + }); + }); + }); diff --git a/src/app/shared/notifications/notification/notification.component.ts b/src/app/shared/notifications/notification/notification.component.ts index 0c64d3e263..5f00084761 100644 --- a/src/app/shared/notifications/notification/notification.component.ts +++ b/src/app/shared/notifications/notification/notification.component.ts @@ -1,4 +1,4 @@ -import {of as observableOf, Observable } from 'rxjs'; +import { Observable, of as observableOf } from 'rxjs'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -23,6 +23,7 @@ import { fadeInEnter, fadeInState, fadeOutLeave, fadeOutState } from '../../anim import { NotificationAnimationsStatus } from '../models/notification-animations-type'; import { isNotEmpty } from '../../empty.util'; import { INotification } from '../models/notification.model'; +import { filter, first } from 'rxjs/operators'; @Component({ selector: 'ds-notification', @@ -47,6 +48,11 @@ export class NotificationComponent implements OnInit, OnDestroy { @Input() public notification = null as INotification; + /** + * Whether this notification's countdown should be paused + */ + @Input() public isPaused$: Observable = observableOf(false); + // Progress bar variables public title: Observable; public content: Observable; @@ -99,17 +105,21 @@ export class NotificationComponent implements OnInit, OnDestroy { private instance = () => { this.diff = (new Date().getTime() - this.start) - (this.count * this.speed); - if (this.count++ === this.steps) { - this.remove(); - // this.item.timeoutEnd!.emit(); - } else if (!this.stopTime) { - if (this.showProgressBar) { - this.progressWidth += 100 / this.steps; - } + this.isPaused$.pipe( + filter(paused => !paused), + first(), + ).subscribe(() => { + if (this.count++ === this.steps) { + this.remove(); + } else if (!this.stopTime) { + if (this.showProgressBar) { + this.progressWidth += 100 / this.steps; + } - this.timer = setTimeout(this.instance, (this.speed - this.diff)); - } - this.zone.run(() => this.cdr.detectChanges()); + this.timer = setTimeout(this.instance, (this.speed - this.diff)); + } + this.zone.run(() => this.cdr.detectChanges()); + }); } public remove() { diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.html b/src/app/shared/notifications/notifications-board/notifications-board.component.html index 15f5044bc4..854842f30d 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.html +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.html @@ -1,7 +1,10 @@ -
+
+ [notification]="a" [isPaused$]="isPaused$">
diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts b/src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts index dad667cf3d..1d3faabdaa 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; -import { BrowserModule } from '@angular/platform-browser'; +import { BrowserModule, By } from '@angular/platform-browser'; import { ChangeDetectorRef } from '@angular/core'; import { NotificationsService } from '../notifications.service'; @@ -14,6 +14,9 @@ import { NotificationType } from '../models/notification-type'; import { uniqueId } from 'lodash'; import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces'; import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; +import { cold } from 'jasmine-marbles'; + +export const bools = { f: false, t: true }; describe('NotificationsBoardComponent', () => { let comp: NotificationsBoardComponent; @@ -67,6 +70,40 @@ describe('NotificationsBoardComponent', () => { it('should have two notifications', () => { expect(comp.notifications.length).toBe(2); + expect(fixture.debugElement.queryAll(By.css('ds-notification')).length).toBe(2); + }); + + describe('notification countdown', () => { + let wrapper; + + beforeEach(() => { + wrapper = fixture.debugElement.query(By.css('div.notifications-wrapper')); + }); + + it('should not be paused by default', () => { + expect(comp.isPaused$).toBeObservable(cold('f', bools)); + }); + + it('should pause on mouseenter', () => { + wrapper.triggerEventHandler('mouseenter'); + + expect(comp.isPaused$).toBeObservable(cold('t', bools)); + }); + + it('should resume on mouseleave', () => { + wrapper.triggerEventHandler('mouseenter'); + wrapper.triggerEventHandler('mouseleave'); + + expect(comp.isPaused$).toBeObservable(cold('f', bools)); + }); + + it('should be passed to all notifications', () => { + fixture.debugElement.queryAll(By.css('ds-notification')) + .map(node => node.componentInstance) + .forEach(notification => { + expect(notification.isPaused$).toEqual(comp.isPaused$); + }); + }); }); }) diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.ts b/src/app/shared/notifications/notifications-board/notifications-board.component.ts index 829cfadf0f..f153d1009e 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.ts +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.ts @@ -9,7 +9,7 @@ import { } from '@angular/core'; import { select, Store } from '@ngrx/store'; -import { Subscription } from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; import { difference } from 'lodash'; import { NotificationsService } from '../notifications.service'; @@ -44,6 +44,11 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { public rtl = false; public animate: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' = 'fromRight'; + /** + * Whether to pause the dismiss countdown of all notifications on the board + */ + public isPaused$: BehaviorSubject = new BehaviorSubject(false); + constructor(private service: NotificationsService, private store: Store, private cdr: ChangeDetectorRef) { @@ -129,7 +134,6 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { } }); } - ngOnDestroy(): void { if (this.sub) { this.sub.unsubscribe();