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 8a703757ecfc..05258758872f 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 @@ -1,12 +1,21 @@ -
+
@if (posting.isSaved) { -
+
} + @if (isConsecutive()) { + + {{ posting.creationDate | artemisDate: 'time' }} + + } @if (!isConsecutive()) {
} @if (!createAnswerPostModal.isInputOpen) { -
+
-
+
} -
+
@if (!isDeleted) { -
+
+
@if (posting.isSaved) {
@@ -7,6 +10,11 @@
} + @if (isConsecutive()) { + + {{ posting.creationDate | artemisDate: 'time' }} + + } @if (!isConsecutive()) {
implements timeDiff = currentDate.diff(lastDate, 'minute'); } - if (currentPost.author?.id === currentGroup.author?.id && timeDiff < 1 && timeDiff >= 0) { + if (currentPost.author?.id === currentGroup.author?.id && timeDiff < 5 && timeDiff >= 0) { currentGroup.posts.push({ ...currentPost, isConsecutive: true }); // consecutive post } else { groups.push(currentGroup); 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 a03fb20cced3..f7d42ac92f61 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 @@ -31,7 +31,11 @@ , } - {{ postingIsOfToday ? (posting.creationDate | artemisDate: 'time') : (posting.creationDate | artemisDate: 'short-date') }} + {{ + postingIsOfToday + ? (posting.creationDate | artemisDate: 'time') + : (posting.creationDate | artemisDate: 'short-date') + ' - ' + (posting.creationDate | artemisDate: 'time') + }} @if (!!isCommunicationPage && (!lastReadDate || (lastReadDate && posting.creationDate && posting.creationDate.isAfter(lastReadDate))) && !isAuthorOfPosting) { 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 1bce282adaed..deadcb377cdd 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 @@ -31,7 +31,11 @@ , } - {{ postingIsOfToday ? (posting.creationDate | artemisDate: 'time') : (posting.creationDate | artemisDate: 'short-date') }} + {{ + postingIsOfToday + ? (posting.creationDate | artemisDate: 'time') + : (posting.creationDate | artemisDate: 'short-date') + ' - ' + (posting.creationDate | artemisDate: 'time') + }} @if (posting.resolved) { 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 3a84c4f12e87..45badc3c912d 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 @@ -1,13 +1,13 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AnswerPostComponent } from 'app/shared/metis/answer-post/answer-post.component'; import { DebugElement, input, runInInjectionContext } from '@angular/core'; -import { MockComponent, MockModule, MockPipe, ngMocks } from 'ng-mocks'; +import { MockComponent, MockDirective, MockModule, MockPipe, ngMocks } from 'ng-mocks'; import { HtmlForMarkdownPipe } from 'app/shared/pipes/html-for-markdown.pipe'; import { By } from '@angular/platform-browser'; import { AnswerPostHeaderComponent } from 'app/shared/metis/posting-header/answer-post-header/answer-post-header.component'; import { AnswerPostReactionsBarComponent } from 'app/shared/metis/posting-reactions-bar/answer-post-reactions-bar/answer-post-reactions-bar.component'; import { PostingContentComponent } from 'app/shared/metis/posting-content/posting-content.components'; -import { metisResolvingAnswerPostUser1 } from '../../../../helpers/sample/metis-sample-data'; +import { metisPostExerciseUser1, metisResolvingAnswerPostUser1 } from '../../../../helpers/sample/metis-sample-data'; import { OverlayModule } from '@angular/cdk/overlay'; import { AnswerPostCreateEditModalComponent } from 'app/shared/metis/posting-create-edit-modal/answer-post-create-edit-modal/answer-post-create-edit-modal.component'; import { DOCUMENT } from '@angular/common'; @@ -17,6 +17,13 @@ import { MetisService } from 'app/shared/metis/metis.service'; import { MockMetisService } from '../../../../helpers/mocks/service/mock-metis-service.service'; 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 { MockTranslateService } from '../../../../helpers/mocks/service/mock-translate.service'; +import { TranslateService } from '@ngx-translate/core'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; describe('AnswerPostComponent', () => { let component: AnswerPostComponent; @@ -30,7 +37,7 @@ describe('AnswerPostComponent', () => { document.body.appendChild(mainContainer); return TestBed.configureTestingModule({ - imports: [OverlayModule, MockModule(BrowserAnimationsModule)], + imports: [OverlayModule, MockModule(BrowserAnimationsModule), MockDirective(NgbTooltip)], declarations: [ AnswerPostComponent, MockPipe(HtmlForMarkdownPipe), @@ -38,10 +45,14 @@ describe('AnswerPostComponent', () => { MockComponent(PostingContentComponent), MockComponent(AnswerPostCreateEditModalComponent), MockComponent(AnswerPostReactionsBarComponent), + ArtemisDatePipe, + ArtemisTranslatePipe, + MockDirective(TranslateDirective), ], providers: [ { provide: DOCUMENT, useValue: document }, { provide: MetisService, useClass: MockMetisService }, + { provide: TranslateService, useClass: MockTranslateService }, ], }) .compileComponents() @@ -237,4 +248,31 @@ describe('AnswerPostComponent', () => { expect(component.posting).toBeInstanceOf(AnswerPost); expect(spy).toHaveBeenCalled(); }); + + it('should display post-time span when isConsecutive() returns true', () => { + const fixedDate = dayjs('2024-12-06T23:39:27.080Z'); + component.posting = { ...metisPostExerciseUser1, creationDate: fixedDate }; + + jest.spyOn(component, 'isConsecutive').mockReturnValue(true); + fixture.detectChanges(); + + const postTimeDebugElement = debugElement.query(By.css('span.post-time')); + const postTimeElement = postTimeDebugElement.nativeElement as HTMLElement; + + expect(postTimeDebugElement).toBeTruthy(); + + const expectedTime = dayjs(fixedDate).format('HH:mm'); + expect(postTimeElement.textContent?.trim()).toBe(expectedTime); + }); + + it('should not display post-time span when isConsecutive() returns false', () => { + const fixedDate = dayjs('2024-12-06T23:39:27.080Z'); + component.posting = { ...metisPostExerciseUser1, creationDate: fixedDate }; + + jest.spyOn(component, 'isConsecutive').mockReturnValue(false); + fixture.detectChanges(); + + const postTimeElement = debugElement.query(By.css('span.post-time')); + expect(postTimeElement).toBeFalsy(); + }); }); 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 6c0859326aaa..3f37a29a0d23 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 @@ -11,7 +11,7 @@ import { MockMetisService } from '../../../../helpers/mocks/service/mock-metis-s import { MetisService } from 'app/shared/metis/metis.service'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { DisplayPriority, PageType } from 'app/shared/metis/metis.util'; -import { TranslatePipeMock } from '../../../../helpers/mocks/service/mock-translate.service'; +import { MockTranslateService, TranslatePipeMock } from '../../../../helpers/mocks/service/mock-translate.service'; import { OverlayModule } from '@angular/cdk/overlay'; import { metisChannel, @@ -38,6 +38,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DOCUMENT } from '@angular/common'; import { Posting, PostingType } from 'app/entities/metis/posting.model'; import { Post } from 'app/entities/metis/post.model'; +import { ArtemisTranslatePipe } from '../../../../../../../main/webapp/app/shared/pipes/artemis-translate.pipe'; +import { ArtemisDatePipe } from '../../../../../../../main/webapp/app/shared/pipes/artemis-date.pipe'; +import { TranslateDirective } from '../../../../../../../main/webapp/app/shared/language/translate.directive'; +import { TranslateService } from '@ngx-translate/core'; +import { By } from '@angular/platform-browser'; +import dayjs from 'dayjs/esm'; describe('PostComponent', () => { let component: PostComponent; @@ -64,6 +70,7 @@ describe('PostComponent', () => { { provide: DOCUMENT, useValue: document }, MockProvider(MetisConversationService), MockProvider(OneToOneChatService), + { provide: TranslateService, useClass: MockTranslateService }, ], declarations: [ PostComponent, @@ -77,6 +84,9 @@ describe('PostComponent', () => { MockRouterLinkDirective, MockQueryParamsDirective, TranslatePipeMock, + ArtemisDatePipe, + ArtemisTranslatePipe, + MockDirective(TranslateDirective), ], }) .compileComponents() @@ -380,4 +390,31 @@ describe('PostComponent', () => { expect(component.posting).toBeInstanceOf(Post); expect(spy).toHaveBeenCalled(); }); + + it('should display post-time span when isConsecutive() returns true', () => { + const fixedDate = dayjs('2024-12-06T23:39:27.080Z'); + component.posting = { ...metisPostExerciseUser1, creationDate: fixedDate }; + + jest.spyOn(component, 'isConsecutive').mockReturnValue(true); + fixture.detectChanges(); + + const postTimeDebugElement = debugElement.query(By.css('span.post-time')); + const postTimeElement = postTimeDebugElement.nativeElement as HTMLElement; + + expect(postTimeDebugElement).toBeTruthy(); + + const expectedTime = dayjs(fixedDate).format('HH:mm'); + expect(postTimeElement.textContent?.trim()).toBe(expectedTime); + }); + + it('should not display post-time span when isConsecutive() returns false', () => { + const fixedDate = dayjs('2024-12-06T23:39:27.080Z'); + component.posting = { ...metisPostExerciseUser1, creationDate: fixedDate }; + + jest.spyOn(component, 'isConsecutive').mockReturnValue(false); + fixture.detectChanges(); + + const postTimeElement = debugElement.query(By.css('span.post-time')); + expect(postTimeElement).toBeFalsy(); + }); }); diff --git a/src/test/javascript/spec/component/shared/metis/postings-footer/post-footer/post-footer.component.spec.ts b/src/test/javascript/spec/component/shared/metis/postings-footer/post-footer/post-footer.component.spec.ts index 8071b5a84a95..c7975f26849d 100644 --- a/src/test/javascript/spec/component/shared/metis/postings-footer/post-footer/post-footer.component.spec.ts +++ b/src/test/javascript/spec/component/shared/metis/postings-footer/post-footer/post-footer.component.spec.ts @@ -17,6 +17,7 @@ import { MockMetisService } from '../../../../../helpers/mocks/service/mock-meti import { metisPostExerciseUser1, post, unsortedAnswerArray } from '../../../../../helpers/sample/metis-sample-data'; import { AnswerPost } from 'app/entities/metis/answer-post.model'; import { User } from 'app/core/user/user.model'; +import dayjs from 'dayjs/esm'; interface PostGroup { author: User | undefined; @@ -72,8 +73,8 @@ describe('PostFooterComponent', () => { it('should group answer posts correctly', () => { component.sortedAnswerPosts = unsortedAnswerArray; component.groupAnswerPosts(); - expect(component.groupedAnswerPosts.length).toBeGreaterThan(0); // Ensure groups are created - expect(component.groupedAnswerPosts[0].posts.length).toBeGreaterThan(0); // Ensure posts exist in groups + expect(component.groupedAnswerPosts.length).toBeGreaterThan(0); + expect(component.groupedAnswerPosts[0].posts.length).toBeGreaterThan(0); }); it('should group answer posts and detect changes on changes to sortedAnswerPosts input', () => { @@ -160,4 +161,39 @@ describe('PostFooterComponent', () => { component.closeCreateAnswerPostModal(); expect(createAnswerPostModalClose).toHaveBeenCalledOnce(); }); + + it('should group answer posts correctly based on author and time difference', () => { + const authorA: User = { id: 1, login: 'authorA' } as User; + const authorB: User = { id: 2, login: 'authorB' } as User; + + const baseTime = dayjs(); + + const post1: AnswerPost = { id: 1, author: authorA, creationDate: baseTime.toDate() } as unknown as AnswerPost; + const post2: AnswerPost = { id: 2, author: authorA, creationDate: baseTime.add(3, 'minute').toDate() } as unknown as AnswerPost; + const post3: AnswerPost = { id: 3, author: authorA, creationDate: baseTime.add(10, 'minute').toDate() } as unknown as AnswerPost; + const post4: AnswerPost = { id: 4, author: authorB, creationDate: baseTime.add(12, 'minute').toDate() } as unknown as AnswerPost; + const post5: AnswerPost = { id: 5, author: authorB, creationDate: baseTime.add(14, 'minute').toDate() } as unknown as AnswerPost; + + component.sortedAnswerPosts = [post3, post1, post5, post2, post4]; + + component.groupAnswerPosts(); + expect(component.groupedAnswerPosts).toHaveLength(3); + + const group1 = component.groupedAnswerPosts[0]; + expect(group1.author).toEqual(authorA); + expect(group1.posts).toHaveLength(2); + expect(group1.posts).toContainEqual(expect.objectContaining({ id: post1.id })); + expect(group1.posts).toContainEqual(expect.objectContaining({ id: post2.id })); + + const group2 = component.groupedAnswerPosts[1]; + expect(group2.author).toEqual(authorA); + expect(group2.posts).toHaveLength(1); + expect(group2.posts).toContainEqual(expect.objectContaining({ id: post3.id })); + + const group3 = component.groupedAnswerPosts[2]; + expect(group3.author).toEqual(authorB); + expect(group3.posts).toHaveLength(2); + expect(group3.posts).toContainEqual(expect.objectContaining({ id: post4.id })); + expect(group3.posts).toContainEqual(expect.objectContaining({ id: post5.id })); + }); });