1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { fakeAsync, TestBed, tick } from '@angular/core/testing';
4 import _ from 'lodash';
5 import { ToastrService } from 'ngx-toastr';
7 import { configureTestBed } from '~/testing/unit-test-helper';
8 import { RbdService } from '../api/rbd.service';
9 import { NotificationType } from '../enum/notification-type.enum';
10 import { CdNotificationConfig } from '../models/cd-notification';
11 import { FinishedTask } from '../models/finished-task';
12 import { CdDatePipe } from '../pipes/cd-date.pipe';
13 import { NotificationService } from './notification.service';
14 import { TaskMessageService } from './task-message.service';
16 describe('NotificationService', () => {
17 let service: NotificationService;
18 const toastFakeService = {
28 { provide: ToastrService, useValue: toastFakeService },
29 { provide: CdDatePipe, useValue: { transform: (d: any) => d } },
32 imports: [HttpClientTestingModule]
36 service = TestBed.inject(NotificationService);
40 it('should be created', () => {
41 expect(service).toBeTruthy();
44 it('should read empty notification list', () => {
45 localStorage.setItem('cdNotifications', '[]');
46 expect(service['dataSource'].getValue()).toEqual([]);
49 it('should read old notifications', fakeAsync(() => {
52 '[{"type":2,"message":"foobar","timestamp":"2018-05-24T09:41:32.726Z"}]'
54 service = new NotificationService(null, null, null);
55 expect(service['dataSource'].getValue().length).toBe(1);
58 it('should cancel a notification', fakeAsync(() => {
59 const timeoutId = service.show(NotificationType.error, 'Simple test');
60 service.cancel(timeoutId);
62 expect(service['dataSource'].getValue().length).toBe(0);
65 describe('Saved notifications', () => {
66 const expectSavedNotificationToHave = (expected: object) => {
68 expect(service['dataSource'].getValue().length).toBe(1);
69 const notification = service['dataSource'].getValue()[0];
70 Object.keys(expected).forEach((key) => {
71 expect(notification[key]).toBe(expected[key]);
75 const addNotifications = (quantity: number) => {
76 for (let index = 0; index < quantity; index++) {
77 service.show(NotificationType.info, `${index}`);
83 spyOn(service, 'show').and.callThrough();
84 service.cancel((<any>service)['justShownTimeoutId']);
87 it('should create a success notification and save it', fakeAsync(() => {
88 service.show(new CdNotificationConfig(NotificationType.success, 'Simple test'));
89 expectSavedNotificationToHave({ type: NotificationType.success });
92 it('should create an error notification and save it', fakeAsync(() => {
93 service.show(NotificationType.error, 'Simple test');
94 expectSavedNotificationToHave({ type: NotificationType.error });
97 it('should create an info notification and save it', fakeAsync(() => {
98 service.show(new CdNotificationConfig(NotificationType.info, 'Simple test'));
99 expectSavedNotificationToHave({
100 type: NotificationType.info,
101 title: 'Simple test',
106 it('should never have more then 10 notifications', fakeAsync(() => {
107 addNotifications(15);
108 expect(service['dataSource'].getValue().length).toBe(10);
111 it('should show a success task notification, but not save it', fakeAsync(() => {
112 const task = _.assign(new FinishedTask(), {
116 service.notifyTask(task, true);
119 expect(service.show).toHaveBeenCalled();
120 const notifications = service['dataSource'].getValue();
121 expect(notifications.length).toBe(0);
124 it('should be able to stop notifyTask from notifying', fakeAsync(() => {
125 const task = _.assign(new FinishedTask(), {
128 const timeoutId = service.notifyTask(task, true);
129 service.cancel(timeoutId);
131 expect(service['dataSource'].getValue().length).toBe(0);
134 it('should show a error task notification', fakeAsync(() => {
135 const task = _.assign(
136 new FinishedTask('rbd/create', {
137 pool_name: 'somePool',
138 image_name: 'someImage'
147 service.notifyTask(task);
151 expect(service.show).toHaveBeenCalled();
152 const notifications = service['dataSource'].getValue();
153 expect(notifications.length).toBe(0);
156 it('combines different notifications with the same title', fakeAsync(() => {
157 service.show(NotificationType.error, '502 - Bad Gateway', 'Error occurred in path a');
159 service.show(NotificationType.error, '502 - Bad Gateway', 'Error occurred in path b');
160 expectSavedNotificationToHave({
161 type: NotificationType.error,
162 title: '502 - Bad Gateway',
163 message: '<ul><li>Error occurred in path a</li><li>Error occurred in path b</li></ul>'
167 it('should remove a single notification', fakeAsync(() => {
169 let messages = service['dataSource'].getValue().map((notification) => notification.title);
170 expect(messages).toEqual(['4', '3', '2', '1', '0']);
172 messages = service['dataSource'].getValue().map((notification) => notification.title);
173 expect(messages).toEqual(['4', '3', '1', '0']);
176 it('should remove all notifications', fakeAsync(() => {
178 expect(service['dataSource'].getValue().length).toBe(5);
180 expect(service['dataSource'].getValue().length).toBe(0);
184 describe('notification queue', () => {
185 const n1 = new CdNotificationConfig(NotificationType.success, 'Some success');
186 const n2 = new CdNotificationConfig(NotificationType.info, 'Some info');
188 const showArray = (arr: any[]) => arr.forEach((n) => service.show(n));
191 spyOn(service, 'save').and.stub();
194 it('filters out duplicated notifications on single call', fakeAsync(() => {
195 showArray([n1, n1, n2, n2]);
197 expect(service.save).toHaveBeenCalledTimes(2);
200 it('filters out duplicated notifications presented in different calls', fakeAsync(() => {
204 expect(service.save).toHaveBeenCalledTimes(2);
207 it('will reset the timeout on every call', fakeAsync(() => {
212 expect(service.save).toHaveBeenCalledTimes(0);
214 expect(service.save).toHaveBeenCalledTimes(2);
217 it('wont filter out duplicated notifications if timeout was reached before', fakeAsync(() => {
222 expect(service.save).toHaveBeenCalledTimes(4);
226 describe('showToasty', () => {
227 let toastr: ToastrService;
228 const time = '2022-02-22T00:00:00.000Z';
231 const baseTime = new Date(time);
232 spyOn(global, 'Date').and.returnValue(baseTime);
233 spyOn(window, 'setTimeout').and.callFake((fn) => fn());
235 toastr = TestBed.inject(ToastrService);
236 // spyOn needs to know the methods before spying and can't read the array for clarification
237 ['error', 'info', 'success'].forEach((method: 'error' | 'info' | 'success') =>
238 spyOn(toastr, method).and.stub()
242 it('should show with only title defined', () => {
243 service.show(NotificationType.info, 'Some info');
244 expect(toastr.info).toHaveBeenCalledWith(
245 `<small class="date">${time}</small>` +
246 '<i class="float-right custom-icon ceph-icon" title="Ceph"></i>',
252 it('should show with title and message defined', () => {
255 new CdNotificationConfig(NotificationType.error, 'Some error', 'Some operation failed')
257 expect(toastr.error).toHaveBeenCalledWith(
258 'Some operation failed<br>' +
259 `<small class="date">${time}</small>` +
260 '<i class="float-right custom-icon ceph-icon" title="Ceph"></i>',
266 it('should show with title, message and application defined', () => {
268 new CdNotificationConfig(
269 NotificationType.success,
271 'Some alert resolved',
276 expect(toastr.success).toHaveBeenCalledWith(
277 'Some alert resolved<br>' +
278 `<small class="date">${time}</small>` +
279 '<i class="float-right custom-icon prometheus-icon" title="Prometheus"></i>',