diff --git a/src/main/webapp/app/overview/course-conversations/course-conversations.module.ts b/src/main/webapp/app/overview/course-conversations/course-conversations.module.ts index 172e645e956d..0d593728c9f2 100644 --- a/src/main/webapp/app/overview/course-conversations/course-conversations.module.ts +++ b/src/main/webapp/app/overview/course-conversations/course-conversations.module.ts @@ -3,13 +3,11 @@ import { ArtemisSharedModule } from 'app/shared/shared.module'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ConversationSelectionSidebarComponent } from 'app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component'; import { CourseConversationsComponent } from 'app/overview/course-conversations/course-conversations.component'; import { ArtemisDataTableModule } from 'app/shared/data-table/data-table.module'; import { ConversationMessagesComponent } from 'app/overview/course-conversations/layout/conversation-messages/conversation-messages.component'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { ConversationThreadSidebarComponent } from 'app/overview/course-conversations/layout/conversation-thread-sidebar/conversation-thread-sidebar.component'; -import { ConversationSidebarSectionComponent } from './layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component'; import { ChannelsOverviewDialogComponent } from './dialogs/channels-overview-dialog/channels-overview-dialog.component'; import { ChannelItemComponent } from './dialogs/channels-overview-dialog/channel-item/channel-item.component'; import { ChannelFormComponent } from './dialogs/channels-create-dialog/channel-form/channel-form.component'; @@ -26,7 +24,6 @@ import { ConversationMemberRowComponent } from './dialogs/conversation-detail-di import { GenericUpdateTextPropertyDialogComponent } from './dialogs/generic-update-text-property-dialog/generic-update-text-property-dialog.component'; import { GenericConfirmationDialogComponent } from './dialogs/generic-confirmation-dialog/generic-confirmation-dialog.component'; import { ConversationSettingsComponent } from './dialogs/conversation-detail-dialog/tabs/conversation-settings/conversation-settings.component'; -import { ConversationSidebarEntryComponent } from './layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component'; import { OneToOneChatCreateDialogComponent } from './dialogs/one-to-one-chat-create-dialog/one-to-one-chat-create-dialog.component'; import { GroupChatCreateDialogComponent } from './dialogs/group-chat-create-dialog/group-chat-create-dialog.component'; import { GroupChatIconComponent } from './other/group-chat-icon/group-chat-icon.component'; @@ -61,10 +58,8 @@ const routes: Routes = [ declarations: [ CourseConversationsComponent, CourseConversationsCodeOfConductComponent, - ConversationSelectionSidebarComponent, ConversationThreadSidebarComponent, ConversationMessagesComponent, - ConversationSidebarSectionComponent, ChannelsOverviewDialogComponent, ChannelItemComponent, ChannelFormComponent, @@ -80,7 +75,6 @@ const routes: Routes = [ GenericUpdateTextPropertyDialogComponent, GenericConfirmationDialogComponent, ConversationSettingsComponent, - ConversationSidebarEntryComponent, OneToOneChatCreateDialogComponent, GroupChatCreateDialogComponent, GroupChatIconComponent, diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.html b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.html deleted file mode 100644 index 932aca3ae777..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.html +++ /dev/null @@ -1,256 +0,0 @@ -@if (course) { -
-
-
- -
-

- - {{ 'artemisApp.conversationsLayout.conversationSelectionSideBar.header' | artemisTranslate }} -

-
- -
-
- -
-
- - - ({{ numberOfConversationsPassingFilter }}) - - -
-
- - - - - - @if (isMessagingEnabled) { -
- -
- - @if (canCreateChannel(course)) { - - } -
-
- } -
-
- - - - @if (isMessagingEnabled) { -
- -
- -
-
- } -
-
- - - - @if (isMessagingEnabled) { -
- -
- -
-
- } -
-
- - - - @if (isMessagingEnabled) { -
- -
- -
-
- } -
-
- - @if (isMessagingEnabled) { - - -
- -
-
-
- } - - @if (isMessagingEnabled) { - - -
- -
-
-
- } -
-
- - @if (course.courseInformationSharingMessagingCodeOfConduct) { - - } -
-
- -
-
- -
- - - - -
-
-} diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.scss b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.scss deleted file mode 100644 index 2f4ef688b478..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.scss +++ /dev/null @@ -1,151 +0,0 @@ -@import 'src/main/webapp/content/scss/artemis-variables'; - -$draggable-width: 15px; -$conversation-section-card-min-width: 215px; - -@mixin icon { - fa-icon svg path { - fill: var(--metis-conversation-sidebar); - stroke: var(--metis-conversation-sidebar); - } -} - -@mixin active-icon { - fa-icon svg path { - fill: var(--metis-conversation-sidebar-active); - stroke: var(--metis-conversation-sidebar-active); - } -} - -.selection-sidebar { - .filter-active { - span { - color: var(--primary); - } - - fa-icon svg path { - fill: var(--primary); - stroke: var(--primary); - } - } - - .sidebar-button { - border: 0; - border-radius: var(--bs-btn-border-radius); - - @include icon; - - &:hover { - background-color: var(--metis-conversation-sidebar-button-background-hover); - } - } - - .expand-button { - color: var(--metis-conversation-sidebar); - border-radius: var(--bs-btn-border-radius); - - @include icon; - - &:hover { - color: var(--metis-conversation-sidebar-active); - background-color: var(--metis-conversation-sidebar-button-background-hover); - - @include active-icon; - } - } - - .dropdown-toggle::after { - content: none; - } - - .expanded-conversations { - display: flex; - width: calc(#{$draggable-width} + #{$conversation-section-card-min-width}); - margin-left: auto; - - .scrollbar { - position: relative; - max-height: 700px; - overflow: auto; - } - - .wrapper-scroll-y { - display: block; - } - - .draggable-right { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-width: $draggable-width; - } - - .card { - width: inherit; - min-width: $conversation-section-card-min-width; - - .card-header { - display: inline-flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - - .card-title { - display: flex; - } - - .row > .col-auto:last-child { - display: flex; - flex-direction: column; - justify-content: center; - } - } - - .card-body { - padding: 0; - - .conversation-empty { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - opacity: 0.75; - padding-top: 30px; - gap: 10px; - } - } - } - } - - .collapsed-conversations { - display: flex; - width: 38px; - justify-content: space-between; - flex-flow: column; - cursor: pointer; - - span { - writing-mode: vertical-lr; - transform: rotate(180deg); - margin: auto; - } - - .expand-conversations-icon { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - place-self: center; - } - } - - @media screen and (max-width: 992px) { - .expanded-conversations { - width: 94vw; - - .draggable-right { - display: none; - } - } - } -} diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.ts b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.ts deleted file mode 100644 index 0858dccc61b2..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.ts +++ /dev/null @@ -1,366 +0,0 @@ -import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; -import interact from 'interactjs'; -import { faChevronLeft, faChevronRight, faComments, faFilter, faGripLinesVertical, faPlus } from '@fortawesome/free-solid-svg-icons'; -import { EMPTY, Subject, from, map, takeUntil } from 'rxjs'; -import { UserPublicInfoDTO } from 'app/core/user/user.model'; -import { Course, isMessagingEnabled } from 'app/entities/course.model'; -import { ConversationDTO } from 'app/entities/metis/conversation/conversation.model'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { ChannelsOverviewDialogComponent } from 'app/overview/course-conversations/dialogs/channels-overview-dialog/channels-overview-dialog.component'; -import { ChannelsCreateDialogComponent } from 'app/overview/course-conversations/dialogs/channels-create-dialog/channels-create-dialog.component'; -import { Channel, ChannelDTO, ChannelSubType, isChannelDTO } from 'app/entities/metis/conversation/channel.model'; -import { GroupChatDTO, isGroupChatDTO } from 'app/entities/metis/conversation/group-chat.model'; -import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; -import { canCreateChannel } from 'app/shared/metis/conversations/conversation-permissions.utils'; -import { AccountService } from 'app/core/auth/account.service'; -import { OneToOneChatCreateDialogComponent } from 'app/overview/course-conversations/dialogs/one-to-one-chat-create-dialog/one-to-one-chat-create-dialog.component'; -import { OneToOneChatDTO, isOneToOneChatDTO } from 'app/entities/metis/conversation/one-to-one-chat.model'; -import { GroupChatCreateDialogComponent } from 'app/overview/course-conversations/dialogs/group-chat-create-dialog/group-chat-create-dialog.component'; -import { catchError, debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; -import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; -import { defaultFirstLayerDialogOptions } from 'app/overview/course-conversations/other/conversation.util'; - -interface SearchQuery { - searchTerm: string; - force: boolean; -} - -@Component({ - selector: 'jhi-conversation-selection-sidebar', - styleUrls: ['./conversation-selection-sidebar.component.scss'], - templateUrl: './conversation-selection-sidebar.component.html', - encapsulation: ViewEncapsulation.None, -}) -export class ConversationSelectionSidebarComponent implements AfterViewInit, OnInit, OnDestroy { - private ngUnsubscribe = new Subject(); - private readonly search$ = new Subject(); - searchTerm = ''; - course?: Course; - - activeConversation?: ConversationDTO; - allConversations: ConversationDTO[] = []; - - starredConversations: ConversationDTO[] = []; - displayedStarredConversations: ConversationDTO[] = []; - - channelConversations: ChannelDTO[] = []; - displayedChannelConversations: ChannelDTO[] = []; - - oneToOneChats: OneToOneChatDTO[] = []; - displayedOneToOneChats: OneToOneChatDTO[] = []; - - groupChats: GroupChatDTO[] = []; - displayedGroupChats: GroupChatDTO[] = []; - - collapsed: boolean; - // Icons - faChevronLeft = faChevronLeft; - faChevronRight = faChevronRight; - faGripLinesVertical = faGripLinesVertical; - faConversation = faComments; - faPlus = faPlus; - faFilter = faFilter; - numberOfConversationsPassingFilter = 0; - - canCreateChannel = canCreateChannel; - channelSubType = ChannelSubType; - - displayedGeneralChannels: ChannelDTO[] = []; - displayedExerciseChannels: ChannelDTO[] = []; - displayedLectureChannels: ChannelDTO[] = []; - displayedExamChannels: ChannelDTO[] = []; - - isMessagingEnabled = false; - - constructor( - private modalService: NgbModal, - private cdr: ChangeDetectorRef, - // instantiated at course-conversation.component.ts - public metisConversationService: MetisConversationService, - public accountService: AccountService, - public conversationService: ConversationService, - ) {} - - ngOnInit(): void { - this.course = this.metisConversationService.course; - this.isMessagingEnabled = isMessagingEnabled(this.course); - this.subscribeToSearch(); - this.subscribeToActiveConversation(); - this.subscribeToConversationsOfUser(); - } - - private subscribeToSearch() { - this.search$ - .pipe( - debounceTime(300), - distinctUntilChanged((prev, curr) => { - if (curr.force === true) { - return false; - } else { - return prev === curr; - } - }), - tap(() => { - this.displayedStarredConversations = []; - this.setDisplayedChannels([]); - this.displayedOneToOneChats = []; - this.displayedGroupChats = []; - }), - map((query: SearchQuery) => { - const searchTerm = query.searchTerm !== null && query.searchTerm !== undefined ? query.searchTerm : ''; - return searchTerm.trim().toLowerCase(); - }), - tap((searchTerm: string) => { - this.searchTerm = searchTerm; - }), - takeUntil(this.ngUnsubscribe), - ) - .subscribe({ - next: (searchTerm: string) => { - this.displayedStarredConversations = this.starredConversations.filter((conversation) => { - return this.conversationService.getConversationName(conversation).toLowerCase().includes(searchTerm); - }); - this.setDisplayedChannels( - this.channelConversations.filter((conversation) => { - return this.conversationService.getConversationName(conversation).toLowerCase().includes(searchTerm); - }), - ); - this.displayedOneToOneChats = this.oneToOneChats.filter((conversation) => { - return this.conversationService.getConversationName(conversation).toLowerCase().includes(searchTerm); - }); - this.displayedGroupChats = this.groupChats.filter((conversation) => { - return this.conversationService.getConversationName(conversation).toLowerCase().includes(searchTerm); - }); - this.numberOfConversationsPassingFilter = - this.displayedStarredConversations.length + - this.displayedChannelConversations.length + - this.displayedOneToOneChats.length + - this.displayedGroupChats.length; - - this.cdr.detectChanges(); - }, - }); - } - - ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); - } - - private subscribeToActiveConversation() { - this.metisConversationService.activeConversation$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((activeConversation: ConversationDTO) => { - this.activeConversation = activeConversation; - }); - } - - private subscribeToConversationsOfUser() { - this.metisConversationService.conversationsOfUser$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((conversations: ConversationDTO[]) => { - this.onConversationsUpdate(conversations); - }); - } - - onSearchQueryInput($event: Event) { - const searchTerm = ($event.target as HTMLInputElement).value?.trim().toLowerCase() ?? ''; - this.search$.next({ - searchTerm, - force: false, - }); - } - - onConversationsUpdate(conversations: ConversationDTO[]) { - this.allConversations = conversations ?? []; - - this.starredConversations = this.allConversations - .filter((conversation) => conversation.isFavorite) - .sort((a, b) => { - // sort by last message date - const aLastMessageDate = a.lastMessageDate ? a.lastMessageDate : a.creationDate; - const bLastMessageDate = b.lastMessageDate ? b.lastMessageDate : b.creationDate; - // newest messages at the top of the list - return bLastMessageDate!.isAfter(aLastMessageDate!) ? 1 : -1; - }); - this.channelConversations = this.allConversations - .filter((conversation) => isChannelDTO(conversation) && !conversation.isFavorite) - .map((channel) => channel as Channel) - .sort((a, b) => a.name!.localeCompare(b.name!)); - this.oneToOneChats = this.allConversations - .filter((conversation) => isOneToOneChatDTO(conversation) && !conversation.isFavorite) - .map((oneToOneChat) => oneToOneChat as OneToOneChatDTO) - .sort((a, b) => { - // sort by last message date - const aLastMessageDate = a.lastMessageDate ? a.lastMessageDate : a.creationDate; - const bLastMessageDate = b.lastMessageDate ? b.lastMessageDate : b.creationDate; - // newest messages at the top of the list - return bLastMessageDate!.isAfter(aLastMessageDate!) ? 1 : -1; - }); - this.groupChats = this.allConversations - .filter((conversation) => isGroupChatDTO(conversation) && !conversation.isFavorite) - .map((groupChatDTO) => groupChatDTO as GroupChatDTO) - .sort((a, b) => { - // sort by last message date - const aLastMessageDate = a.lastMessageDate ? a.lastMessageDate : a.creationDate; - const bLastMessageDate = b.lastMessageDate ? b.lastMessageDate : b.creationDate; - // newest messages at the top of the list - return bLastMessageDate!.isAfter(aLastMessageDate!) ? 1 : -1; - }); - this.search$.next({ - searchTerm: this.searchTerm, - force: true, - }); - } - - ngAfterViewInit(): void { - // allows the conversation sidebar to be resized towards the right-hand side - interact('.expanded-conversations') - .resizable({ - edges: { left: false, right: '.draggable-right', bottom: false, top: false }, - modifiers: [ - // Set maximum width of the conversation sidebar - interact.modifiers!.restrictSize({ - min: { width: 230, height: 0 }, - max: { width: 500, height: 4000 }, - }), - ], - inertia: true, - }) - .on('resizestart', function (event: any) { - event.target.classList.add('card-resizable'); - }) - .on('resizeend', function (event: any) { - event.target.classList.remove('card-resizable'); - }) - .on('resizemove', function (event: any) { - const target = event.target; - target.style.width = event.rect.width + 'px'; - }); - } - - onSettingsDidChange() { - this.metisConversationService - .forceRefresh() - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe({ - complete: () => {}, - }); - } - - openCreateChannelDialog(event: MouseEvent) { - event.stopPropagation(); - const modalRef: NgbModalRef = this.modalService.open(ChannelsCreateDialogComponent, defaultFirstLayerDialogOptions); - modalRef.componentInstance.course = this.course; - modalRef.componentInstance.initialize(); - from(modalRef.result) - .pipe( - catchError(() => EMPTY), - takeUntil(this.ngUnsubscribe), - ) - .subscribe((channelToCreate: ChannelDTO) => { - this.metisConversationService.createChannel(channelToCreate).subscribe({ - complete: () => { - this.metisConversationService.forceRefresh().subscribe({ - complete: () => {}, - }); - }, - }); - }); - } - - openCreateGroupChatDialog(event: MouseEvent) { - event.stopPropagation(); - const modalRef: NgbModalRef = this.modalService.open(GroupChatCreateDialogComponent, defaultFirstLayerDialogOptions); - modalRef.componentInstance.course = this.course; - modalRef.componentInstance.initialize(); - from(modalRef.result) - .pipe( - catchError(() => EMPTY), - takeUntil(this.ngUnsubscribe), - ) - .subscribe((chatPartners: UserPublicInfoDTO[]) => { - this.metisConversationService.createGroupChat(chatPartners?.map((partner) => partner.login!)).subscribe({ - complete: () => { - this.metisConversationService.forceRefresh().subscribe({ - complete: () => {}, - }); - }, - }); - }); - } - - openCreateOneToOneChatDialog(event: MouseEvent) { - event.stopPropagation(); - const modalRef: NgbModalRef = this.modalService.open(OneToOneChatCreateDialogComponent, defaultFirstLayerDialogOptions); - modalRef.componentInstance.course = this.course; - modalRef.componentInstance.initialize(); - from(modalRef.result) - .pipe( - catchError(() => EMPTY), - takeUntil(this.ngUnsubscribe), - ) - .subscribe((chatPartner: UserPublicInfoDTO) => { - if (chatPartner?.login) { - this.metisConversationService.createOneToOneChat(chatPartner.login).subscribe({ - complete: () => { - this.metisConversationService.forceRefresh().subscribe({ - complete: () => {}, - }); - }, - }); - } - }); - } - - openChannelOverviewDialog(event: MouseEvent, subType: ChannelSubType) { - event.stopPropagation(); - const modalRef: NgbModalRef = this.modalService.open(ChannelsOverviewDialogComponent, defaultFirstLayerDialogOptions); - modalRef.componentInstance.course = this.course; - modalRef.componentInstance.createChannelFn = subType === ChannelSubType.GENERAL ? this.metisConversationService.createChannel : undefined; - modalRef.componentInstance.channelSubType = subType; - modalRef.componentInstance.initialize(); - from(modalRef.result) - .pipe( - catchError(() => EMPTY), - takeUntil(this.ngUnsubscribe), - ) - .subscribe((result) => { - const [newActiveConversation, isModificationPerformed] = result; - if (isModificationPerformed) { - // when new active conversation is explictely set, we do not need to notify subscribers in the force refresh - this.metisConversationService.forceRefresh(!newActiveConversation, true).subscribe({ - complete: () => { - if (newActiveConversation) { - this.metisConversationService.setActiveConversation(newActiveConversation); - } - }, - }); - } else { - if (newActiveConversation) { - this.metisConversationService.setActiveConversation(newActiveConversation); - } - } - }); - } - - onConversationSelected($event: ConversationDTO) { - this.metisConversationService.setActiveConversation($event); - } - - updateConversations() { - this.onConversationsUpdate([...this.allConversations]); - } - - private setDisplayedChannels(channels: ChannelDTO[]): void { - this.displayedChannelConversations = channels; - this.displayedGeneralChannels = this.filterChannelsOfType(ChannelSubType.GENERAL); - this.displayedExerciseChannels = this.filterChannelsOfType(ChannelSubType.EXERCISE); - this.displayedLectureChannels = this.filterChannelsOfType(ChannelSubType.LECTURE); - this.displayedExamChannels = this.filterChannelsOfType(ChannelSubType.EXAM); - } - - private filterChannelsOfType(subType: ChannelSubType): ChannelDTO[] { - return this.displayedChannelConversations.filter((channel) => channel.subType === subType); - } - - openCodeOfConduct() { - this.metisConversationService.setCodeOfConduct(); - } -} diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.html b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.html deleted file mode 100644 index 4a70da5b2d6e..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.html +++ /dev/null @@ -1,64 +0,0 @@ -@if (conversation) { -
-
- - @if (conversationAsChannel) { - - } - @if (getAsGroupChat(conversation); as groupChatDTO) { - - } - {{ conversationService.getConversationName(conversation) }} - - - - {{ conversation.unreadMessagesCount }} - - -
-
-
- -
- @if (conversationAsChannel?.subTypeReferenceId) { - - {{ channelSubTypeReferenceTranslationKey | artemisTranslate }} - - } - @if (!isOneToOneChat(conversation)) { - - } - - - -
-
-
-
-} diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.scss b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.scss deleted file mode 100644 index ad1ca54a41d5..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.scss +++ /dev/null @@ -1,49 +0,0 @@ -@import '../../conversation-selection-sidebar.component.scss'; - -.conversation-list-entry { - cursor: pointer; - display: flex; - padding: 0.2rem; - justify-content: space-between; - color: var(--metis-conversation-sidebar); - - .dropdown-toggle::after { - content: none; - } - - &.active { - background-color: var(--metis-light-blue); - color: var(--metis-conversation-sidebar-active); - } - - &.muted { - color: var(--metis-conversation-sidebar-muted); - } - - .interaction { - visibility: hidden; - } - - &:hover { - color: var(--metis-conversation-sidebar-active); - - .interaction { - visibility: visible; - } - - .sidebar-button { - @include active-icon; - } - } - - &.conversation-name { - font-size: 0.8rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } -} - -a.sub-type-reference:link { - text-decoration: none !important; -} diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.ts b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.ts deleted file mode 100644 index 244ee8137875..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core'; -import { ConversationDTO, shouldNotifyRecipient } from 'app/entities/metis/conversation/conversation.model'; -import { ChannelDTO, getAsChannelDTO } from 'app/entities/metis/conversation/channel.model'; -import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; -import { faEllipsis } from '@fortawesome/free-solid-svg-icons'; -import { EMPTY, Subject, debounceTime, distinctUntilChanged, from, takeUntil } from 'rxjs'; -import { mergeWith } from 'rxjs/operators'; -import { Course } from 'app/entities/course.model'; -import { AlertService } from 'app/core/util/alert.service'; -import { onError } from 'app/shared/util/global.utils'; -import { HttpErrorResponse } from '@angular/common/http'; -import { getAsGroupChatDTO } from 'app/entities/metis/conversation/group-chat.model'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { - ConversationDetailDialogComponent, - ConversationDetailTabs, -} from 'app/overview/course-conversations/dialogs/conversation-detail-dialog/conversation-detail-dialog.component'; -import { isOneToOneChatDTO } from 'app/entities/metis/conversation/one-to-one-chat.model'; -import { defaultFirstLayerDialogOptions, getChannelSubTypeReferenceTranslationKey } from 'app/overview/course-conversations/other/conversation.util'; -import { catchError } from 'rxjs/operators'; -import { MetisService } from 'app/shared/metis/metis.service'; -import { NotificationService } from 'app/shared/notification/notification.service'; - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: '[jhi-conversation-sidebar-entry]', - templateUrl: './conversation-sidebar-entry.component.html', - styleUrls: ['./conversation-sidebar-entry.component.scss'], - encapsulation: ViewEncapsulation.None, -}) -export class ConversationSidebarEntryComponent implements OnInit, OnDestroy { - private ngUnsubscribe = new Subject(); - - favorite$ = new Subject(); - hide$ = new Subject(); - mute$ = new Subject(); - - @Input() - course: Course; - - @Input() - conversation: ConversationDTO; - - @Input() - activeConversation: ConversationDTO | undefined; - - @Output() - settingsDidChange = new EventEmitter(); - - @Output() - conversationIsFavoriteDidChange = new EventEmitter(); - - @Output() - conversationIsHiddenDidChange = new EventEmitter(); - - @Output() - conversationIsMutedDidChange = new EventEmitter(); - - conversationAsChannel?: ChannelDTO; - channelSubTypeReferenceTranslationKey?: string; - channelSubTypeReferenceRouterLink?: string; - - faEllipsis = faEllipsis; - - constructor( - public conversationService: ConversationService, - private metisService: MetisService, - private notificationService: NotificationService, - private alertService: AlertService, - private modalService: NgbModal, - ) {} - - get isConversationUnread(): boolean { - // do not show unread count for open conversation that the user is currently reading - if (this.isActiveConversation || !this.conversation) { - return false; - } else { - return !!this.conversation.unreadMessagesCount && this.conversation.unreadMessagesCount > 0; - } - } - - get isActiveConversation() { - return this.activeConversation && this.conversation && this.activeConversation.id! === this.conversation.id!; - } - - getAsGroupChat = getAsGroupChatDTO; - - isOneToOneChat = isOneToOneChatDTO; - - onHiddenClicked(event: MouseEvent) { - event.stopPropagation(); - this.hide$.next(!this.conversation.isHidden); - } - - onFavoriteClicked($event: MouseEvent) { - $event.stopPropagation(); - this.favorite$.next(!this.conversation.isFavorite); - } - - onMuteClicked($event: MouseEvent) { - $event.stopPropagation(); - this.mute$.next(!this.conversation.isMuted); - } - - openConversationDetailDialog(event: MouseEvent) { - event.stopPropagation(); - const modalRef: NgbModalRef = this.modalService.open(ConversationDetailDialogComponent, defaultFirstLayerDialogOptions); - modalRef.componentInstance.course = this.course; - modalRef.componentInstance.activeConversation = this.conversation; - modalRef.componentInstance.selectedTab = ConversationDetailTabs.SETTINGS; - modalRef.componentInstance.initialize(); - from(modalRef.result) - .pipe( - catchError(() => EMPTY), - takeUntil(this.ngUnsubscribe), - ) - .subscribe(() => { - this.settingsDidChange.emit(); - }); - } - - private updateConversationIsFavorite() { - this.favorite$.pipe(debounceTime(100), distinctUntilChanged(), takeUntil(this.ngUnsubscribe)).subscribe((isFavorite) => { - if (!this.course.id || !this.conversation.id) return; - - this.conversationService.updateIsFavorite(this.course.id, this.conversation.id, isFavorite).subscribe({ - next: () => { - this.conversation.isFavorite = isFavorite; - this.conversationIsFavoriteDidChange.emit(); - }, - error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse), - }); - }); - } - - private updateConversationIsHidden() { - this.hide$.pipe(debounceTime(100), distinctUntilChanged(), takeUntil(this.ngUnsubscribe)).subscribe((isHidden) => { - if (!this.course.id || !this.conversation.id) return; - - this.conversationService.updateIsHidden(this.course.id, this.conversation.id, isHidden).subscribe({ - next: () => { - this.conversation.isHidden = isHidden; - this.conversationIsHiddenDidChange.emit(); - }, - error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse), - }); - }); - } - - private updateConversationIsMuted() { - this.mute$.pipe(debounceTime(100), distinctUntilChanged(), takeUntil(this.ngUnsubscribe)).subscribe((isMuted) => { - if (!this.course.id || !this.conversation.id) return; - - this.conversationService.updateIsMuted(this.course.id, this.conversation.id, isMuted).subscribe({ - next: () => { - this.conversation.isMuted = isMuted; - this.conversationIsMutedDidChange.emit(); - }, - error: (errorResponse: HttpErrorResponse) => onError(this.alertService, errorResponse), - }); - }); - } - - private updateConversationShouldNotifyRecipient() { - this.conversationIsHiddenDidChange.pipe(mergeWith(this.conversationIsMutedDidChange), takeUntil(this.ngUnsubscribe)).subscribe(() => { - if (!this.conversation.id) return; - - if (shouldNotifyRecipient(this.conversation)) { - this.notificationService.unmuteNotificationsForConversation(this.conversation.id); - } else { - this.notificationService.muteNotificationsForConversation(this.conversation.id); - } - }); - } - - ngOnInit(): void { - this.updateConversationIsFavorite(); - this.updateConversationIsHidden(); - this.updateConversationIsMuted(); - this.updateConversationShouldNotifyRecipient(); - this.conversationAsChannel = getAsChannelDTO(this.conversation); - this.channelSubTypeReferenceTranslationKey = getChannelSubTypeReferenceTranslationKey(this.conversationAsChannel?.subType); - this.channelSubTypeReferenceRouterLink = this.metisService.getLinkForChannelSubType(this.conversationAsChannel); - } - - ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); - } -} diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.html b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.html deleted file mode 100644 index bc4ba8d0a5a5..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.html +++ /dev/null @@ -1,61 +0,0 @@ - - - -
  • -
    diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.scss b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.scss deleted file mode 100644 index 91c05af4a8f8..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.scss +++ /dev/null @@ -1,35 +0,0 @@ -@import '../conversation-selection-sidebar.component.scss'; - -.sidebar-section { - .section-header { - display: flex; - justify-content: space-between; - cursor: pointer; - padding: 0.2rem; - color: var(--metis-conversation-sidebar); - - &:hover { - color: var(--metis-conversation-sidebar-active); - - .sidebar-button { - @include active-icon; - } - } - } - - .conversation-list { - margin: 0; - padding: 0; - list-style-type: none; - } - - .hidden-conversation-divider { - cursor: pointer; - color: var(--metis-conversation-sidebar); - - &:hover { - text-decoration: underline; - color: var(--metis-conversation-sidebar-active); - } - } -} diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.ts b/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.ts deleted file mode 100644 index 3f547add1af6..000000000000 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef, ViewEncapsulation } from '@angular/core'; -import { faChevronRight, faMessage } from '@fortawesome/free-solid-svg-icons'; -import { ConversationDTO } from 'app/entities/metis/conversation/conversation.model'; -import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; -import { Course } from 'app/entities/course.model'; -import { LocalStorageService } from 'ngx-webstorage'; - -@Component({ - selector: 'jhi-conversation-sidebar-section', - templateUrl: './conversation-sidebar-section.component.html', - styleUrls: ['./conversation-sidebar-section.component.scss'], - encapsulation: ViewEncapsulation.None, -}) -export class ConversationSidebarSectionComponent implements OnInit { - @Output() conversationSelected = new EventEmitter(); - @Output() settingsDidChange = new EventEmitter(); - @Output() conversationIsFavoriteDidChange = new EventEmitter(); - @Output() conversationIsHiddenDidChange = new EventEmitter(); - @Output() conversationIsMutedDidChange = new EventEmitter(); - - @Input() label: string; - @Input() course: Course; - @Input() activeConversation?: ConversationDTO; - @Input() headerKey: string; - @Input() searchTerm: string; - @Input() hideIfEmpty = true; - - @Input() set conversations(conversations: ConversationDTO[]) { - this.hiddenConversations = []; - this.mutedConversations = []; - this.visibleConversations = []; - this.allConversations = conversations ?? []; - conversations.forEach((conversation) => { - if (conversation.isHidden) { - this.hiddenConversations.push(conversation); - } else { - if (conversation.isMuted && !conversation.isFavorite) { - this.mutedConversations.push(conversation); - } else { - this.visibleConversations.push(conversation); - } - } - }); - this.numberOfConversations = this.allConversations.length; - } - - @ContentChild(TemplateRef) sectionButtons: TemplateRef; - - readonly PREFIX = 'collapsed.'; - - isCollapsed: boolean; - isHiddenConversationListPresented = false; - - numberOfConversations = 0; - allConversations: ConversationDTO[] = []; - visibleConversations: ConversationDTO[] = []; - mutedConversations: ConversationDTO[] = []; - hiddenConversations: ConversationDTO[] = []; - - // icon imports - faChevronRight = faChevronRight; - faMessage = faMessage; - - constructor( - public conversationService: ConversationService, - public localStorageService: LocalStorageService, - ) {} - - ngOnInit(): void { - this.isCollapsed = !!this.localStorageService.retrieve(this.storageKey); - this.localStorageService.store(this.storageKey, this.isCollapsed); - } - - get storageKey() { - return this.PREFIX + this.headerKey; - } - - get anyConversationUnread(): boolean { - // do not show unread badge for open conversation that the user is currently reading - let containsUnreadConversation = false; - for (const conversation of this.allConversations) { - if ( - conversation.unreadMessagesCount && - conversation.unreadMessagesCount > 0 && - !(this.activeConversation && this.activeConversation.id === conversation.id) && - this.isCollapsed - ) { - containsUnreadConversation = true; - break; - } - } - return containsUnreadConversation; - } - - get anyHiddenConversationUnread(): boolean { - // do not show unread badge for open conversation that the user is currently reading - let containsUnreadConversation = false; - for (const conversation of this.hiddenConversations) { - if ( - conversation.unreadMessagesCount && - conversation.unreadMessagesCount > 0 && - !(this.activeConversation && this.activeConversation.id === conversation.id) && - !this.isHiddenConversationListPresented - ) { - containsUnreadConversation = true; - break; - } - } - return containsUnreadConversation; - } - - conversationsTrackByFn = (index: number, conversation: ConversationDTO): number => conversation.id!; - - toggleCollapsed() { - this.isCollapsed = !this.isCollapsed; - this.localStorageService.store(this.storageKey, this.isCollapsed); - } - - hide() { - const noMatchesInSearch = this.searchTerm && this.searchTerm.length > 0 && !this.allConversations?.length; - const emptyConversations = this.hideIfEmpty && !this.allConversations.length; - return noMatchesInSearch || emptyConversations; - } -} diff --git a/src/main/webapp/app/shared/metis/metis.service.ts b/src/main/webapp/app/shared/metis/metis.service.ts index 644fbeb9de9c..62728044a811 100644 --- a/src/main/webapp/app/shared/metis/metis.service.ts +++ b/src/main/webapp/app/shared/metis/metis.service.ts @@ -153,19 +153,6 @@ export class MetisService implements OnDestroy { this.posts$.next(posts); } - /** - * fetches all post tags used in the current course, informs all subscribing components - */ - // TODO: unused, delete - updateCoursePostTags(): void { - this.postService - .getAllPostTagsByCourseId(this.courseId) - .pipe(map((res: HttpResponse) => res.body!.filter((tag) => !!tag))) - .subscribe((tags: string[]) => { - this.tags$.next(tags); - }); - } - /** * fetches all posts for a course, optionally fetching posts only for a certain context, i.e. a lecture, exercise or specified course-wide-context, * informs all components that subscribed on posts by sending the newly fetched posts @@ -535,16 +522,6 @@ export class MetisService implements OnDestroy { return { routerLinkComponents, displayName, queryParams }; } - /** - * Invokes the post service to get a top-k-list of course posts with high similarity scores when compared with a certain strategy - * @param {Post} tempPost that is currently created and compared against existing course posts on updates in the form group - * @return {Observable} array of similar posts that were found in the course - */ - // TODO: unused, remove - getSimilarPosts(tempPost: Post): Observable { - return this.postService.computeSimilarityScoresWithCoursePosts(tempPost, this.courseId).pipe(map((res: HttpResponse) => res.body!)); - } - /** * Creates (and updates) the websocket channel for receiving messages in dedicated channels; * On message reception, subsequent actions for updating the dependent components are defined based on the MetisPostAction encapsulated in the MetisPostDTO (message payload); diff --git a/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.spec.ts deleted file mode 100644 index c47567019a75..000000000000 --- a/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component.spec.ts +++ /dev/null @@ -1,378 +0,0 @@ -import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; -import { ConversationSelectionSidebarComponent } from 'app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-selection-sidebar.component'; -import { ConversationDTO } from 'app/entities/metis/conversation/conversation.model'; -import { Type } from '@angular/core'; -import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { NgbDropdownMocksModule } from '../../../../../helpers/mocks/directive/ngbDropdownMocks.module'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { MetisConversationService } from 'app/shared/metis/metis-conversation.service'; -import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; -import { AccountService } from 'app/core/auth/account.service'; -import { generateExampleChannelDTO, generateExampleGroupChatDTO, generateOneToOneChatDTO } from '../../helpers/conversationExampleModels'; -import { BehaviorSubject, EMPTY } from 'rxjs'; -import { By } from '@angular/platform-browser'; -import { ChannelDTO, ChannelSubType } from 'app/entities/metis/conversation/channel.model'; -import { ChannelsCreateDialogComponent } from 'app/overview/course-conversations/dialogs/channels-create-dialog/channels-create-dialog.component'; -import { defaultFirstLayerDialogOptions } from 'app/overview/course-conversations/other/conversation.util'; -import { UserPublicInfoDTO } from 'app/core/user/user.model'; -import { AbstractDialogComponent } from 'app/overview/course-conversations/dialogs/abstract-dialog.component'; -import { GroupChatCreateDialogComponent } from 'app/overview/course-conversations/dialogs/group-chat-create-dialog/group-chat-create-dialog.component'; -import { OneToOneChatCreateDialogComponent } from 'app/overview/course-conversations/dialogs/one-to-one-chat-create-dialog/one-to-one-chat-create-dialog.component'; -import { GroupChatDTO } from 'app/entities/metis/conversation/group-chat.model'; -import { ChannelsOverviewDialogComponent } from 'app/overview/course-conversations/dialogs/channels-overview-dialog/channels-overview-dialog.component'; -import { ConversationSidebarSectionComponent } from 'app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component'; -import { MockLocalStorageService } from '../../../../../helpers/mocks/service/mock-local-storage.service'; -import { LocalStorageService } from 'ngx-webstorage'; -import { NgbCollapseMocksModule } from '../../../../../helpers/mocks/directive/ngbCollapseMocks.module'; -import { ConversationSidebarEntryComponent } from 'app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component'; -import { TranslateService } from '@ngx-translate/core'; -import { GroupChatIconComponent } from 'app/overview/course-conversations/other/group-chat-icon/group-chat-icon.component'; -import { ChannelIconComponent } from 'app/overview/course-conversations/other/channel-icon/channel-icon.component'; -import { NgbTooltipMocksModule } from '../../../../../helpers/mocks/directive/ngbTooltipMocks.module'; -import { MetisService } from 'app/shared/metis/metis.service'; -import { CourseInformationSharingConfiguration } from 'app/entities/course.model'; -import { NotificationService } from 'app/shared/notification/notification.service'; - -const examples: (ConversationDTO | undefined)[] = [ - undefined, - generateOneToOneChatDTO({}), - generateExampleGroupChatDTO({}), - generateExampleChannelDTO({}), - generateExampleChannelDTO({ subType: ChannelSubType.EXERCISE }), - generateExampleChannelDTO({ subType: ChannelSubType.LECTURE }), - generateExampleChannelDTO({ subType: ChannelSubType.EXAM }), -]; - -examples.forEach((activeConversation) => { - describe( - 'ConversationSelectionSidebarComponent with ' + - (activeConversation instanceof ChannelDTO ? activeConversation.subType + ' ' : '') + - (activeConversation?.type || 'no active conversation'), - () => { - let component: ConversationSelectionSidebarComponent; - let fixture: ComponentFixture; - let metisConversationService: MetisConversationService; - const course = { id: 1, courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_AND_MESSAGING } as any; - const canCreateChannel = jest.fn(); - let allConversations: ConversationDTO[] = []; - - const visibleGroupChat = generateExampleGroupChatDTO({ id: 3 }); - const favoriteGroupChat = generateExampleGroupChatDTO({ id: 4, isFavorite: true }); - const mutedGroupChat = generateExampleGroupChatDTO({ id: 101, isMuted: true }); - const hiddenGroupChat = generateExampleGroupChatDTO({ id: 2, isHidden: true }); - - const visibleChannel = generateExampleChannelDTO({ id: 5 }); - const favoriteChannel = generateExampleChannelDTO({ id: 7, isFavorite: true }); - const mutedChannel = generateExampleChannelDTO({ id: 102, isMuted: true }); - const hiddenChannel = generateExampleChannelDTO({ id: 6, isHidden: true }); - - const visibleExerciseChannel = generateExampleChannelDTO({ id: 8, subType: ChannelSubType.EXERCISE }); - const favoriteExerciseChannel = generateExampleChannelDTO({ id: 10, isFavorite: true, subType: ChannelSubType.EXERCISE }); - const mutedExerciseChannel = generateExampleChannelDTO({ id: 103, isMuted: true, subType: ChannelSubType.EXERCISE }); - const hiddenExerciseChannel = generateExampleChannelDTO({ id: 9, isHidden: true, subType: ChannelSubType.EXERCISE }); - - const visibleLectureChannel = generateExampleChannelDTO({ id: 11, subType: ChannelSubType.LECTURE }); - const favoriteLectureChannel = generateExampleChannelDTO({ id: 13, isFavorite: true, subType: ChannelSubType.LECTURE }); - const mutedLectureChannel = generateExampleChannelDTO({ id: 104, isMuted: true, subType: ChannelSubType.LECTURE }); - const hiddenLectureChannel = generateExampleChannelDTO({ id: 12, isHidden: true, subType: ChannelSubType.LECTURE }); - - const visibleExamChannel = generateExampleChannelDTO({ id: 14, subType: ChannelSubType.EXAM }); - const hiddenExamChannel = generateExampleChannelDTO({ id: 15, isHidden: true, subType: ChannelSubType.EXAM }); - const mutedExamChannel = generateExampleChannelDTO({ id: 105, isMuted: true, subType: ChannelSubType.EXAM }); - const favoriteExamChannel = generateExampleChannelDTO({ id: 16, isFavorite: true, subType: ChannelSubType.EXAM }); - - const visibleOneToOneChat = generateOneToOneChatDTO({ id: 17 }); - const favoriteOneToOneChat = generateOneToOneChatDTO({ id: 19, isFavorite: true }); - const mutedOneToOneChat = generateOneToOneChatDTO({ id: 106, isMuted: true }); - const hiddenOneToOneChat = generateOneToOneChatDTO({ id: 18, isHidden: true }); - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [NgbDropdownMocksModule, NgbCollapseMocksModule, NgbTooltipMocksModule], - declarations: [ - ConversationSelectionSidebarComponent, - ConversationSidebarSectionComponent, - ConversationSidebarEntryComponent, - MockComponent(FaIconComponent), - MockPipe(ArtemisTranslatePipe), - MockComponent(GroupChatIconComponent), - MockComponent(ChannelIconComponent), - ], - providers: [ - MockProvider(TranslateService), - MockProvider(NgbModal), - MockProvider(MetisConversationService), - MockProvider(NotificationService), - MockProvider(AccountService), - MockProvider(MetisService), - { provide: LocalStorageService, useClass: MockLocalStorageService }, - MockProvider(ConversationService, { - getConversationName: (conversation: ConversationDTO) => { - return conversation.id + ''; - }, - }), - ], - }).compileComponents(); - })); - - beforeEach(() => { - allConversations = [ - visibleChannel, - favoriteChannel, - mutedChannel, - hiddenChannel, - - visibleExerciseChannel, - favoriteExerciseChannel, - mutedExerciseChannel, - hiddenExerciseChannel, - - visibleLectureChannel, - favoriteLectureChannel, - mutedLectureChannel, - hiddenLectureChannel, - - visibleExamChannel, - favoriteExamChannel, - mutedExamChannel, - hiddenExamChannel, - - visibleGroupChat, - favoriteGroupChat, - mutedGroupChat, - hiddenGroupChat, - - visibleOneToOneChat, - favoriteOneToOneChat, - mutedOneToOneChat, - hiddenOneToOneChat, - ]; - - canCreateChannel.mockReturnValue(true); - metisConversationService = TestBed.inject(MetisConversationService); - Object.defineProperty(metisConversationService, 'course', { get: () => course }); - Object.defineProperty(metisConversationService, 'activeConversation$', { get: () => new BehaviorSubject(activeConversation).asObservable() }); - Object.defineProperty(metisConversationService, 'conversationsOfUser$', { - get: () => new BehaviorSubject(allConversations).asObservable(), - }); - Object.defineProperty(metisConversationService, 'forceRefresh', { value: () => EMPTY }); - Object.defineProperty(metisConversationService, 'setActiveConversation', { value: () => {} }); - - fixture = TestBed.createComponent(ConversationSelectionSidebarComponent); - component = fixture.componentInstance; - component.canCreateChannel = canCreateChannel; - }); - - it('should create', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - expect(component).toBeTruthy(); - })); - - it('should set properties correctly', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - expect(component.course).toEqual(course); - expect(component.activeConversation).toEqual(activeConversation); - allConversations.forEach((conversation) => { - expect(component.allConversations).toContain(conversation); - }); - - expect(component.starredConversations).toContain(favoriteChannel); - expect(component.starredConversations).toContain(favoriteGroupChat); - expect(component.starredConversations).toContain(favoriteOneToOneChat); - expect(component.starredConversations).toHaveLength(6); - expect(component.starredConversations).toEqual(component.displayedStarredConversations); - - expect(component.channelConversations).toContain(visibleChannel); - expect(component.channelConversations).toContain(mutedChannel); - expect(component.channelConversations).toContain(hiddenChannel); - expect(component.channelConversations).toHaveLength(12); - expect(component.channelConversations).toEqual(component.displayedChannelConversations); - - expect(component.groupChats).toContain(visibleGroupChat); - expect(component.groupChats).toContain(mutedGroupChat); - expect(component.groupChats).toContain(hiddenGroupChat); - expect(component.groupChats).toHaveLength(3); - expect(component.groupChats).toEqual(component.displayedGroupChats); - - expect(component.oneToOneChats).toContain(visibleOneToOneChat); - expect(component.oneToOneChats).toContain(mutedOneToOneChat); - expect(component.oneToOneChats).toContain(hiddenOneToOneChat); - expect(component.oneToOneChats).toHaveLength(3); - expect(component.oneToOneChats).toEqual(component.displayedOneToOneChats); - })); - - it('should filter conversations correctly when search term is entered', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - const inputField = fixture.debugElement.query(By.css('input')); - inputField.nativeElement.value = visibleGroupChat.id + ''; - inputField.nativeElement.dispatchEvent(new Event('input')); - tick(301); - expect(component.searchTerm).toEqual(visibleGroupChat.id + ''); - expect(component.displayedStarredConversations).toHaveLength(1); - expect(component.displayedChannelConversations).toHaveLength(1); - expect(component.displayedGroupChats).toHaveLength(1); - expect(component.displayedGroupChats).toContain(visibleGroupChat); - expect(component.displayedOneToOneChats).toHaveLength(0); - })); - - it('should not show create channel button if user is missing the permission', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - canCreateChannel.mockReturnValue(false); - fixture.detectChanges(); - expect(fixture.debugElement.nativeElement.querySelector('#createChannel')).toBeFalsy(); - })); - - it('should open create channel dialog when button is pressed', fakeAsync(() => { - const createChannelSpy = jest.fn().mockReturnValue(EMPTY); - Object.defineProperty(metisConversationService, 'createChannel', { value: createChannelSpy }); - createConversationDialogTest(new ChannelDTO(), ChannelsCreateDialogComponent, 'createChannel'); - fixture.whenStable().then(() => { - expect(createChannelSpy).toHaveBeenCalledOnce(); - }); - })); - - it('should open create group chat dialog when button is pressed', fakeAsync(() => { - const createGroupChatSpy = jest.fn().mockReturnValue(EMPTY); - Object.defineProperty(metisConversationService, 'createGroupChat', { value: createGroupChatSpy }); - createConversationDialogTest([new UserPublicInfoDTO()], GroupChatCreateDialogComponent, 'createGroupChat'); - fixture.whenStable().then(() => { - expect(createGroupChatSpy).toHaveBeenCalledOnce(); - }); - })); - - it('should open one to one chat dialog when button is pressed', fakeAsync(() => { - const createOneToOneChatSpy = jest.fn().mockReturnValue(EMPTY); - Object.defineProperty(metisConversationService, 'createOneToOneChat', { value: createOneToOneChatSpy }); - const chatPartner = new UserPublicInfoDTO(); - chatPartner.login = 'test'; - createConversationDialogTest(chatPartner, OneToOneChatCreateDialogComponent, 'createOneToOne'); - fixture.whenStable().then(() => { - expect(createOneToOneChatSpy).toHaveBeenCalledOnce(); - }); - })); - - it('should open channel overview dialog when button is pressed', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - const modalService = TestBed.inject(NgbModal); - const mockModalRef = { - componentInstance: { - course: undefined, - createChannelFn: undefined, - initialize: () => {}, - }, - result: Promise.resolve([new GroupChatDTO(), true]), - }; - const openDialogSpy = jest.spyOn(modalService, 'open').mockReturnValue(mockModalRef as unknown as NgbModalRef); - fixture.detectChanges(); - - const dialogOpenButton = fixture.debugElement.nativeElement.querySelector('#channelOverview'); - dialogOpenButton.click(); - tick(301); - fixture.whenStable().then(() => { - expect(openDialogSpy).toHaveBeenCalledOnce(); - expect(openDialogSpy).toHaveBeenCalledWith(ChannelsOverviewDialogComponent, defaultFirstLayerDialogOptions); - expect(mockModalRef.componentInstance.course).toEqual(course); - }); - })); - - it('should refresh when settings are changed', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - - const forceRefreshMock = jest.spyOn(metisConversationService, 'forceRefresh').mockReturnValue(EMPTY); - component.onSettingsDidChange(); - expect(forceRefreshMock).toHaveBeenCalledOnce(); - })); - - it('should run conversations update when favorite status is changed', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - const onConversationsUpdateSpy = jest.spyOn(component, 'onConversationsUpdate'); - component.updateConversations(); - tick(301); - expect(onConversationsUpdateSpy).toHaveBeenCalledOnce(); - expect(onConversationsUpdateSpy).toHaveBeenCalledWith(component.allConversations); - })); - - it('should run conversations update when hidden status is changed', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - const onConversationsUpdateSpy = jest.spyOn(component, 'onConversationsUpdate'); - component.updateConversations(); - tick(301); - expect(onConversationsUpdateSpy).toHaveBeenCalledOnce(); - expect(onConversationsUpdateSpy).toHaveBeenCalledWith(component.allConversations); - })); - - it('should run conversations update when muted status is changed', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - const onConversationsUpdateSpy = jest.spyOn(component, 'onConversationsUpdate'); - component.updateConversations(); - tick(301); - expect(onConversationsUpdateSpy).toHaveBeenCalledOnce(); - expect(onConversationsUpdateSpy).toHaveBeenCalledWith(component.allConversations); - })); - - it('should open code of conduct', () => { - const metisSpy = jest.spyOn(metisConversationService, 'setCodeOfConduct'); - component.openCodeOfConduct(); - expect(metisSpy).toHaveBeenCalledOnce(); - }); - - it('should hide buttons if messaging disabled', fakeAsync(() => { - fixture.detectChanges(); - tick(301); - component.course = { id: 1, courseInformationSharingConfiguration: CourseInformationSharingConfiguration.COMMUNICATION_ONLY } as any; - component.isMessagingEnabled = false; - fixture.detectChanges(); - tick(301); - - const channelButton = fixture.debugElement.query(By.css('#channelButton')); - const exerciseChannelButton = fixture.debugElement.query(By.css('#exerciseChannelButton')); - const lectureChannelButton = fixture.debugElement.query(By.css('#lectureChannelButton')); - const examChannelButton = fixture.debugElement.query(By.css('#examChannelButton')); - const groupChatButton = fixture.debugElement.query(By.css('#createGroupChat')); - const oneToOneChatButton = fixture.debugElement.query(By.css('#createOneToOne')); - - expect(channelButton).toBeNull(); - expect(exerciseChannelButton).toBeNull(); - expect(lectureChannelButton).toBeNull(); - expect(examChannelButton).toBeNull(); - expect(groupChatButton).toBeNull(); - expect(oneToOneChatButton).toBeNull(); - })); - - function createConversationDialogTest(modalReturnValue: any, dialog: Type, buttonId: string) { - fixture.detectChanges(); - tick(301); - const modalService = TestBed.inject(NgbModal); - const mockModalRef = { - componentInstance: { - course: undefined, - initialize: () => {}, - }, - result: Promise.resolve(modalReturnValue), - }; - const openDialogSpy = jest.spyOn(modalService, 'open').mockReturnValue(mockModalRef as unknown as NgbModalRef); - fixture.detectChanges(); - - const dialogOpenButton = fixture.debugElement.nativeElement.querySelector('#' + buttonId); - dialogOpenButton.click(); - tick(301); - fixture.whenStable().then(() => { - expect(openDialogSpy).toHaveBeenCalledOnce(); - expect(openDialogSpy).toHaveBeenCalledWith(dialog, defaultFirstLayerDialogOptions); - expect(mockModalRef.componentInstance.course).toEqual(course); - }); - } - }, - ); -}); diff --git a/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.spec.ts deleted file mode 100644 index bb0b84a8475b..000000000000 --- a/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component.spec.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; -import { Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; -import { ConversationSidebarEntryComponent } from 'app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-entry/conversation-sidebar-entry.component'; -import { NgbDropdownMocksModule } from '../../../../../../../helpers/mocks/directive/ngbDropdownMocks.module'; -import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { ConversationDTO } from 'app/entities/metis/conversation/conversation.model'; -import { generateExampleChannelDTO, generateExampleGroupChatDTO, generateOneToOneChatDTO } from '../../../../helpers/conversationExampleModels'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { AlertService } from 'app/core/util/alert.service'; -import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; -import { HttpResponse } from '@angular/common/http'; -import { of } from 'rxjs'; -import { ChannelIconComponent } from 'app/overview/course-conversations/other/channel-icon/channel-icon.component'; -import { GroupChatIconComponent } from 'app/overview/course-conversations/other/group-chat-icon/group-chat-icon.component'; -import { - ConversationDetailDialogComponent, - ConversationDetailTabs, -} from 'app/overview/course-conversations/dialogs/conversation-detail-dialog/conversation-detail-dialog.component'; -import { defaultFirstLayerDialogOptions } from 'app/overview/course-conversations/other/conversation.util'; -import { isOneToOneChatDTO } from 'app/entities/metis/conversation/one-to-one-chat.model'; -import { ChannelDTO, ChannelSubType } from 'app/entities/metis/conversation/channel.model'; -import { MetisService } from 'app/shared/metis/metis.service'; -import { MockMetisService } from '../../../../../../../helpers/mocks/service/mock-metis-service.service'; -import { CourseLectureDetailsComponent } from 'app/overview/course-lectures/course-lecture-details.component'; -import { ExamDetailComponent } from 'app/exam/manage/exams/exam-detail.component'; -import { CourseExerciseDetailsComponent } from 'app/overview/exercise-details/course-exercise-details.component'; -import { NotificationService } from 'app/shared/notification/notification.service'; -import { MockNotificationService } from '../../../../../../../helpers/mocks/service/mock-notification.service'; -import { Course } from 'app/entities/course.model'; - -const examples: (() => ConversationDTO)[] = [ - () => generateOneToOneChatDTO({}), - () => generateExampleGroupChatDTO({}), - () => generateExampleChannelDTO({}), - () => generateExampleChannelDTO({ subType: ChannelSubType.EXERCISE, subTypeReferenceId: 1 }), - () => generateExampleChannelDTO({ subType: ChannelSubType.LECTURE, subTypeReferenceId: 1 }), - () => generateExampleChannelDTO({ subType: ChannelSubType.EXAM, subTypeReferenceId: 1 }), -]; - -const configureTestBed = () => { - TestBed.configureTestingModule({ - imports: [ - NgbDropdownMocksModule, - RouterTestingModule.withRoutes([ - { path: 'courses/:courseId/lectures/:lectureId', component: CourseLectureDetailsComponent }, - { path: 'courses/:courseId/exercises/:exerciseId', component: CourseExerciseDetailsComponent }, - { path: 'courses/:courseId/exams/:examId', component: ExamDetailComponent }, - ]), - ], - declarations: [ - ConversationSidebarEntryComponent, - MockPipe(ArtemisTranslatePipe), - MockComponent(FaIconComponent), - MockComponent(ChannelIconComponent), - MockComponent(GroupChatIconComponent), - ], - providers: [ - MockProvider(ConversationService), - MockProvider(AlertService), - MockProvider(NgbModal), - { provide: MetisService, useClass: MockMetisService }, - { provide: NotificationService, useClass: MockNotificationService }, - ], - }).compileComponents(); -}; - -examples.forEach((conversation) => { - const testDescription = conversation(); - - describe('ConversationSidebarEntryComponent with ' + (testDescription instanceof ChannelDTO ? testDescription.subType + ' ' : '') + testDescription.type, () => { - let component: ConversationSidebarEntryComponent; - let fixture: ComponentFixture; - let conversationService: ConversationService; - let updateIsFavoriteSpy: jest.SpyInstance; - let updateIsHiddenSpy: jest.SpyInstance; - let updateIsMutedSpy: jest.SpyInstance; - let location: Location; - let notificationService: NotificationService; - const course = { id: 1 } as any; - const activeConversation = generateExampleGroupChatDTO({ id: 99 }); - - beforeEach(waitForAsync(configureTestBed)); - - beforeEach(() => { - fixture = TestBed.createComponent(ConversationSidebarEntryComponent); - conversationService = TestBed.inject(ConversationService); - updateIsFavoriteSpy = jest.spyOn(conversationService, 'updateIsFavorite').mockReturnValue(of(new HttpResponse())); - updateIsHiddenSpy = jest.spyOn(conversationService, 'updateIsHidden').mockReturnValue(of(new HttpResponse())); - updateIsMutedSpy = jest.spyOn(conversationService, 'updateIsMuted').mockReturnValue(of(new HttpResponse())); - - location = TestBed.inject(Location); - notificationService = TestBed.inject(NotificationService); - - component = fixture.componentInstance; - component.conversation = conversation(); - component.activeConversation = activeConversation; - component.course = course; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should call updateIsFavorite when button is clicked', fakeAsync(() => { - const conversationFavoriteStatusChangeSpy = jest.spyOn(component.conversationIsFavoriteDidChange, 'emit'); - const button = fixture.debugElement.nativeElement.querySelector('.favorite'); - button.click(); - tick(501); - expect(updateIsFavoriteSpy).toHaveBeenCalledOnce(); - expect(updateIsFavoriteSpy).toHaveBeenCalledWith(course.id, component.conversation.id, true); - expect(conversationFavoriteStatusChangeSpy).toHaveBeenCalledOnce(); - })); - - it('should call updateIsHidden when button is clicked', fakeAsync(() => { - const conversationIsHiddenDidChangeSpy = jest.spyOn(component.conversationIsHiddenDidChange, 'emit'); - const button = fixture.debugElement.nativeElement.querySelector('.hide'); - button.click(); - tick(501); - expect(updateIsHiddenSpy).toHaveBeenCalledOnce(); - expect(updateIsHiddenSpy).toHaveBeenCalledWith(course.id, component.conversation.id, true); - expect(conversationIsHiddenDidChangeSpy).toHaveBeenCalledOnce(); - })); - - it('should call updateIsMuted when button is clicked', fakeAsync(() => { - const conversationIsMutedDidChangeSpy = jest.spyOn(component.conversationIsMutedDidChange, 'emit'); - const button = fixture.debugElement.nativeElement.querySelector('.mute'); - button.click(); - tick(501); - expect(updateIsMutedSpy).toHaveBeenCalledOnce(); - expect(updateIsMutedSpy).toHaveBeenCalledWith(course.id, component.conversation.id, true); - expect(conversationIsMutedDidChangeSpy).toHaveBeenCalledOnce(); - })); - - it('should update the notification service`s muted conversations', fakeAsync(() => { - const button = fixture.debugElement.nativeElement.querySelector('.hide'); - const muteNotificationsForConversationSpy = jest.spyOn(notificationService, 'muteNotificationsForConversation').mockReturnValue(); - button.click(); - tick(501); - expect(muteNotificationsForConversationSpy).toHaveBeenCalledOnce(); - const unmuteNotificationsForConversationSpy = jest.spyOn(notificationService, 'unmuteNotificationsForConversation').mockReturnValue(); - button.click(); - tick(501); - expect(unmuteNotificationsForConversationSpy).toHaveBeenCalledOnce(); - })); - - it('should open conversation detail with setting tab if setting button is clicked', fakeAsync(() => { - if (isOneToOneChatDTO(component.conversation)) { - const button = fixture.debugElement.nativeElement.querySelector('.setting'); - expect(button).toBeFalsy(); // should not be present for one-to-one chats - } else { - const button = fixture.debugElement.nativeElement.querySelector('.setting'); - const modalService = TestBed.inject(NgbModal); - const mockModalRef = { - componentInstance: { - course: undefined, - activeConversation, - selectedTab: undefined, - initialize: () => {}, - }, - result: Promise.resolve(), - }; - const openDialogSpy = jest.spyOn(modalService, 'open').mockReturnValue(mockModalRef as unknown as NgbModalRef); - button.click(); - fixture.whenStable().then(() => { - expect(openDialogSpy).toHaveBeenCalledOnce(); - expect(openDialogSpy).toHaveBeenCalledWith(ConversationDetailDialogComponent, defaultFirstLayerDialogOptions); - expect(mockModalRef.componentInstance.course).toEqual(course); - expect(mockModalRef.componentInstance.activeConversation).toEqual(component.conversation); - expect(mockModalRef.componentInstance.selectedTab).toEqual(ConversationDetailTabs.SETTINGS); - }); - } - })); - - if (testDescription instanceof ChannelDTO && testDescription.subType !== ChannelSubType.GENERAL) { - it( - 'should navigate to ' + testDescription.subType, - fakeAsync(() => { - const button = fixture.debugElement.nativeElement.querySelector('.sub-type-reference'); - button.click(); - tick(); - - // Assert that the router has navigated to the correct link - expect(location.path()).toBe('/courses/1/' + testDescription.subType + 's/1'); - }), - ); - } - }); -}); - -describe('ConversationSidebarEntryComponent without conversation.id', () => { - let component: ConversationSidebarEntryComponent; - let fixture: ComponentFixture; - let conversationService: ConversationService; - let updateIsFavoriteSpy: jest.SpyInstance; - let updateIsHiddenSpy: jest.SpyInstance; - let updateIsMutedSpy: jest.SpyInstance; - - beforeEach(waitForAsync(configureTestBed)); - - beforeEach(() => { - fixture = TestBed.createComponent(ConversationSidebarEntryComponent); - conversationService = TestBed.inject(ConversationService); - updateIsFavoriteSpy = jest.spyOn(conversationService, 'updateIsFavorite').mockReturnValue(of(new HttpResponse())); - updateIsHiddenSpy = jest.spyOn(conversationService, 'updateIsHidden').mockReturnValue(of(new HttpResponse())); - updateIsMutedSpy = jest.spyOn(conversationService, 'updateIsMuted').mockReturnValue(of(new HttpResponse())); - - component = fixture.componentInstance; - component.conversation = new ChannelDTO(); - component.activeConversation = component.conversation; - component.course = new Course(); - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should not call updateIsFavorite when button is clicked', fakeAsync(() => { - const button = fixture.debugElement.nativeElement.querySelector('.favorite'); - button.click(); - tick(501); - expect(updateIsFavoriteSpy).not.toHaveBeenCalled(); - })); - - it('should not call updateIsHidden when button is clicked', fakeAsync(() => { - const button = fixture.debugElement.nativeElement.querySelector('.hide'); - button.click(); - tick(501); - expect(updateIsHiddenSpy).not.toHaveBeenCalled(); - })); - - it('should not call updateIsMuted when button is clicked', fakeAsync(() => { - const button = fixture.debugElement.nativeElement.querySelector('.mute'); - button.click(); - tick(501); - expect(updateIsMutedSpy).not.toHaveBeenCalled(); - })); -}); diff --git a/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.spec.ts b/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.spec.ts deleted file mode 100644 index 6102e2077ac6..000000000000 --- a/src/test/javascript/spec/component/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component.spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { Course } from 'app/entities/course.model'; -import { ConversationDTO } from 'app/entities/metis/conversation/conversation.model'; -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { generateExampleChannelDTO, generateExampleGroupChatDTO, generateOneToOneChatDTO } from '../../../helpers/conversationExampleModels'; -import { MockComponent, MockPipe, MockProvider } from 'ng-mocks'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { NgbCollapseMocksModule } from '../../../../../../helpers/mocks/directive/ngbCollapseMocks.module'; -import { MockLocalStorageService } from '../../../../../../helpers/mocks/service/mock-local-storage.service'; -import { ConversationService } from 'app/shared/metis/conversations/conversation.service'; -import { ConversationSidebarSectionComponent } from 'app/overview/course-conversations/layout/conversation-selection-sidebar/conversation-sidebar-section/conversation-sidebar-section.component'; -import { LocalStorageService } from 'ngx-webstorage'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; - -@Component({ - // eslint-disable-next-line @angular-eslint/component-selector - selector: '[jhi-conversation-sidebar-entry]', - template: '', -}) -class ConversationSidebarEntryStubComponent { - @Input() - course: Course; - - @Input() - conversation: ConversationDTO; - - @Input() - activeConversation: ConversationDTO | undefined; - - @Output() - settingsDidChange = new EventEmitter(); - - @Output() - conversationIsFavoriteDidChange = new EventEmitter(); - - @Output() - conversationIsHiddenDidChange = new EventEmitter(); - - @Output() - conversationIsMutedDidChange = new EventEmitter(); -} - -const examples: (ConversationDTO | undefined)[] = [undefined, generateOneToOneChatDTO({}), generateExampleGroupChatDTO({}), generateExampleChannelDTO({})]; -examples.forEach((activeConversation) => { - describe('ConversationSidebarSectionComponent with ' + (activeConversation?.type || 'no active conversation'), () => { - let component: ConversationSidebarSectionComponent; - let fixture: ComponentFixture; - const course = { id: 1 } as Course; - - const visibleConversation = generateExampleChannelDTO({ id: 2, isHidden: false }); - const mutedConversation = generateExampleChannelDTO({ id: 3, unreadMessagesCount: 1, isMuted: true }); - const hiddenConversation = generateExampleChannelDTO({ id: 4, unreadMessagesCount: 1, isHidden: true }); - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [NgbCollapseMocksModule], - declarations: [ConversationSidebarSectionComponent, ConversationSidebarEntryStubComponent, MockComponent(FaIconComponent), MockPipe(ArtemisTranslatePipe)], - providers: [{ provide: LocalStorageService, useClass: MockLocalStorageService }, MockProvider(ConversationService)], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ConversationSidebarSectionComponent); - component = fixture.componentInstance; - component.course = course; - component.activeConversation = activeConversation; - component.label = 'label'; - component.headerKey = 'headerKey'; - component.conversations = [hiddenConversation, mutedConversation, visibleConversation]; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should separate hidden, muted, and visible conversations', () => { - expect(component.visibleConversations).toEqual([visibleConversation]); - expect(component.mutedConversations).toEqual([mutedConversation]); - expect(component.hiddenConversations).toEqual([hiddenConversation]); - expect(component.allConversations).toEqual([hiddenConversation, mutedConversation, visibleConversation]); - expect(component.numberOfConversations).toBe(3); - }); - - it('should store collapsed status in local storage', () => { - expect(component.localStorageService.retrieve(component.storageKey)).toBeFalse(); - component.toggleCollapsed(); - expect(component.localStorageService.retrieve(component.storageKey)).toBeTrue(); - component.toggleCollapsed(); - }); - - it('should display a conversation is unread', () => { - component.toggleCollapsed(); - expect(component.anyConversationUnread).toBeTrue(); - expect(component.anyHiddenConversationUnread).toBeTrue(); - component.toggleCollapsed(); - }); - - it('should hide if empty only if hideIfEmpty is set', () => { - component.allConversations = []; - expect(component.hide()).toBeTrue(); - - component.hideIfEmpty = false; - expect(component.hide()).toBeFalse(); - }); - - it('should hide if search term is entered and conversations are empty and ignore hideIfEmpty flag', () => { - component.allConversations = []; - component.hideIfEmpty = false; - - component.searchTerm = 'test'; - expect(component.hide()).toBeTrue(); - - component.searchTerm = ''; - expect(component.hide()).toBeFalse(); - }); - }); -}); diff --git a/src/test/javascript/spec/service/metis/metis.service.spec.ts b/src/test/javascript/spec/service/metis/metis.service.spec.ts index 98aaaedf1e01..54ba1f1d5db5 100644 --- a/src/test/javascript/spec/service/metis/metis.service.spec.ts +++ b/src/test/javascript/spec/service/metis/metis.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { Post } from 'app/entities/metis/post.model'; -import { Course, CourseInformationSharingConfiguration } from 'app/entities/course.model'; +import { Course } from 'app/entities/course.model'; import { MockPostService } from '../../helpers/mocks/service/mock-post.service'; import { MockAnswerPostService } from '../../helpers/mocks/service/mock-answer-post.service'; import { MetisService } from 'app/shared/metis/metis.service'; @@ -182,23 +182,11 @@ describe('Metis Service', () => { postsSub.unsubscribe(); })); - it('should update post tags', () => { - const postServiceSpy = jest.spyOn(postService, 'getAllPostTagsByCourseId'); - metisService.updateCoursePostTags(); - expect(postServiceSpy).toHaveBeenCalledOnce(); - }); - it('should get posts for course', () => { const postServiceSpy = jest.spyOn(postService, 'getPosts'); metisService.getFilteredPosts({ courseId: course.id }); expect(postServiceSpy).toHaveBeenCalledOnce(); }); - - it('should get similar posts within course', () => { - const postServiceSpy = jest.spyOn(postService, 'computeSimilarityScoresWithCoursePosts'); - metisService.getSimilarPosts(post); - expect(postServiceSpy).toHaveBeenCalledOnce(); - }); }); describe('Invoke answer post service methods', () => { @@ -307,15 +295,6 @@ describe('Metis Service', () => { expect(metisUserIsAuthorOfPostingReturn).toBeFalse(); }); - it('should not fetch course post tags if communication is not enabled', () => { - const updateCoursePostTagsSpy = jest.spyOn(metisService, 'updateCoursePostTags'); - course.courseInformationSharingConfiguration = CourseInformationSharingConfiguration.MESSAGING_ONLY; - metisService.setCourse(course); - const getCourseReturn = metisService.getCourse(); - expect(getCourseReturn).toEqual(course); - expect(updateCoursePostTagsSpy).not.toHaveBeenCalled(); - }); - it('should set course when current course has different id', () => { metisService.setCourse(course); const newCourse = new Course();