diff --git a/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.html b/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.html
index ae902f4ec8de..783d822a2e84 100644
--- a/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.html
+++ b/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.html
@@ -16,6 +16,7 @@
}
}
+
@if (isCorrectUserToFileAction && !complaint) {
@if (isExamMode || (course?.maxComplaints && course!.maxComplaints! > 0)) {
@@ -23,7 +24,7 @@
id="complain"
class="btn btn-primary"
[class.not-allowed]="(!isExamMode && remainingNumberOfComplaints === 0) || !timeOfComplaintValid"
- (click)="formComplaintType = ComplaintType.COMPLAINT"
+ (click)="openComplaintForm(ComplaintType.COMPLAINT)"
[disabled]="(!isExamMode && remainingNumberOfComplaints === 0) || !timeOfComplaintValid"
title="{{
(!isExamMode && remainingNumberOfComplaints === 0) || !timeOfComplaintValid
@@ -35,9 +36,10 @@
}
@if (!isExamMode && course?.requestMoreFeedbackEnabled) {
}
+
@if (complaint) {
diff --git a/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.ts b/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.ts
index b469dcab432f..26d9308a110e 100644
--- a/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.ts
+++ b/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { Exercise, getCourseFromExercise } from 'app/entities/exercise.model';
import { Complaint, ComplaintType } from 'app/entities/complaint.model';
import { ComplaintService } from 'app/complaints/complaint.service';
@@ -28,6 +28,8 @@ export class ComplaintsStudentViewComponent implements OnInit {
// flag to indicate exam test run. Default set to false.
@Input() testRun = false;
+ @ViewChild('complaintScrollpoint') complaintScrollpoint: ElementRef;
+
submission: Submission;
complaint: Complaint;
course?: Course;
@@ -51,6 +53,7 @@ export class ComplaintsStudentViewComponent implements OnInit {
private serverDateService: ArtemisServerDateService,
private accountService: AccountService,
private courseService: CourseManagementService,
+ private cdr: ChangeDetectorRef,
) {}
/**
@@ -149,4 +152,20 @@ export class ComplaintsStudentViewComponent implements OnInit {
}
return false;
}
+
+ /**
+ * Function to set complaint type (which opens the complaint form) and scrolls to the complaint form
+ */
+ openComplaintForm(complainType: ComplaintType): void {
+ this.formComplaintType = complainType;
+ this.cdr.detectChanges(); // Wait for the view to update
+ this.scrollToComplaint();
+ }
+
+ /**
+ * Function to scroll to the complaint form
+ */
+ private scrollToComplaint(): void {
+ this.complaintScrollpoint?.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
+ }
}
diff --git a/src/test/javascript/spec/component/complaints/complaint-student-view.component.spec.ts b/src/test/javascript/spec/component/complaints/complaint-student-view.component.spec.ts
index 528e9d6d5c6a..91172f9c6516 100644
--- a/src/test/javascript/spec/component/complaints/complaint-student-view.component.spec.ts
+++ b/src/test/javascript/spec/component/complaints/complaint-student-view.component.spec.ts
@@ -25,6 +25,8 @@ import { AssessmentType } from 'app/entities/assessment-type.model';
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { CourseManagementService } from 'app/course/manage/course-management.service';
import { MockCourseManagementService } from '../../helpers/mocks/service/mock-course-management.service';
+import { ElementRef } from '@angular/core';
+import { ComplaintType } from 'app/entities/complaint.model';
describe('ComplaintsStudentViewComponent', () => {
const complaintTimeLimitDays = 7;
@@ -146,6 +148,39 @@ describe('ComplaintsStudentViewComponent', () => {
expect(userMock).toHaveBeenCalledOnce();
}));
+ it('should set complaint type COMPLAINT and scroll to complaint form when pressing complaint', fakeAsync(() => {
+ component.exercise = examExercise;
+ component.result = result;
+ component.exam = defaultExam;
+ component.showSection = true;
+ component.isCorrectUserToFileAction = true;
+ const complaintBySubmissionMock = jest.spyOn(complaintService, 'findBySubmissionId').mockReturnValue(of());
+
+ fixture.detectChanges();
+
+ //Check if button is available
+ expect(component.complaint).toBeUndefined();
+ expect(complaintBySubmissionMock).toHaveBeenCalledOnce();
+
+ // Mock complaint scrollpoint
+ const scrollIntoViewMock = jest.fn();
+ component.complaintScrollpoint = {
+ nativeElement: {
+ scrollIntoView: scrollIntoViewMock,
+ },
+ } as ElementRef;
+
+ const button = fixture.debugElement.nativeElement.querySelector('#complain');
+ button.click();
+
+ fixture.detectChanges();
+
+ expect(component.formComplaintType).toBe(ComplaintType.COMPLAINT);
+ // Wait for setTimeout to execute
+ tick();
+ expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth', block: 'end' });
+ }));
+
it('should be visible on test run', fakeAsync(() => {
const now = dayjs();
const examWithFutureReview: Exam = { examStudentReviewStart: dayjs(now).add(1, 'day'), examStudentReviewEnd: dayjs(now).add(2, 'day') } as Exam;
@@ -204,6 +239,71 @@ describe('ComplaintsStudentViewComponent', () => {
expect(component.complaint).toStrictEqual(complaint);
}));
+ it('should set complaint type COMPLAINT and scroll to complaint form when pressing complaint', fakeAsync(() => {
+ testInitWithResultStub(of());
+ const courseWithMaxComplaints: Course = {
+ ...course,
+ maxComplaints: 3,
+ };
+ const exerciseWithMaxComplaints: Exercise = {
+ ...courseExercise,
+ course: courseWithMaxComplaints,
+ };
+ component.course = courseWithMaxComplaints;
+ component.exercise = exerciseWithMaxComplaints;
+
+ component.showSection = true;
+ component.isCorrectUserToFileAction = true;
+ component.remainingNumberOfComplaints = 1;
+
+ fixture.detectChanges();
+
+ // Mock complaint scrollpoint
+ const scrollIntoViewMock = jest.fn();
+ component.complaintScrollpoint = {
+ nativeElement: {
+ scrollIntoView: scrollIntoViewMock,
+ },
+ } as ElementRef;
+
+ const button = fixture.debugElement.nativeElement.querySelector('#complain');
+ button.click();
+
+ fixture.detectChanges();
+
+ expect(component.formComplaintType).toBe(ComplaintType.COMPLAINT);
+ tick(); // Wait for update to happen
+ expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth', block: 'end' });
+ }));
+
+ it('should set complaint type MORE_FEEDBACK and scroll to complaint form when pressing complaint', fakeAsync(() => {
+ testInitWithResultStub(of());
+ component.showSection = true;
+ component.isCorrectUserToFileAction = true;
+
+ fixture.detectChanges();
+
+ //Check if button is available
+ expect(component.complaint).toBeUndefined();
+
+ // Mock complaint scrollpoint
+ const scrollIntoViewMock = jest.fn();
+ component.complaintScrollpoint = {
+ nativeElement: {
+ scrollIntoView: scrollIntoViewMock,
+ },
+ } as ElementRef;
+
+ const button = fixture.debugElement.nativeElement.querySelector('#more-feedback');
+ button.click();
+
+ fixture.detectChanges();
+
+ expect(component.formComplaintType).toBe(ComplaintType.MORE_FEEDBACK);
+ tick(); // Wait for update to happen
+ expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth', block: 'end' });
+ }));
+
it('should not be available if before or at assessment due date', fakeAsync(() => {
const exercise: Exercise = { id: 1, teamMode: false, course, assessmentDueDate: dayjs() } as Exercise;
const resultMatchingDate: Result = { id: 1, completionDate: dayjs(exercise.assessmentDueDate) } as Result;