Skip to content

Commit

Permalink
Merge pull request #3463 from ita-social-projects/bugfix/#7777-habit-…
Browse files Browse the repository at this point in the history
…notifications

[Bugfix] #7777, #7791 habit notifications
  • Loading branch information
hnativlyubomyr authored Nov 25, 2024
2 parents ead2d57 + a1d3ecc commit 37cdaeb
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export class MatSnackBarComponent {
addFriend: { classname: SnackbarClassName.success, key: 'snack-bar.success.add-friend' },
friendValidation: { classname: SnackbarClassName.error, key: 'snack-bar.error.friend-request' },
friendInValidRequest: { classname: SnackbarClassName.error, key: 'snack-bar.error.friend-already-added' },
friendRequestAccepted: { classname: SnackbarClassName.success, key: 'snack-bar.success.friend-added-success' },
friendRequestDeclined: { classname: SnackbarClassName.success, key: 'snack-bar.success.friend-declined-success' },
habitAcceptRequest: { classname: SnackbarClassName.success, key: 'snack-bar.success.habit-added-success' },
habitDeclineRequest: { classname: SnackbarClassName.success, key: 'snack-bar.success.habit-decline-success' },
habitAcceptInValidRequest: { classname: SnackbarClassName.error, key: 'snack-bar.error.habit-not-added' },
habitDeclineInValidRequest: { classname: SnackbarClassName.error, key: 'snack-bar.error.habit-already-added' },
cancelRequest: { classname: SnackbarClassName.success, key: 'snack-bar.success.cancel-request' },
jointEventRequest: { classname: SnackbarClassName.success, key: 'snack-bar.success.joint-event-request' },
errorImageTypeSize: { classname: SnackbarClassName.error, key: 'user.photo-upload.error-img-type-and-size' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ <h1 class="heading h5">{{ 'homepage.notifications.title' | translate }}</h1>
(click)="navigate($event)"
(keydown)="navigate($event)"
></p>
<div class="friend-action-button" *ngIf="notification.notificationType === notificationFriendRequest">
<button (click)="declineRequest(notification.actionUserId[0])" class="decline-request">
<div class="friend-action-button" *ngIf="isHabitInvitation(notification) || isFriendRequest(notification)">
<button (click)="declineRequest(notification)" class="decline-request">
<span class="material-icons">close</span><span>{{ 'profile.friends.request-decline' | translate }}</span>
</button>
<button (click)="acceptRequest(notification.actionUserId[0])" class="accept-request">
<button (click)="acceptRequest(notification)" class="accept-request">
<span class="material-icons">done</span><span>{{ 'profile.friends.request-accept' | translate }}</span>
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { UserNotificationsComponent } from './user-notifications.component';
import { TranslateService, TranslateModule } from '@ngx-translate/core';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { of, BehaviorSubject, throwError } from 'rxjs';
import { of, BehaviorSubject } from 'rxjs';
import { LocalStorageService } from '@global-service/localstorage/local-storage.service';
import { Language } from 'src/app/main/i18n/Language';
import { PipeTransform, Pipe, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { MatSnackBarComponent } from '@global-errors/mat-snack-bar/mat-snack-bar.component';
import { FilterCriteria, NotificationFilter } from '@global-user/models/notification.model';
import { FilterCriteria } from '@global-user/models/notification.model';
import { Router } from '@angular/router';
import { UserNotificationService } from '@global-user/services/user-notification.service';

import { UserService } from '@global-service/user/user.service';
import { LocalizedDatePipe } from 'src/app/shared/localized-date-pipe/localized-date.pipe';
import { RelativeDatePipe } from 'src/app/shared/relative-date.pipe';
import { By } from '@angular/platform-browser';
import { UserFriendsService } from '@global-user/services/user-friends.service';
import { HabitService } from '@global-service/habit/habit.service';

@Pipe({ name: 'translate' })
class TranslatePipeMock implements PipeTransform {
Expand Down Expand Up @@ -50,7 +52,7 @@ describe('UserNotificationsComponent', () => {
bodyText: 'test texts1',
message: 'test message1',
notificationId: 2,
notificationType: '',
notificationType: 'HABIT_INVITE',
projectName: 'GreeCity',
secondMessage: 'secondMessageTest',
secondMessageId: 5,
Expand Down Expand Up @@ -79,6 +81,16 @@ describe('UserNotificationsComponent', () => {
localStorageServiceMock.languageSubject = of('en');
localStorageServiceMock.getUserId = () => 1;

const userFriendsServiceMock = {
acceptRequest: jasmine.createSpy('acceptRequest').and.returnValue(of(null)),
declineRequest: jasmine.createSpy('declineRequest').and.returnValue(of(null))
};

const habitServiceMock = {
acceptHabitInvitation: jasmine.createSpy('acceptHabitInvitation').and.returnValue(of(null)),
declineHabitInvitation: jasmine.createSpy('declineHabitInvitation').and.returnValue(of(null))
};

const routerMock = jasmine.createSpyObj('router', ['navigate']);

const userNotificationServiceMock = jasmine.createSpyObj('userNotificationService', [
Expand Down Expand Up @@ -113,7 +125,9 @@ describe('UserNotificationsComponent', () => {
{ provide: MatSnackBarComponent, useValue: { openSnackBar: () => {} } },
{ provide: Router, useValue: routerMock },
{ provide: UserNotificationService, useValue: userNotificationServiceMock },
{ provide: UserService, useValue: { userId: 1 } }
{ provide: UserService, useValue: { userId: 1 } },
{ provide: UserFriendsService, useValue: userFriendsServiceMock },
{ provide: HabitService, useValue: habitServiceMock }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]
}).compileComponents();
Expand All @@ -140,8 +154,7 @@ describe('UserNotificationsComponent', () => {
expect(component.filterCriteriaOptions.find((el) => el.name === FilterCriteria.ORIGIN).isSelected).toBeTrue();
});

it('should navigate to news page when notification type is ECONEWS', fakeAsync(() => {
const event = new MouseEvent('click');
it('should navigate to news page when notification type is ECONEWS', waitForAsync(() => {
const target = document.createElement('div');
target.setAttribute('data-notificationType', 'ECONEWS');
target.setAttribute('data-targetid', '5');
Expand All @@ -157,13 +170,11 @@ describe('UserNotificationsComponent', () => {
spyOn(component, 'navigate').and.callThrough();

component.navigate(customEvent);
tick();

expect(routerMock.navigate).toHaveBeenCalledWith(['news', 5]);
}));

it('should navigate to habit editing page when notification type is HABIT', fakeAsync(() => {
const event = new MouseEvent('click');
it('should navigate to habit editing page when notification type is HABIT', waitForAsync(() => {
const target = document.createElement('div');
target.setAttribute('data-notificationType', 'HABIT');
target.setAttribute('data-targetid', '3');
Expand All @@ -179,36 +190,69 @@ describe('UserNotificationsComponent', () => {
spyOn(component, 'navigate').and.callThrough();

component.navigate(customEvent);
tick();

expect(routerMock.navigate).toHaveBeenCalledWith(['profile', 1, 'allhabits', 'edithabit', 3]);
expect(routerMock.navigate).toHaveBeenCalledWith(['profile', 1, 'allhabits', 'addhabit', 3]);
}));

it('should return checkSelectedFilter', () => {
component.filterCriteriaOptions = filterCriteriaOptions;
expect(component.checkSelectedFilter(FilterCriteria.TYPE)).toBeFalsy();
});

it('should call declineRequest', fakeAsync(() => {
it('should call declineRequest', waitForAsync(() => {
component.notifications = notifications;
const spy = spyOn(component, 'declineRequest');
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('.decline-request'));
button.triggerEventHandler('click', null);
tick();
expect(spy).toHaveBeenCalled();
}));

it('should call accept request', fakeAsync(() => {
it('should call userFriendsService.declineRequest for a friend request', waitForAsync(() => {
component.notifications = [notifications[0]];
fixture.detectChanges();

component.declineRequest(notifications[0]);

expect(userFriendsServiceMock.declineRequest).toHaveBeenCalledWith(notifications[0].actionUserId[0]);
}));

it('should call habitService.declineHabitInvitation for a habit invitation', waitForAsync(() => {
component.notifications = [notifications[1]];
fixture.detectChanges();

component.declineRequest(notifications[1]);

expect(habitServiceMock.declineHabitInvitation).toHaveBeenCalledWith(notifications[1].secondMessageId);
}));

it('should call accept request', waitForAsync(() => {
component.notifications = notifications;
const spy = spyOn(component, 'acceptRequest');
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('.accept-request'));
button.triggerEventHandler('click', null);
tick();
expect(spy).toHaveBeenCalled();
}));

it('should call userFriendsService.acceptRequest for a friend request', waitForAsync(() => {
component.notifications = [notifications[0]];
fixture.detectChanges();

component.acceptRequest(notifications[0]);

expect(userFriendsServiceMock.acceptRequest).toHaveBeenCalledWith(notifications[0].actionUserId[0]);
}));

it('should call habitService.acceptHabitInvitation for a habit invitation', waitForAsync(() => {
component.notifications = [notifications[1]];
fixture.detectChanges();

component.acceptRequest(notifications[1]);

expect(habitServiceMock.acceptHabitInvitation).toHaveBeenCalledWith(notifications[1].secondMessageId);
}));

it('onScroll', () => {
const spy = spyOn(component, 'getNotification');
component.isLoading = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Subject } from 'rxjs';
import { debounceTime, take, takeUntil } from 'rxjs/operators';
import { NotificationBody, Notifications } from '@ubs/ubs-admin/models/ubs-user.model';
import { HttpParams } from '@angular/common/http';
import { HabitService } from '@global-service/habit/habit.service';

@Component({
selector: 'app-user-notifications',
Expand All @@ -32,8 +33,8 @@ export class UserNotificationsComponent implements OnInit, OnDestroy {
filterCriteriaOptions = filterCriteriaOptions;
notificationCriteriaOptions = notificationCriteriaOptions;
projects = projects;
notificationTypes = NotificationCriteria;
notificationFriendRequest = NotificationCriteria.FRIEND_REQUEST_RECEIVED;
notificationHabitInvitation = NotificationCriteria.HABIT_INVITATION;

notifications: NotificationModel[] = [];
currentPage = 0;
Expand All @@ -45,13 +46,14 @@ export class UserNotificationsComponent implements OnInit, OnDestroy {
private filterAll = 'All';

constructor(
private localStorageService: LocalStorageService,
public translate: TranslateService,
private userNotificationService: UserNotificationService,
private matSnackBar: MatSnackBarComponent,
private userFriendsService: UserFriendsService,
private router: Router,
private userService: UserService
private readonly localStorageService: LocalStorageService,
public readonly translate: TranslateService,
private readonly userNotificationService: UserNotificationService,
private readonly matSnackBar: MatSnackBarComponent,
private readonly userFriendsService: UserFriendsService,
private readonly router: Router,
private readonly userService: UserService,
private readonly habitService: HabitService
) {}

ngOnInit() {
Expand Down Expand Up @@ -114,7 +116,7 @@ export class UserNotificationsComponent implements OnInit, OnDestroy {
private getAllSelectedFilters(approach: string): NotificationFilter[] {
const filterArr = approach === this.filterCriteria.TYPE ? notificationCriteriaOptions : projects;
const allOption = filterArr.find((el) => el.name === this.filterAll);
return allOption.isSelected ? [] : [...filterArr.filter((el) => {return el.isSelected === true && el.name !== this.filterAll;})];
return allOption.isSelected ? [] : [...filterArr.filter((el) => { return el.isSelected === true && el.name !== this.filterAll; })];
}

getNotification(page: number): void {
Expand Down Expand Up @@ -260,47 +262,96 @@ export class UserNotificationsComponent implements OnInit, OnDestroy {
}
}

acceptRequest(userId: number): void {
isFriendRequest(notification: NotificationModel): boolean {
return notification.notificationType === this.notificationFriendRequest;
}

isHabitInvitation(notification: NotificationModel): boolean {
return notification.notificationType === this.notificationHabitInvitation;
}

acceptRequest(notification: NotificationModel): void {
if (this.isFriendRequest(notification)) {
this.handleFriendRequest(notification, 'accept');
} else if (this.isHabitInvitation(notification)) {
this.handleHabitInvitation(notification, 'accept');
}
}

declineRequest(notification: NotificationModel): void {
if (this.isFriendRequest(notification)) {
this.handleFriendRequest(notification, 'decline');
} else if (this.isHabitInvitation(notification)) {
this.handleHabitInvitation(notification, 'decline');
}
}

private handleFriendRequest(notification: NotificationModel, action: 'accept' | 'decline'): void {
let isAccepted = true;
this.userFriendsService.acceptRequest(userId).subscribe({
error: () => {
isAccepted = false;
},
complete: () => {
if (isAccepted) {
this.matSnackBar.openSnackBar('friendInValidRequest');

const userId = notification.actionUserId[0];
if (action === 'accept') {
this.userFriendsService.acceptRequest(userId).subscribe({
error: () => {
isAccepted = false;
},
complete: () => {
this.matSnackBar.openSnackBar(isAccepted ? 'friendRequestAccepted' : 'friendInValidRequest');
}
}
});
});
} else {
this.userFriendsService.declineRequest(userId).subscribe({
error: () => {
isAccepted = false;
},
complete: () => {
this.matSnackBar.openSnackBar(isAccepted ? 'friendRequestDeclined' : 'friendInValidRequest');
}
});
}
}

declineRequest(userId: number): void {
private handleHabitInvitation(notification: NotificationModel, action: 'accept' | 'decline'): void {
let isAccepted = true;
this.userFriendsService.declineRequest(userId).subscribe({
error: () => {
isAccepted = false;
},
complete: () => {
if (isAccepted) {
this.matSnackBar.openSnackBar('friendInValidRequest');
const invitationId = notification.secondMessageId;
if (action === 'accept') {
this.habitService.acceptHabitInvitation(invitationId).subscribe({
error: () => {
isAccepted = false;
},
complete: () => {
this.matSnackBar.openSnackBar(isAccepted ? 'habitAcceptRequest' : 'habitAcceptInValidRequest');
}
}
});
});
} else {
this.habitService.declineHabitInvitation(invitationId).subscribe({
error: () => {
isAccepted = false;
},
complete: () => {
this.matSnackBar.openSnackBar(isAccepted ? 'habitDeclineRequest' : 'habitDeclineInValidRequest');
}
});
}
}

navigate(event: Event): void {
const target = event.target as HTMLElement;
const userId = this.userService.userId;
const targetTextContent = target.textContent?.trim() || '';
const targetUserId = target.getAttribute('data-userid')?.toString();
const targetUserId = Number(target.getAttribute('data-userId'));
const notificationType = target.getAttribute('data-notificationType');
const targetId = Number(target.getAttribute('data-targetid'));
const targetId = Number(target.getAttribute('data-targetId'));

const isClickOrEnter = event instanceof MouseEvent || (event instanceof KeyboardEvent && event.key === 'Enter');
if (!isClickOrEnter) {
return;
}

if (targetUserId === userId) {
this.router.navigate(['profile', userId]);
return;
}
if (targetUserId) {
this.router.navigate(['profile', userId, 'users', targetTextContent, targetUserId]);
return;
Expand All @@ -310,7 +361,7 @@ export class UserNotificationsComponent implements OnInit, OnDestroy {
const routes = {
EVENT: ['events', targetId],
ECONEWS: ['news', targetId],
HABIT: ['profile', userId, 'allhabits', 'edithabit', targetId]
HABIT: ['profile', userId, 'allhabits', 'addhabit', targetId]
};

for (const type in routes) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NotificContentReplaceDirective } from './notific-content-replace.directive';

@Component({
Expand Down Expand Up @@ -30,7 +30,7 @@ describe('NotificContentReplaceDirective', () => {
viewed: false
};

beforeEach(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [TestComponent, NotificContentReplaceDirective]
});
Expand All @@ -39,7 +39,7 @@ describe('NotificContentReplaceDirective', () => {
fixture.detectChanges();
component = fixture.componentInstance;
paragrEl = fixture.nativeElement.querySelector('p');
});
}));

it('should display multiple user replacements', () => {
component.notification = { ...notification, ...{ bodyText: '{user1} and {user2} liked your post' } };
Expand Down
Loading

0 comments on commit 37cdaeb

Please sign in to comment.