From b1928a75b36bfec978eeacdbfc07864daab55b93 Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Thu, 12 Dec 2024 12:17:02 +0100 Subject: [PATCH 1/3] Add shortcut to private messages --- .../conversation/OneToOneChatResource.java | 56 ++++++++++++- .../answer-post/answer-post.component.html | 3 +- .../conversations/one-to-one-chat.service.ts | 6 ++ .../metis/metis-conversation.service.ts | 2 + .../app/shared/metis/post/post.component.html | 1 + .../app/shared/metis/post/post.component.ts | 30 +------ .../answer-post-header.component.html | 2 +- .../post-header/post-header.component.html | 2 +- .../posting-header.directive.ts | 12 ++- .../app/shared/metis/posting.directive.ts | 55 ++++++++++++ .../OneToOneChatIntegrationTest.java | 81 ++++++++++++++---- .../metis-conversation.service.spec.ts | 26 ++++++ .../services/one-to-one-chat.service.spec.ts | 84 ++++++++++++------- .../spec/directive/posting.directive.spec.ts | 17 +++- 14 files changed, 290 insertions(+), 87 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/web/conversation/OneToOneChatResource.java b/src/main/java/de/tum/cit/aet/artemis/communication/web/conversation/OneToOneChatResource.java index a5fe0e2c7356..b7825b920da7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/web/conversation/OneToOneChatResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/web/conversation/OneToOneChatResource.java @@ -25,6 +25,8 @@ import de.tum.cit.aet.artemis.communication.service.conversation.OneToOneChatService; import de.tum.cit.aet.artemis.communication.service.conversation.auth.OneToOneChatAuthorizationService; import de.tum.cit.aet.artemis.communication.service.notifications.SingleUserNotificationService; +import de.tum.cit.aet.artemis.core.domain.Course; +import de.tum.cit.aet.artemis.core.domain.User; import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; import de.tum.cit.aet.artemis.core.repository.CourseRepository; import de.tum.cit.aet.artemis.core.repository.UserRepository; @@ -66,7 +68,8 @@ public OneToOneChatResource(SingleUserNotificationService singleUserNotification * * @param courseId the id of the course * @param otherChatParticipantLogins logins of other participants (must be 1 for one to one chat) excluding the requesting user - * @return ResponseEntity with status 201 (Created) and with body containing the created one to one chat + * + * @return ResponseEntity according to createOneToOneChat function */ @PostMapping("{courseId}/one-to-one-chats") @EnforceAtLeastStudent @@ -74,8 +77,8 @@ public ResponseEntity startOneToOneChat(@PathVariable Long cour var requestingUser = userRepository.getUserWithGroupsAndAuthorities(); log.debug("REST request to create one to one chat in course {} between : {} and : {}", courseId, requestingUser.getLogin(), otherChatParticipantLogins); var course = courseRepository.findByIdElseThrow(courseId); - checkMessagingEnabledElseThrow(course); - oneToOneChatAuthorizationService.isAllowedToCreateOneToOneChat(course, requestingUser); + + validateInputElseThrow(requestingUser, course); var loginsToSearchFor = new HashSet<>(otherChatParticipantLogins); loginsToSearchFor.add(requestingUser.getLogin()); @@ -88,8 +91,53 @@ public ResponseEntity startOneToOneChat(@PathVariable Long cour var userA = chatMembers.getFirst(); var userB = chatMembers.get(1); - var oneToOneChat = oneToOneChatService.startOneToOneChat(course, userA, userB); var userToBeNotified = userA.getLogin().equals(requestingUser.getLogin()) ? userB : userA; + return createOneToOneChat(requestingUser, userToBeNotified, course); + } + + /** + * POST /api/courses/:courseId/one-to-one-chats/:userId: Starts a new one to one chat in a course + * + * @param courseId the id of the course + * @param userId the id of the participating user + * + * @return ResponseEntity according to createOneToOneChat function + */ + @PostMapping("{courseId}/one-to-one-chats/{userId}") + @EnforceAtLeastStudent + public ResponseEntity startOneToOneChat(@PathVariable Long courseId, @PathVariable Long userId) throws URISyntaxException { + var requestingUser = userRepository.getUserWithGroupsAndAuthorities(); + var otherUser = userRepository.findByIdElseThrow(userId); + log.debug("REST request to create one to one chat by id in course {} between : {} and : {}", courseId, requestingUser.getLogin(), otherUser.getLogin()); + var course = courseRepository.findByIdElseThrow(courseId); + + validateInputElseThrow(requestingUser, course); + + return createOneToOneChat(requestingUser, otherUser, course); + } + + /** + * Function to validate incoming request data + * + * @param requestingUser user that wants to create the one to one chat + * @param course course to create the one to one chat + */ + private void validateInputElseThrow(User requestingUser, Course course) { + checkMessagingEnabledElseThrow(course); + oneToOneChatAuthorizationService.isAllowedToCreateOneToOneChat(course, requestingUser); + } + + /** + * Function to create a one to one chat and return the corresponding response to the client + * + * @param requestingUser user that wants to create the one to one chat + * @param userToBeNotified user that is invited into the one to one chat + * @param course course to create the one to one chat + * + * @return ResponseEntity with status 201 (Created) and with body containing the created one to one chat + */ + private ResponseEntity createOneToOneChat(User requestingUser, User userToBeNotified, Course course) throws URISyntaxException { + var oneToOneChat = oneToOneChatService.startOneToOneChat(course, requestingUser, userToBeNotified); singleUserNotificationService.notifyClientAboutConversationCreationOrDeletion(oneToOneChat, userToBeNotified, requestingUser, NotificationType.CONVERSATION_CREATE_ONE_TO_ONE_CHAT); // also send notification to the author in order for the author to subscribe to the new chat (this notification won't be saved and shown to author) diff --git a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html index 05258758872f..537e0a527b01 100644 --- a/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html +++ b/src/main/webapp/app/shared/metis/answer-post/answer-post.component.html @@ -24,6 +24,7 @@ [isCommunicationPage]="isCommunicationPage" [lastReadDate]="lastReadDate" [isDeleted]="isDeleted" + (onUserNameClicked)="onUserNameClicked()" /> } @@ -39,7 +40,7 @@ [isDeleted]="isDeleted" [deleteTimerInSeconds]="deleteTimerInSeconds" (onUndoDeleteEvent)="onDeleteEvent(false)" - (userReferenceClicked)="userReferenceClicked.emit($event)" + (userReferenceClicked)="onUserReferenceClicked($event)" (channelReferenceClicked)="channelReferenceClicked.emit($event)" />
diff --git a/src/main/webapp/app/shared/metis/conversations/one-to-one-chat.service.ts b/src/main/webapp/app/shared/metis/conversations/one-to-one-chat.service.ts index 92ed92e33bf2..03b2a5b3dee3 100644 --- a/src/main/webapp/app/shared/metis/conversations/one-to-one-chat.service.ts +++ b/src/main/webapp/app/shared/metis/conversations/one-to-one-chat.service.ts @@ -19,4 +19,10 @@ export class OneToOneChatService { .post(`${this.resourceUrl}${courseId}/one-to-one-chats`, [loginOfChatPartner], { observe: 'response' }) .pipe(map(this.conversationService.convertDateFromServer)); } + + createWithId(courseId: number, userIdOfChatPartner: number) { + return this.http + .post(`${this.resourceUrl}${courseId}/one-to-one-chats/${userIdOfChatPartner}`, null, { observe: 'response' }) + .pipe(map(this.conversationService.convertDateFromServer)); + } } diff --git a/src/main/webapp/app/shared/metis/metis-conversation.service.ts b/src/main/webapp/app/shared/metis/metis-conversation.service.ts index b21b4074294d..216d4d49afdb 100644 --- a/src/main/webapp/app/shared/metis/metis-conversation.service.ts +++ b/src/main/webapp/app/shared/metis/metis-conversation.service.ts @@ -210,6 +210,8 @@ export class MetisConversationService implements OnDestroy { public createOneToOneChat = (loginOfChatPartner: string): Observable> => this.onConversationCreation(this.oneToOneChatService.create(this._courseId, loginOfChatPartner)); + public createOneToOneChatWithId = (userId: number): Observable> => + this.onConversationCreation(this.oneToOneChatService.createWithId(this._courseId, userId)); public createChannel = (channel: ChannelDTO) => this.onConversationCreation(this.channelService.create(this._courseId, channel)); public createGroupChat = (loginsOfChatPartners: string[]) => this.onConversationCreation(this.groupChatService.create(this._courseId, loginsOfChatPartners)); private onConversationCreation = (creation$: Observable>): Observable => { diff --git a/src/main/webapp/app/shared/metis/post/post.component.html b/src/main/webapp/app/shared/metis/post/post.component.html index a9620cd7e925..ddc77434d323 100644 --- a/src/main/webapp/app/shared/metis/post/post.component.html +++ b/src/main/webapp/app/shared/metis/post/post.component.html @@ -24,6 +24,7 @@ [isDeleted]="isDeleted" [isCommunicationPage]="isCommunicationPage" (isModalOpen)="displayInlineInput = true" + (onUserNameClicked)="onUserNameClicked()" [lastReadDate]="lastReadDate" />
diff --git a/src/main/webapp/app/shared/metis/post/post.component.ts b/src/main/webapp/app/shared/metis/post/post.component.ts index feeb30d3ed0d..39e1b04e0fa0 100644 --- a/src/main/webapp/app/shared/metis/post/post.component.ts +++ b/src/main/webapp/app/shared/metis/post/post.component.ts @@ -23,10 +23,7 @@ import { ContextInformation, DisplayPriority, PageType, RouteComponents } from ' import { faBookmark, faBullhorn, faCheckSquare, faComments, faPencilAlt, faSmile, faThumbtack, faTrash } from '@fortawesome/free-solid-svg-icons'; import dayjs from 'dayjs/esm'; import { PostFooterComponent } from 'app/shared/metis/posting-footer/post-footer/post-footer.component'; -import { OneToOneChatService } from 'app/shared/metis/conversations/one-to-one-chat.service'; -import { isCommunicationEnabled, isMessagingEnabled } from 'app/entities/course.model'; -import { Router } from '@angular/router'; -import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; +import { isCommunicationEnabled } from 'app/entities/course.model'; import { getAsChannelDTO } from 'app/entities/metis/conversation/channel.model'; import { AnswerPost } from 'app/entities/metis/answer-post.model'; import { AnswerPostCreateEditModalComponent } from 'app/shared/metis/posting-create-edit-modal/answer-post-create-edit-modal/answer-post-create-edit-modal.component'; @@ -98,9 +95,6 @@ export class PostComponent extends PostingDirective implements OnInit, OnC constructor( public metisService: MetisService, public changeDetector: ChangeDetectorRef, - private oneToOneChatService: OneToOneChatService, - private metisConversationService: MetisConversationService, - private router: Router, public renderer: Renderer2, @Inject(DOCUMENT) private document: Document, ) { @@ -255,28 +249,6 @@ export class PostComponent extends PostingDirective implements OnInit, OnC ); } - /** - * Create a or navigate to one-to-one chat with the referenced user - * - * @param referencedUserLogin login of the referenced user - */ - onUserReferenceClicked(referencedUserLogin: string) { - const course = this.metisService.getCourse(); - if (isMessagingEnabled(course)) { - if (this.isCommunicationPage) { - this.metisConversationService.createOneToOneChat(referencedUserLogin).subscribe(); - } else { - this.oneToOneChatService.create(course.id!, referencedUserLogin).subscribe((res) => { - this.router.navigate(['courses', course.id, 'communication'], { - queryParams: { - conversationId: res.body!.id, - }, - }); - }); - } - } - } - /** * Navigate to the referenced channel * diff --git a/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html b/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html index f7d42ac92f61..cc197006ce3a 100644 --- a/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html +++ b/src/main/webapp/app/shared/metis/posting-header/answer-post-header/answer-post-header.component.html @@ -15,7 +15,7 @@ > - {{ posting.author?.name }} + {{ posting.author?.name }} diff --git a/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.html b/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.html index deadcb377cdd..ac20f91f82a2 100644 --- a/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.html +++ b/src/main/webapp/app/shared/metis/posting-header/post-header/post-header.component.html @@ -15,7 +15,7 @@ > - {{ posting.author?.name }} + {{ posting.author?.name }} diff --git a/src/main/webapp/app/shared/metis/posting-header/posting-header.directive.ts b/src/main/webapp/app/shared/metis/posting-header/posting-header.directive.ts index 8868f60124c1..0325610f669a 100644 --- a/src/main/webapp/app/shared/metis/posting-header/posting-header.directive.ts +++ b/src/main/webapp/app/shared/metis/posting-header/posting-header.directive.ts @@ -1,5 +1,5 @@ import { Posting } from 'app/entities/metis/posting.model'; -import { Directive, EventEmitter, Input, OnInit, Output, inject, input } from '@angular/core'; +import { Directive, EventEmitter, Input, OnInit, Output, inject, input, output } from '@angular/core'; import dayjs from 'dayjs/esm'; import { MetisService } from 'app/shared/metis/metis.service'; import { UserRole } from 'app/shared/metis/metis.util'; @@ -19,6 +19,8 @@ export abstract class PostingHeaderDirective implements OnIni isDeleted = input(false); + readonly onUserNameClicked = output(); + isAtLeastTutorInCourse: boolean; isAuthorOfPosting: boolean; postingIsOfToday: boolean; @@ -99,4 +101,12 @@ export abstract class PostingHeaderDirective implements OnIni this.userAuthorityTooltip = 'artemisApp.metis.userAuthorityTooltips.deleted'; } } + + protected userNameClicked() { + if (this.isAuthorOfPosting || !this.posting.authorRole) { + return; + } + + this.onUserNameClicked.emit(); + } } diff --git a/src/main/webapp/app/shared/metis/posting.directive.ts b/src/main/webapp/app/shared/metis/posting.directive.ts index f62af4907e18..5e7f0d062b3b 100644 --- a/src/main/webapp/app/shared/metis/posting.directive.ts +++ b/src/main/webapp/app/shared/metis/posting.directive.ts @@ -4,6 +4,10 @@ import { MetisService } from 'app/shared/metis/metis.service'; import { DisplayPriority } from 'app/shared/metis/metis.util'; import { faBookmark } from '@fortawesome/free-solid-svg-icons'; import { faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons'; +import { isMessagingEnabled } from 'app/entities/course.model'; +import { OneToOneChatService } from 'app/shared/metis/conversations/one-to-one-chat.service'; +import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; +import { Router } from '@angular/router'; @Directive() export abstract class PostingDirective implements OnInit, OnDestroy { @@ -28,8 +32,11 @@ export abstract class PostingDirective implements OnInit, OnD content?: string; + protected oneToOneChatService = inject(OneToOneChatService); + protected metisConversationService = inject(MetisConversationService); protected metisService = inject(MetisService); protected changeDetector = inject(ChangeDetectorRef); + protected router = inject(Router); // Icons farBookmark = farBookmark; @@ -131,4 +138,52 @@ export abstract class PostingDirective implements OnInit, OnD this.posting.isSaved = true; } } + + /** + * Create a or navigate to one-to-one chat with the referenced user + * + * @param referencedUserLogin login of the referenced user + */ + onUserReferenceClicked(referencedUserLogin: string) { + const course = this.metisService.getCourse(); + if (isMessagingEnabled(course)) { + if (this.isCommunicationPage) { + this.metisConversationService.createOneToOneChat(referencedUserLogin).subscribe(); + } else { + this.oneToOneChatService.create(course.id!, referencedUserLogin).subscribe((res) => { + this.router.navigate(['courses', course.id, 'communication'], { + queryParams: { + conversationId: res.body!.id, + }, + }); + }); + } + } + } + + /** + * Create a or navigate to one-to-one chat with the referenced user + */ + onUserNameClicked() { + if (!this.posting.author?.id) { + return; + } + + const referencedUserId = this.posting.author?.id; + + const course = this.metisService.getCourse(); + if (isMessagingEnabled(course)) { + if (this.isCommunicationPage) { + this.metisConversationService.createOneToOneChatWithId(referencedUserId).subscribe(); + } else { + this.oneToOneChatService.createWithId(course.id!, referencedUserId).subscribe((res) => { + this.router.navigate(['courses', course.id, 'communication'], { + queryParams: { + conversationId: res.body!.id, + }, + }); + }); + } + } + } } diff --git a/src/test/java/de/tum/cit/aet/artemis/communication/OneToOneChatIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/communication/OneToOneChatIntegrationTest.java index 5c8c90be7165..e6caa14684a6 100644 --- a/src/test/java/de/tum/cit/aet/artemis/communication/OneToOneChatIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/communication/OneToOneChatIntegrationTest.java @@ -49,7 +49,7 @@ String getTestPrefix() { @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void startOneToOneChat_asStudent1_shouldCreateMultipleOneToOneChats() throws Exception { + void shouldCreateMultipleOneToOneChatsWhenDifferentLoginsAreProvided() throws Exception { // when var chat1 = request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats", List.of(testPrefix + "student2"), OneToOneChatDTO.class, HttpStatus.CREATED); @@ -67,7 +67,7 @@ void startOneToOneChat_asStudent1_shouldCreateMultipleOneToOneChats() throws Exc @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void startOneToOneChat_asStudent2WithStudent1_shouldUseExistingOneToOneChat() throws Exception { + void shouldUseExistingOneToOneChatWhenChatAlreadyExists() throws Exception { var chat = request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats", List.of(testPrefix + "student2"), OneToOneChatDTO.class, HttpStatus.CREATED); @@ -88,13 +88,9 @@ void startOneToOneChat_asStudent2WithStudent1_shouldUseExistingOneToOneChat() th @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void startOneToOneChat_invalidNumberOfChatPartners_shouldReturnBadRequest() throws Exception { - // chat with too many users - // then + void shouldReturnBadRequestWhenSupplyingInsufficientAmountOfLogins() throws Exception { request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats", List.of(testPrefix + "student2", testPrefix + "student3"), OneToOneChatDTO.class, HttpStatus.BAD_REQUEST); - // chat with too few users - // then request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats", List.of(), OneToOneChatDTO.class, HttpStatus.BAD_REQUEST); verifyNoParticipantTopicWebsocketSent(); @@ -103,9 +99,8 @@ void startOneToOneChat_invalidNumberOfChatPartners_shouldReturnBadRequest() thro @ParameterizedTest @EnumSource(value = CourseInformationSharingConfiguration.class, names = { "COMMUNICATION_ONLY", "DISABLED" }) @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void startOneToOneChat_messagingFeatureDeactivated_shouldReturnForbidden(CourseInformationSharingConfiguration courseInformationSharingConfiguration) throws Exception { + void shouldReturnForbiddenWhenMessagingIsDisabled(CourseInformationSharingConfiguration courseInformationSharingConfiguration) throws Exception { startOneToOneChat_messagingDeactivated(courseInformationSharingConfiguration); - } void startOneToOneChat_messagingDeactivated(CourseInformationSharingConfiguration courseInformationSharingConfiguration) throws Exception { @@ -119,33 +114,28 @@ void startOneToOneChat_messagingDeactivated(CourseInformationSharingConfiguratio @Test @WithMockUser(username = TEST_PREFIX + "student42", roles = "USER") - void startOneToOneChat_notAllowedAsNotStudentInCourse_shouldReturnBadRequest() throws Exception { - // then + void shouldReturnBadRequestWhenStudentIsNotAllowedInCourse() throws Exception { request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats", List.of(testPrefix + "student2"), OneToOneChatDTO.class, HttpStatus.FORBIDDEN); } @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void startOneToOneChat_chatAlreadyExists_shouldReturnExistingChat() throws Exception { - // when + void shouldReturnExistingChatWhenChatAlreadyExists() throws Exception { var chat = request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats", List.of(testPrefix + "student2"), OneToOneChatDTO.class, HttpStatus.CREATED); var chat2 = request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats", List.of(testPrefix + "student2"), OneToOneChatDTO.class, HttpStatus.CREATED); - // then assertThat(chat).isNotNull(); assertThat(chat2).isNotNull(); assertThat(chat.getId()).isEqualTo(chat2.getId()); assertParticipants(chat.getId(), 2, "student1", "student2"); - // members of the created one to one chat are only notified in case the first message within the conversation is created verifyNoParticipantTopicWebsocketSent(); - } @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void postInOneToOneChat_firstPost_chatPartnerShouldBeNotifiedAboutNewConversation() throws Exception { + void shouldNotifyChatPartnerAboutNewConversationWhenChatIsStarted() throws Exception { // when var chat = request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats", List.of(testPrefix + "student2"), OneToOneChatDTO.class, HttpStatus.CREATED); @@ -157,4 +147,61 @@ void postInOneToOneChat_firstPost_chatPartnerShouldBeNotifiedAboutNewConversatio verifyNoParticipantTopicWebsocketSentExceptAction(MetisCrudAction.CREATE, MetisCrudAction.NEW_MESSAGE); } + + // PR + + @Test + @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") + void shouldCreateOneToOneChatWhenRequestedWithUserId() throws Exception { + Long student2Id = userRepository.findOneByLogin(testPrefix + "student2").orElseThrow().getId(); + + var chat = request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats/" + student2Id, null, OneToOneChatDTO.class, HttpStatus.CREATED); + + assertThat(chat).isNotNull(); + assertParticipants(chat.getId(), 2, "student1", "student2"); + verifyNoParticipantTopicWebsocketSent(); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") + void shouldReturnExistingChatWhenRequestedWithUserIdAndChatExists() throws Exception { + Long student2Id = userRepository.findOneByLogin(testPrefix + "student2").orElseThrow().getId(); + + var chat1 = request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats/" + student2Id, null, OneToOneChatDTO.class, HttpStatus.CREATED); + + var chat2 = request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats/" + student2Id, null, OneToOneChatDTO.class, HttpStatus.CREATED); + + assertThat(chat1).isNotNull(); + assertThat(chat2).isNotNull(); + assertThat(chat1.getId()).isEqualTo(chat2.getId()); + assertParticipants(chat1.getId(), 2, "student1", "student2"); + verifyNoParticipantTopicWebsocketSent(); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") + void shouldReturnNotFoundWhenUnknownUserIdIsPassed() throws Exception { + request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats/99999", null, OneToOneChatDTO.class, HttpStatus.NOT_FOUND); + } + + @ParameterizedTest + @EnumSource(value = CourseInformationSharingConfiguration.class, names = { "COMMUNICATION_ONLY", "DISABLED" }) + @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") + void shouldReturnForbiddenWhenMessagingIsDisabledAndUserIdIsSupplied(CourseInformationSharingConfiguration courseInformationSharingConfiguration) throws Exception { + Long student2Id = userRepository.findOneByLogin(testPrefix + "student2").orElseThrow().getId(); + + setCourseInformationSharingConfiguration(courseInformationSharingConfiguration); + + request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats/" + student2Id, null, OneToOneChatDTO.class, HttpStatus.FORBIDDEN); + + setCourseInformationSharingConfiguration(CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING); + } + + @Test + @WithMockUser(username = TEST_PREFIX + "student42", roles = "USER") + void shouldReturnForbiddenWhenStudentIsNotInCourse() throws Exception { + Long student2Id = userRepository.findOneByLogin(testPrefix + "student2").orElseThrow().getId(); + + request.postWithResponseBody("/api/courses/" + exampleCourseId + "/one-to-one-chats/" + student2Id, null, OneToOneChatDTO.class, HttpStatus.FORBIDDEN); + } } diff --git a/src/test/javascript/spec/component/overview/course-conversations/services/metis-conversation.service.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/services/metis-conversation.service.spec.ts index 489aa6a853d2..b397fe2951d3 100644 --- a/src/test/javascript/spec/component/overview/course-conversations/services/metis-conversation.service.spec.ts +++ b/src/test/javascript/spec/component/overview/course-conversations/services/metis-conversation.service.spec.ts @@ -269,6 +269,32 @@ describe('MetisConversationService', () => { }); }); + it('should set active conversation to newly created one to one chat when calling with id', () => { + return new Promise((done) => { + metisConversationService.setUpConversationService(course).subscribe({ + complete: () => { + const newOneToOneChat = generateOneToOneChatDTO({ id: 99 }); + const createOneToOneChatSpy = jest.spyOn(oneToOneChatService, 'createWithId').mockReturnValue(of(new HttpResponse({ body: newOneToOneChat }))); + const getConversationSpy = jest + .spyOn(conversationService, 'getConversationsOfUser') + .mockReturnValue(of(new HttpResponse({ body: [groupChat, oneToOneChat, channel, newOneToOneChat] }))); + createOneToOneChatSpy.mockClear(); + metisConversationService.createOneToOneChatWithId(1).subscribe({ + complete: () => { + expect(createOneToOneChatSpy).toHaveBeenCalledOnce(); + expect(createOneToOneChatSpy).toHaveBeenCalledWith(course.id, 1); + metisConversationService.activeConversation$.subscribe((activeConversation) => { + expect(activeConversation).toBe(newOneToOneChat); + expect(getConversationSpy).toHaveBeenCalledTimes(2); + done({}); + }); + }, + }); + }, + }); + }); + }); + it('should add new conversation to conversations of user on conversation create received', () => { return new Promise((done) => { metisConversationService.setUpConversationService(course).subscribe({ diff --git a/src/test/javascript/spec/component/overview/course-conversations/services/one-to-one-chat.service.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/services/one-to-one-chat.service.spec.ts index 6514ba2c0458..c0e3d42d4291 100644 --- a/src/test/javascript/spec/component/overview/course-conversations/services/one-to-one-chat.service.spec.ts +++ b/src/test/javascript/spec/component/overview/course-conversations/services/one-to-one-chat.service.spec.ts @@ -1,53 +1,73 @@ import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; -import { TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { take } from 'rxjs/operators'; -import { generateOneToOneChatDTO } from '../helpers/conversationExampleModels'; -import { TranslateService } from '@ngx-translate/core'; -import { MockAccountService } from '../../../../helpers/mocks/service/mock-account.service'; -import { MockTranslateService } from '../../../../helpers/mocks/service/mock-translate.service'; -import { AccountService } from 'app/core/auth/account.service'; +import { TestBed } from '@angular/core/testing'; import { OneToOneChatService } from 'app/shared/metis/conversations/one-to-one-chat.service'; import { OneToOneChatDTO } from 'app/entities/metis/conversation/one-to-one-chat.model'; -import { NotificationService } from 'app/shared/notification/notification.service'; -import { MockNotificationService } from '../../../../helpers/mocks/service/mock-notification.service'; +import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; import { provideHttpClient } from '@angular/common/http'; +import dayjs from 'dayjs/esm'; describe('OneToOneChatService', () => { let service: OneToOneChatService; let httpMock: HttpTestingController; - let elemDefault: OneToOneChatDTO; + let conversationServiceMock: jest.Mocked; beforeEach(() => { + conversationServiceMock = { + convertDateFromServer: jest.fn((response) => response), + } as any; + TestBed.configureTestingModule({ - imports: [], - providers: [ - provideHttpClient(), - provideHttpClientTesting(), - { provide: TranslateService, useClass: MockTranslateService }, - { provide: AccountService, useClass: MockAccountService }, - { provide: NotificationService, useClass: MockNotificationService }, - ], + providers: [OneToOneChatService, provideHttpClient(), provideHttpClientTesting(), { provide: ConversationService, useValue: conversationServiceMock }], }); + service = TestBed.inject(OneToOneChatService); httpMock = TestBed.inject(HttpTestingController); - - elemDefault = generateOneToOneChatDTO({}); }); afterEach(() => { httpMock.verify(); }); - it('create', fakeAsync(() => { - const returnedFromService = { ...elemDefault, id: 0 }; - const expected = { ...returnedFromService }; - service - .create(1, 'login') - .pipe(take(1)) - .subscribe((resp) => expect(resp).toMatchObject({ body: expected })); - - const req = httpMock.expectOne({ method: 'POST' }); - req.flush(returnedFromService); - tick(); - })); + describe('create method', () => { + it('should create a one-to-one chat with a login', () => { + const courseId = 1; + const loginOfChatPartner = 'testuser'; + const mockResponse: OneToOneChatDTO = { + id: 1, + creationDate: dayjs(), + }; + + service.create(courseId, loginOfChatPartner).subscribe((response) => { + expect(response.body).toEqual(mockResponse); + }); + + const req = httpMock.expectOne(`/api/courses/${courseId}/one-to-one-chats`); + expect(req.request.method).toBe('POST'); + expect(req.request.body).toEqual([loginOfChatPartner]); + req.flush(mockResponse); + expect(conversationServiceMock.convertDateFromServer).toHaveBeenCalled(); + }); + }); + + describe('createWithId method', () => { + it('should create a one-to-one chat with a user ID', () => { + const courseId = 1; + const userIdOfChatPartner = 42; + const mockResponse: OneToOneChatDTO = { + id: 1, + creationDate: dayjs(), + }; + + service.createWithId(courseId, userIdOfChatPartner).subscribe((response) => { + expect(response.body).toEqual(mockResponse); + }); + + const req = httpMock.expectOne(`/api/courses/${courseId}/one-to-one-chats/${userIdOfChatPartner}`); + expect(req.request.method).toBe('POST'); + expect(req.request.body).toBeNull(); + req.flush(mockResponse); + + expect(conversationServiceMock.convertDateFromServer).toHaveBeenCalled(); + }); + }); }); diff --git a/src/test/javascript/spec/directive/posting.directive.spec.ts b/src/test/javascript/spec/directive/posting.directive.spec.ts index ab17325e9b18..be53b6571652 100644 --- a/src/test/javascript/spec/directive/posting.directive.spec.ts +++ b/src/test/javascript/spec/directive/posting.directive.spec.ts @@ -4,6 +4,14 @@ import { Posting } from 'app/entities/metis/posting.model'; import { DisplayPriority } from 'app/shared/metis/metis.util'; import { PostingDirective } from 'app/shared/metis/posting.directive'; import { MetisService } from 'app/shared/metis/metis.service'; +import { MockTranslateService } from '../helpers/mocks/service/mock-translate.service'; +import { MockSyncStorage } from '../helpers/mocks/service/mock-sync-storage.service'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { TranslateService } from '@ngx-translate/core'; +import { SessionStorageService } from 'ngx-webstorage'; +import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; +import { MockMetisConversationService } from '../helpers/mocks/service/mock-metis-conversation.service'; class MockPosting implements Posting { content: string; @@ -46,7 +54,14 @@ describe('PostingDirective', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TestPostingComponent], - providers: [{ provide: MetisService, useClass: MockMetisService }], + providers: [ + provideHttpClient(), + provideHttpClientTesting(), + { provide: TranslateService, useClass: MockTranslateService }, + { provide: SessionStorageService, useClass: MockSyncStorage }, + { provide: MetisService, useClass: MockMetisService }, + { provide: MetisConversationService, useClass: MockMetisConversationService }, + ], }).compileComponents(); }); From bbd3215eb83dcc0d5e76e03d8dcbfab9d0fed98a Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Thu, 12 Dec 2024 13:01:59 +0100 Subject: [PATCH 2/3] Fix tests --- .../answer-post/answer-post.component.spec.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/test/javascript/spec/component/shared/metis/answer-post/answer-post.component.spec.ts b/src/test/javascript/spec/component/shared/metis/answer-post/answer-post.component.spec.ts index 45badc3c912d..5895889c9f6e 100644 --- a/src/test/javascript/spec/component/shared/metis/answer-post/answer-post.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/answer-post/answer-post.component.spec.ts @@ -18,12 +18,18 @@ import { MockMetisService } from '../../../../helpers/mocks/service/mock-metis-s import { Posting, PostingType } from 'app/entities/metis/posting.model'; import { AnswerPost } from 'app/entities/metis/answer-post.model'; import dayjs from 'dayjs/esm'; -import { ArtemisDatePipe } from '../../../../../../../main/webapp/app/shared/pipes/artemis-date.pipe'; -import { ArtemisTranslatePipe } from '../../../../../../../main/webapp/app/shared/pipes/artemis-translate.pipe'; -import { TranslateDirective } from '../../../../../../../main/webapp/app/shared/language/translate.directive'; +import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; import { MockTranslateService } from '../../../../helpers/mocks/service/mock-translate.service'; import { TranslateService } from '@ngx-translate/core'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { MockSyncStorage } from '../../../../helpers/mocks/service/mock-sync-storage.service'; +import { SessionStorageService } from 'ngx-webstorage'; +import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; +import { MockMetisConversationService } from '../../../../helpers/mocks/service/mock-metis-conversation.service'; describe('AnswerPostComponent', () => { let component: AnswerPostComponent; @@ -50,9 +56,13 @@ describe('AnswerPostComponent', () => { MockDirective(TranslateDirective), ], providers: [ + provideHttpClient(), + provideHttpClientTesting(), { provide: DOCUMENT, useValue: document }, { provide: MetisService, useClass: MockMetisService }, { provide: TranslateService, useClass: MockTranslateService }, + { provide: SessionStorageService, useClass: MockSyncStorage }, + { provide: MetisConversationService, useClass: MockMetisConversationService }, ], }) .compileComponents() From 76b83f2089b50f3b6cd248ff193f2d4c34e3be46 Mon Sep 17 00:00:00 2001 From: Paul Rangger Date: Fri, 13 Dec 2024 12:04:12 +0100 Subject: [PATCH 3/3] Increase code coverage in tests --- .../webapp/app/shared/metis/metis.service.ts | 3 +- .../app/shared/metis/posting.directive.ts | 4 +- .../shared/metis/post/post.component.spec.ts | 1 + .../spec/directive/posting.directive.spec.ts | 203 +++++++++++++++++- .../mock-metis-conversation.service.ts | 8 + 5 files changed, 209 insertions(+), 10 deletions(-) diff --git a/src/main/webapp/app/shared/metis/metis.service.ts b/src/main/webapp/app/shared/metis/metis.service.ts index a4415a2d1cee..da43d6ba258f 100644 --- a/src/main/webapp/app/shared/metis/metis.service.ts +++ b/src/main/webapp/app/shared/metis/metis.service.ts @@ -44,7 +44,6 @@ export class MetisService implements OnDestroy { private currentConversation?: ConversationDTO = undefined; private user: User; private pageType: PageType; - private course: Course; private courseId: number; private cachedPosts: Post[] = []; private cachedTotalNumberOfPosts: number; @@ -53,6 +52,8 @@ export class MetisService implements OnDestroy { private courseWideTopicSubscription: Subscription; private savedPostService: SavedPostService = inject(SavedPostService); + course: Course; + constructor( protected postService: PostService, protected answerPostService: AnswerPostService, diff --git a/src/main/webapp/app/shared/metis/posting.directive.ts b/src/main/webapp/app/shared/metis/posting.directive.ts index 5e7f0d062b3b..71c40bf0b12e 100644 --- a/src/main/webapp/app/shared/metis/posting.directive.ts +++ b/src/main/webapp/app/shared/metis/posting.directive.ts @@ -145,7 +145,7 @@ export abstract class PostingDirective implements OnInit, OnD * @param referencedUserLogin login of the referenced user */ onUserReferenceClicked(referencedUserLogin: string) { - const course = this.metisService.getCourse(); + const course = this.metisService.course; if (isMessagingEnabled(course)) { if (this.isCommunicationPage) { this.metisConversationService.createOneToOneChat(referencedUserLogin).subscribe(); @@ -171,7 +171,7 @@ export abstract class PostingDirective implements OnInit, OnD const referencedUserId = this.posting.author?.id; - const course = this.metisService.getCourse(); + const course = this.metisService.course; if (isMessagingEnabled(course)) { if (this.isCommunicationPage) { this.metisConversationService.createOneToOneChatWithId(referencedUserId).subscribe(); diff --git a/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts b/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts index 3f37a29a0d23..275950975bc4 100644 --- a/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/post/post.component.spec.ts @@ -93,6 +93,7 @@ describe('PostComponent', () => { .then(() => { fixture = TestBed.createComponent(PostComponent); metisService = TestBed.inject(MetisService); + metisService.course = metisCourse; component = fixture.componentInstance; debugElement = fixture.debugElement; diff --git a/src/test/javascript/spec/directive/posting.directive.spec.ts b/src/test/javascript/spec/directive/posting.directive.spec.ts index be53b6571652..0591cf0ded17 100644 --- a/src/test/javascript/spec/directive/posting.directive.spec.ts +++ b/src/test/javascript/spec/directive/posting.directive.spec.ts @@ -12,12 +12,28 @@ import { TranslateService } from '@ngx-translate/core'; import { SessionStorageService } from 'ngx-webstorage'; import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; import { MockMetisConversationService } from '../helpers/mocks/service/mock-metis-conversation.service'; +import { of } from 'rxjs'; +import { OneToOneChatService } from 'app/shared/metis/conversations/one-to-one-chat.service'; +import { Router } from '@angular/router'; +import { Course } from 'app/entities/course.model'; +import { MockProvider } from 'ng-mocks'; +import { User } from 'app/core/user/user.model'; +import * as courseModel from 'app/entities/course.model'; + +class MockOneToOneChatService { + createWithId = jest.fn().mockReturnValue(of({ body: { id: 1 } })); + create = jest.fn().mockReturnValue(of({ body: { id: 1 } })); +} class MockPosting implements Posting { + id: number; content: string; + author?: User; - constructor(content: string) { + constructor(id: number, content: string, author: User) { + this.id = id; this.content = content; + this.author = author; } } @@ -29,8 +45,6 @@ class MockReactionsBar { selectReaction = jest.fn(); } -class MockMetisService {} - @Component({ template: `
`, }) @@ -50,6 +64,10 @@ describe('PostingDirective', () => { let component: TestPostingComponent; let fixture: ComponentFixture; let mockReactionsBar: MockReactionsBar; + let mockMetisService: MetisService; + let mockOneToOneChatService: OneToOneChatService; + let mockMetisConversationService: MetisConversationService; + let mockRouter: Router; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -59,21 +77,35 @@ describe('PostingDirective', () => { provideHttpClientTesting(), { provide: TranslateService, useClass: MockTranslateService }, { provide: SessionStorageService, useClass: MockSyncStorage }, - { provide: MetisService, useClass: MockMetisService }, + MockProvider(MetisService), { provide: MetisConversationService, useClass: MockMetisConversationService }, + { provide: OneToOneChatService, useClass: MockOneToOneChatService }, + { provide: Router, useValue: { navigate: jest.fn() } }, ], }).compileComponents(); - }); - beforeEach(() => { fixture = TestBed.createComponent(TestPostingComponent); component = fixture.componentInstance; + jest.mock('app/entities/course.model', () => ({ + ...jest.requireActual('app/entities/course.model'), + isMessagingEnabled: jest.fn(), + })); mockReactionsBar = new MockReactionsBar(); component.reactionsBar = mockReactionsBar; - component.posting = new MockPosting('Test content'); + const user = new User(); + user.id = 123; + component.posting = new MockPosting(123, 'Test content', user); component.isCommunicationPage = false; component.isThreadSidebar = false; fixture.detectChanges(); + + mockMetisService = TestBed.inject(MetisService); + const course = new Course(); + course.id = 1; + mockMetisService.course = course; + mockOneToOneChatService = TestBed.inject(OneToOneChatService); + mockMetisConversationService = TestBed.inject(MetisConversationService); + mockRouter = TestBed.inject(Router); }); afterEach(() => { @@ -141,4 +173,161 @@ describe('PostingDirective', () => { component.toggleEmojiSelect(); expect(component.showReactionSelector).toBeFalse(); }); + + it('should not proceed in onUserNameClicked if author is not set', () => { + const isMessagingEnabledSpy = jest.spyOn(courseModel, 'isMessagingEnabled').mockReturnValue(true); + + component.posting.author = undefined; + component.onUserNameClicked(); + + expect(isMessagingEnabledSpy).not.toHaveBeenCalled(); + }); + + it('should not proceed in onUserNameClicked if messaging is not enabled', () => { + jest.spyOn(courseModel, 'isMessagingEnabled').mockReturnValue(false); + const createOneToOneChatSpy = jest.spyOn(mockMetisConversationService, 'createOneToOneChatWithId'); + const createChatSpy = jest.spyOn(mockOneToOneChatService, 'createWithId'); + const navigateSpy = jest.spyOn(mockRouter, 'navigate'); + + component.onUserNameClicked(); + + expect(createOneToOneChatSpy).not.toHaveBeenCalled(); + expect(createChatSpy).not.toHaveBeenCalled(); + expect(navigateSpy).not.toHaveBeenCalled(); + }); + + it('should not proceed in onUserReferenceClicked if messaging is not enabled', () => { + jest.spyOn(courseModel, 'isMessagingEnabled').mockReturnValue(false); + const createOneToOneChatSpy = jest.spyOn(mockMetisConversationService, 'createOneToOneChat'); + const createChatSpy = jest.spyOn(mockOneToOneChatService, 'create'); + const navigateSpy = jest.spyOn(mockRouter, 'navigate'); + + component.onUserReferenceClicked('test'); + + expect(createOneToOneChatSpy).not.toHaveBeenCalled(); + expect(createChatSpy).not.toHaveBeenCalled(); + expect(navigateSpy).not.toHaveBeenCalled(); + }); + + it('should create one-to-one chat in onUserNameClicked when messaging is enabled', () => { + jest.spyOn(courseModel, 'isMessagingEnabled').mockReturnValue(true); + component.isCommunicationPage = true; + + const createOneToOneChatIdSpy = jest.spyOn(mockMetisConversationService, 'createOneToOneChatWithId'); + const createWithIdSpy = jest.spyOn(mockOneToOneChatService, 'createWithId'); + + component.onUserNameClicked(); + + expect(createOneToOneChatIdSpy).toHaveBeenCalledWith(123); + + component.isCommunicationPage = false; + + component.onUserNameClicked(); + + expect(createWithIdSpy).toHaveBeenCalledWith(1, 123); + }); + + it('should create one-to-one chat in onUserReferenceClicked when messaging is enabled', () => { + jest.spyOn(courseModel, 'isMessagingEnabled').mockReturnValue(true); + component.isCommunicationPage = true; + + const createOneToOneChatSpy = jest.spyOn(mockMetisConversationService, 'createOneToOneChat'); + const createSpy = jest.spyOn(mockOneToOneChatService, 'create'); + + component.onUserReferenceClicked('test'); + + expect(createOneToOneChatSpy).toHaveBeenCalledWith('test'); + + component.isCommunicationPage = false; + + component.onUserReferenceClicked('test'); + + expect(createSpy).toHaveBeenCalledWith(1, 'test'); + }); + + it('should set isDeleted to true when delete event is triggered', () => { + component.onDeleteEvent(true); + expect(component.isDeleted).toBeTrue(); + }); + + it('should set isDeleted to false when delete event is false', () => { + component.onDeleteEvent(false); + expect(component.isDeleted).toBeFalse(); + }); + + it('should clear existing delete timer and interval before setting up new ones', () => { + const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); + const clearIntervalSpy = jest.spyOn(global, 'clearInterval'); + + component.deleteTimer = setTimeout(() => {}, 1000); + component.deleteInterval = setInterval(() => {}, 1000); + + component.onDeleteEvent(true); + + expect(clearTimeoutSpy).toHaveBeenCalledOnce(); + expect(clearIntervalSpy).toHaveBeenCalledOnce(); + }); + + it('should set delete timer to initial value when delete is true', () => { + component.onDeleteEvent(true); + expect(component.deleteTimerInSeconds).toBe(component.timeToDeleteInSeconds); + }); + + it('should call metisService.deletePost for regular post', () => { + const deletePostSpy = jest.spyOn(mockMetisService, 'deletePost'); + jest.useFakeTimers(); + + component.isAnswerPost = false; + component.onDeleteEvent(true); + + jest.runOnlyPendingTimers(); + + expect(deletePostSpy).toHaveBeenCalledWith(component.posting); + }); + + it('should call metisService.deleteAnswerPost for answer post', () => { + const deleteAnswerPostSpy = jest.spyOn(mockMetisService, 'deleteAnswerPost'); + jest.useFakeTimers(); + + component.isAnswerPost = true; + component.onDeleteEvent(true); + + jest.runOnlyPendingTimers(); + + expect(deleteAnswerPostSpy).toHaveBeenCalledWith(component.posting); + }); + + it('should set up interval to decrement delete timer', () => { + jest.useFakeTimers(); + + component.onDeleteEvent(true); + + jest.advanceTimersByTime(1000); + expect(component.deleteTimerInSeconds).toBe(5); + + jest.advanceTimersByTime(1000); + expect(component.deleteTimerInSeconds).toBe(4); + }); + + it('should stop timer at 0 when decrementing', () => { + jest.useFakeTimers(); + + component.onDeleteEvent(true); + + jest.advanceTimersByTime(7000); + + expect(component.deleteTimerInSeconds).toBe(0); + + jest.useRealTimers(); + }); + + it('should do nothing if delete event is false', () => { + const deletePostSpy = jest.spyOn(mockMetisService, 'deletePost'); + const deleteAnswerPostSpy = jest.spyOn(mockMetisService, 'deleteAnswerPost'); + + component.onDeleteEvent(false); + + expect(deletePostSpy).not.toHaveBeenCalled(); + expect(deleteAnswerPostSpy).not.toHaveBeenCalled(); + }); }); diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-metis-conversation.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-metis-conversation.service.ts index 7f85809374f3..fb023c652251 100644 --- a/src/test/javascript/spec/helpers/mocks/service/mock-metis-conversation.service.ts +++ b/src/test/javascript/spec/helpers/mocks/service/mock-metis-conversation.service.ts @@ -40,6 +40,14 @@ export class MockMetisConversationService { return EMPTY; }; + createOneToOneChatWithId = (userId: number): Observable => { + return EMPTY; + }; + + createOneToOneChat = (userId: number): Observable => { + return EMPTY; + }; + forceRefresh(notifyActiveConversationSubscribers = true, notifyConversationsSubscribers = true): Observable { return EMPTY; }