Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grading: Scroll down to complaint form after clicking on a complain button #9762

Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
}
</p>
}

@if (isCorrectUserToFileAction && !complaint) {
<div class="mt-4">
@if (isExamMode || (course?.maxComplaints && course!.maxComplaints! > 0)) {
<button
id="complain"
class="btn btn-primary"
[class.not-allowed]="(!isExamMode && remainingNumberOfComplaints === 0) || !timeOfComplaintValid"
(click)="formComplaintType = ComplaintType.COMPLAINT"
(click)="startComplaint(ComplaintType.COMPLAINT)"
[disabled]="(!isExamMode && remainingNumberOfComplaints === 0) || !timeOfComplaintValid"
title="{{
(!isExamMode && remainingNumberOfComplaints === 0) || !timeOfComplaintValid
Expand All @@ -37,14 +38,15 @@
<button
class="btn btn-primary ms-1"
[class.not-allowed]="!timeOfFeedbackRequestValid"
(click)="formComplaintType = ComplaintType.MORE_FEEDBACK"
(click)="startComplaint(ComplaintType.MORE_FEEDBACK)"
[disabled]="!timeOfFeedbackRequestValid"
title="{{ !timeOfFeedbackRequestValid ? ('artemisApp.moreFeedback.notAllowedTooltip' | artemisTranslate) : '' }}"
jhiTranslate="artemisApp.moreFeedback.button"
></button>
}
</div>
}
<div #complaintScrollpoint></div>
@if (!complaint && formComplaintType) {
<div class="row">
<jhi-complaint-form
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { 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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -149,4 +151,20 @@ export class ComplaintsStudentViewComponent implements OnInit {
}
return false;
}

/**
* Opens the complaint form (for a given complaint type) for the student to submit a complaint and scrolls to the bottom of the page to make the form visible
*/
startComplaint(complainType: ComplaintType): void {
AjayvirS marked this conversation as resolved.
Show resolved Hide resolved
this.formComplaintType = complainType;
// Wait for the view to update
setTimeout(() => this.scrollToComplaint(), 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: for a more "Angular like" approach I would suggest using this.cdr.detectChanges(); (where cdr is ChangeDetectorRef injected through the constructor) instead of setTimeout

}

/**
* Scrolls to the complaint form
*/
private scrollToComplaint(): void {
this.complaintScrollpoint.nativeElement.scrollIntoView({ behavior: 'smooth' });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the element referred here is rendered conditionally, (see resp. html line 20), a suggestion would be to add the "?" construct to check whether nativeElement (or complaintScrollpoint) really exists to prevent runtime errors.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -146,6 +148,22 @@ describe('ComplaintsStudentViewComponent', () => {
expect(userMock).toHaveBeenCalledOnce();
}));

it('should open complaint form and focus on complaint form when pressing complaint', fakeAsync(() => {
// Mock complaintScrollpoint
const scrollIntoViewMock = jest.fn();
component.complaintScrollpoint = {
nativeElement: {
scrollIntoView: scrollIntoViewMock,
},
} as ElementRef;

component.startComplaint(ComplaintType.COMPLAINT);
expect(component.formComplaintType).toBe(ComplaintType.COMPLAINT);
// Wait for setTimeout to execute
tick();
expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth' });
}));

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;
Expand Down Expand Up @@ -204,6 +222,38 @@ describe('ComplaintsStudentViewComponent', () => {
expect(component.complaint).toStrictEqual(complaint);
}));

it('should open complaint form and focus on complaint form when pressing complaint', fakeAsync(() => {
// Mock complaintScrollpoint
const scrollIntoViewMock = jest.fn();
component.complaintScrollpoint = {
nativeElement: {
scrollIntoView: scrollIntoViewMock,
},
} as ElementRef;

component.startComplaint(ComplaintType.COMPLAINT);
expect(component.formComplaintType).toBe(ComplaintType.COMPLAINT);
// Wait for setTimeout to execute
tick();
expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth' });
}));

it('should open complaint form and focus on complaint form when pressing more feedback', fakeAsync(() => {
// Mock complaintScrollpoint
const scrollIntoViewMock = jest.fn();
component.complaintScrollpoint = {
nativeElement: {
scrollIntoView: scrollIntoViewMock,
},
} as ElementRef;

component.startComplaint(ComplaintType.MORE_FEEDBACK);
expect(component.formComplaintType).toBe(ComplaintType.MORE_FEEDBACK);
// Wait for setTimeout to execute
tick();
expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth' });
}));

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;
Expand Down