-
-
-
+
+
+
+
- @if (processUnitMode) {
-
- }
- @if (processUnitMode) {
-
-
-
-
-
- @if (!fileName && fileInputTouched) {
-
- }
+ @if (isEditMode()) {
+
}
-
-
-
-
+
+ @if (isEditMode()) {
+
+
+
+
+
+
- @if (processUnitMode) {
-
-
-
-
+
+ }
+ @if (processUnitMode) {
+
+ }
+ @if (processUnitMode) {
+
-
- }
+ }
+
+ @if (processUnitMode) {
+
+
+
+
+
+ }
+
+
diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts
index d4653758c979..092e1348c536 100644
--- a/src/main/webapp/app/lecture/lecture-update.component.ts
+++ b/src/main/webapp/app/lecture/lecture-update.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, OnInit, ViewChild, effect, inject, signal, viewChild } from '@angular/core';
+import { Component, OnDestroy, OnInit, computed, effect, inject, model, signal, viewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { Observable, Subscription } from 'rxjs';
@@ -9,8 +9,7 @@ import { Course } from 'app/entities/course.model';
import { onError } from 'app/shared/util/global.utils';
import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils';
import { DocumentationType } from 'app/shared/components/documentation-button/documentation-button.component';
-import { faBan, faHandshakeAngle, faPuzzlePiece, faQuestionCircle, faSave } from '@fortawesome/free-solid-svg-icons';
-import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component';
+import { faBan, faPuzzlePiece, faQuestionCircle, faSave } from '@fortawesome/free-solid-svg-icons';
import { ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER, ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE } from 'app/shared/constants/file-extensions.constants';
import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action';
import { LectureTitleChannelNameComponent } from './lecture-title-channel-name.component';
@@ -18,6 +17,9 @@ import { LectureUpdatePeriodComponent } from 'app/lecture/lecture-period/lecture
import dayjs, { Dayjs } from 'dayjs/esm';
import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component';
import cloneDeep from 'lodash-es/cloneDeep';
+import { FormSectionStatus, FormStatusBarComponent } from 'app/forms/form-status-bar/form-status-bar.component';
+import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component';
+import { LectureUpdateUnitsComponent } from 'app/lecture/lecture-units/lecture-units.component';
@Component({
selector: 'jhi-lecture-update',
@@ -30,28 +32,31 @@ export class LectureUpdateComponent implements OnInit, OnDestroy {
protected readonly faSave = faSave;
protected readonly faPuzzleProcess = faPuzzlePiece;
protected readonly faBan = faBan;
- protected readonly faHandShakeAngle = faHandshakeAngle;
protected readonly allowedFileExtensions = ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE;
protected readonly acceptedFileExtensionsFileBrowser = ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER;
- @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent;
-
private readonly alertService = inject(AlertService);
private readonly lectureService = inject(LectureService);
private readonly activatedRoute = inject(ActivatedRoute);
private readonly navigationUtilService = inject(ArtemisNavigationUtilService);
private readonly router = inject(Router);
- titleSection = viewChild(LectureTitleChannelNameComponent);
- lecturePeriodSection = viewChild(LectureUpdatePeriodComponent);
+ titleSection = viewChild.required(LectureTitleChannelNameComponent);
+ lecturePeriodSection = viewChild.required(LectureUpdatePeriodComponent);
+ attachmentsSection = viewChild(LectureAttachmentsComponent);
+ unitSection = viewChild(LectureUpdateUnitsComponent);
+ formStatusBar = viewChild(FormStatusBarComponent);
+ courseTitle = model
('');
lecture = signal(new Lecture());
lectureOnInit: Lecture;
+ isEditMode = signal(false);
isSaving: boolean;
isProcessing: boolean;
processUnitMode: boolean;
- isShowingWizardMode: boolean;
+
+ formStatusSections: FormSectionStatus[];
courses: Course[];
@@ -60,12 +65,20 @@ export class LectureUpdateComponent implements OnInit, OnDestroy {
fileName: string;
fileInputTouched = false;
- toggleModeFunction = () => this.toggleWizardMode();
- saveLectureFunction = () => this.save();
+ isNewlyCreatedExercise = false;
isChangeMadeToTitleOrPeriodSection = false;
shouldDisplayDismissWarning = true;
+ areSectionsValid = computed(() => {
+ return (
+ this.titleSection().titleChannelNameComponent().isFormValidSignal() &&
+ this.lecturePeriodSection().isPeriodSectionValid() &&
+ (this.unitSection()?.isUnitConfigurationValid() ?? true) &&
+ (this.attachmentsSection()?.isFormValid() ?? true)
+ );
+ });
+
private subscriptions = new Subscription();
constructor() {
@@ -96,13 +109,25 @@ export class LectureUpdateComponent implements OnInit, OnDestroy {
);
}
});
+
+ effect(() => {
+ this.updateFormStatusBar();
+ });
+
+ effect(
+ function scrollToLastSectionAfterLectureCreation() {
+ if (this.unitSection() && this.isNewlyCreatedExercise) {
+ this.isNewlyCreatedExercise = false;
+ this.formStatusBar()?.scrollToHeadline('artemisApp.lecture.sections.period');
+ }
+ }.bind(this),
+ );
}
ngOnInit() {
this.isSaving = false;
this.processUnitMode = false;
this.isProcessing = false;
- this.isShowingWizardMode = false;
this.activatedRoute.parent!.data.subscribe((data) => {
// Create a new lecture to use unless we fetch an existing lecture
const lecture = data['lecture'];
@@ -113,19 +138,45 @@ export class LectureUpdateComponent implements OnInit, OnDestroy {
}
});
- this.activatedRoute.queryParams.subscribe((params) => {
- if (params.shouldBeInWizardMode) {
- this.isShowingWizardMode = params.shouldBeInWizardMode;
- }
- });
-
+ this.isEditMode.set(!this.router.url.endsWith('/new'));
this.lectureOnInit = cloneDeep(this.lecture());
+ this.courseTitle.set(this.lecture().course?.title ?? '');
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
+ updateFormStatusBar() {
+ const updatedFormStatusSections: FormSectionStatus[] = [];
+
+ updatedFormStatusSections.push(
+ {
+ title: 'artemisApp.lecture.sections.title',
+ valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()),
+ },
+ {
+ title: 'artemisApp.lecture.sections.period',
+ valid: Boolean(this.lecturePeriodSection().isPeriodSectionValid()),
+ },
+ );
+
+ if (this.isEditMode()) {
+ updatedFormStatusSections.push(
+ {
+ title: 'artemisApp.lecture.sections.attachments',
+ valid: Boolean(this.attachmentsSection()?.isFormValid()),
+ },
+ {
+ title: 'artemisApp.lecture.sections.units',
+ valid: Boolean(this.unitSection()?.isUnitConfigurationValid()),
+ },
+ );
+ }
+
+ this.formStatusSections = updatedFormStatusSections;
+ }
+
isChangeMadeToTitleSection() {
return (
this.lecture().title !== this.lectureOnInit.title ||
@@ -182,14 +233,6 @@ export class LectureUpdateComponent implements OnInit, OnDestroy {
}
}
- /**
- * Activate or deactivate the wizard mode for easier lecture creation.
- * This function is called by pressing "Switch to guided mode" when creating a new lecture
- */
- toggleWizardMode() {
- this.isShowingWizardMode = !this.isShowingWizardMode;
- }
-
proceedToUnitSplit() {
this.save();
}
@@ -227,25 +270,30 @@ export class LectureUpdateComponent implements OnInit, OnDestroy {
* Action on successful lecture creation or edit
*/
protected onSaveSuccess(lecture: Lecture) {
- if (this.isShowingWizardMode && !this.lecture().id) {
- this.lectureService.findWithDetails(lecture.id!).subscribe({
- next: (response: HttpResponse) => {
- this.isSaving = false;
- this.lecture.set(response.body!);
- this.alertService.success(`Lecture with title ${lecture.title} was successfully created.`);
- this.wizardComponent.onLectureCreationSucceeded();
- },
- });
- } else if (this.processUnitMode) {
- this.isSaving = false;
+ this.isSaving = false;
+
+ if (!lecture.course?.id) {
+ console.error('Lecture has no course id', lecture);
+ return;
+ }
+
+ if (this.processUnitMode) {
this.isProcessing = false;
this.alertService.success(`Lecture with title ${lecture.title} was successfully ${this.lecture().id !== undefined ? 'updated' : 'created'}.`);
- this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id, 'unit-management', 'attachment-units', 'process'], {
+ this.router.navigate(['course-management', lecture.course.id, 'lectures', lecture.id, 'unit-management', 'attachment-units', 'process'], {
state: { file: this.file, fileName: this.fileName },
});
+ } else if (this.isEditMode()) {
+ this.router.navigate(['course-management', lecture.course.id, 'lectures', lecture.id]);
} else {
- this.isSaving = false;
- this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]);
+ // after create we stay on the edit page, as now attachments and lecture units are available (we need the lecture id to save them)
+ this.isNewlyCreatedExercise = true;
+ this.isEditMode.set(true);
+ this.lectureOnInit = cloneDeep(lecture);
+ this.lecture.set(lecture);
+ this.updateIsChangesMadeToTitleOrPeriodSection();
+ window.history.replaceState({}, '', `course-management/${lecture.course.id}/lectures/${lecture.id}/edit`);
+ this.shouldDisplayDismissWarning = true;
}
}
diff --git a/src/main/webapp/app/lecture/lecture.component.ts b/src/main/webapp/app/lecture/lecture.component.ts
index 19c4dc417eb1..7bbe6d1b18b3 100644
--- a/src/main/webapp/app/lecture/lecture.component.ts
+++ b/src/main/webapp/app/lecture/lecture.component.ts
@@ -124,6 +124,12 @@ export class LectureComponent implements OnInit, OnDestroy {
);
}
+ private deleteLectureFromDisplayedLectures(lectureId: number) {
+ this.dialogErrorSource.next('');
+ this.lectures = this.lectures.filter((lecture) => lecture.id !== lectureId);
+ this.applyFilters();
+ }
+
/**
* Deletes Lecture
* @param lectureId the id of the lecture
@@ -131,9 +137,7 @@ export class LectureComponent implements OnInit, OnDestroy {
deleteLecture(lectureId: number) {
this.lectureService.delete(lectureId).subscribe({
next: () => {
- this.dialogErrorSource.next('');
- this.lectures = this.lectures.filter((lecture) => lecture.id !== lectureId);
- this.applyFilters();
+ this.deleteLectureFromDisplayedLectures(lectureId);
},
error: (error: HttpErrorResponse) => this.dialogErrorSource.next(error.message),
});
diff --git a/src/main/webapp/app/lecture/lecture.module.ts b/src/main/webapp/app/lecture/lecture.module.ts
index db1380abd69d..83afd92a0e56 100644
--- a/src/main/webapp/app/lecture/lecture.module.ts
+++ b/src/main/webapp/app/lecture/lecture.module.ts
@@ -1,12 +1,10 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
-
import { ArtemisSharedModule } from 'app/shared/shared.module';
import { FormDateTimePickerModule } from 'app/shared/date-time-picker/date-time-picker.module';
import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module';
import { ArtemisMarkdownEditorModule } from 'app/shared/markdown-editor/markdown-editor.module';
import { LectureUpdateComponent } from 'app/lecture/lecture-update.component';
-import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component';
import { LectureComponent } from 'app/lecture/lecture.component';
import { LectureDetailComponent } from 'app/lecture/lecture-detail.component';
import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component';
@@ -15,14 +13,12 @@ import { ArtemisLectureUnitManagementModule } from 'app/lecture/lecture-unit/lec
import { ArtemisMarkdownModule } from 'app/shared/markdown.module';
import { LectureImportComponent } from 'app/lecture/lecture-import.component';
import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module';
-import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component';
-import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component';
-import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component';
-import { LectureUpdateWizardStepComponent } from 'app/lecture/wizard-mode/lecture-update-wizard-step.component';
import { TitleChannelNameModule } from 'app/shared/form/title-channel-name/title-channel-name.module';
import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component';
import { DetailModule } from 'app/detail-overview-list/detail.module';
import { CompetencyFormComponent } from 'app/course/competencies/forms/competency/competency-form.component';
+import { FormsModule } from 'app/forms/forms.module';
+import { LectureUpdateUnitsComponent } from 'app/lecture/lecture-units/lecture-units.component';
import { LectureUpdatePeriodComponent } from 'app/lecture/lecture-period/lecture-period.component';
const ENTITY_STATES = [...lectureRoute];
@@ -40,20 +36,17 @@ const ENTITY_STATES = [...lectureRoute];
TitleChannelNameModule,
DetailModule,
CompetencyFormComponent,
+ FormsModule,
],
declarations: [
LectureComponent,
LectureDetailComponent,
LectureImportComponent,
LectureUpdateComponent,
- LectureUpdateWizardComponent,
LectureAttachmentsComponent,
- LectureUpdateWizardTitleComponent,
- LectureUpdatePeriodComponent,
- LectureUpdateWizardAttachmentsComponent,
- LectureUpdateWizardUnitsComponent,
- LectureUpdateWizardStepComponent,
LectureTitleChannelNameComponent,
+ LectureUpdateUnitsComponent,
+ LectureUpdatePeriodComponent,
],
})
export class ArtemisLectureModule {}
diff --git a/src/main/webapp/app/lecture/lecture.service.ts b/src/main/webapp/app/lecture/lecture.service.ts
index e6b508e6695a..118ef77172d8 100644
--- a/src/main/webapp/app/lecture/lecture.service.ts
+++ b/src/main/webapp/app/lecture/lecture.service.ts
@@ -112,6 +112,7 @@ export class LectureService {
tap((res: EntityArrayResponseType) => res?.body?.forEach(this.sendTitlesToEntityTitleService.bind(this))),
);
}
+
/**
* triggers the ingestion of All the lectures inside the course specified or one lecture inside of the course
*
@@ -128,6 +129,7 @@ export class LectureService {
observe: 'response',
});
}
+
/**
* Fetch the ingestion state of all the lectures inside the course specified
* @param courseId
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.html
deleted file mode 100644
index fdaee7117471..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- Current step
- @if (descriptionTranslationKey) {
-
- }
-
-
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.ts
deleted file mode 100644
index 9c8d6973a233..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Component, Input } from '@angular/core';
-import { faCheck, faDotCircle } from '@fortawesome/free-solid-svg-icons';
-
-@Component({
- selector: 'jhi-lecture-update-wizard-step',
- templateUrl: './lecture-update-wizard-step.component.html',
- styleUrls: ['./lecture-update-wizard.component.scss'],
-})
-export class LectureUpdateWizardStepComponent {
- @Input()
- currentlySelected: boolean;
-
- @Input()
- isPerformed: boolean;
-
- @Input()
- labelTranslationKey: string;
-
- @Input()
- descriptionTranslationKey: string;
-
- protected readonly faCheck = faCheck;
- protected readonly faDotCircle = faDotCircle;
-}
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html
deleted file mode 100644
index 1e228e1c0747..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html
+++ /dev/null
@@ -1,54 +0,0 @@
-
- @if (currentStep >= LECTURE_UPDATE_WIZARD_TITLE_STEP) {
-
- }
- @if (currentStep >= LECTURE_UPDATE_WIZARD_PERIOD_STEP) {
-
- }
- @if (currentStep >= LECTURE_UPDATE_WIZARD_ATTACHMENT_STEP) {
-
- }
- @if (currentStep >= LECTURE_UPDATE_WIZARD_UNIT_STEP) {
-
- }
-
-
-
-
-
-
-
-
LECTURE_UPDATE_WIZARD_TITLE_STEP"
- [labelTranslationKey]="'artemisApp.lecture.wizardMode.steps.titleStepTitle'"
- />
- LECTURE_UPDATE_WIZARD_TITLE_STEP ? 'check' : 'unset'">
- LECTURE_UPDATE_WIZARD_PERIOD_STEP"
- [labelTranslationKey]="'artemisApp.lecture.wizardMode.steps.periodStepTitle'"
- />
- LECTURE_UPDATE_WIZARD_PERIOD_STEP ? 'check' : 'unset'">
- LECTURE_UPDATE_WIZARD_ATTACHMENT_STEP"
- [labelTranslationKey]="'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle'"
- />
- LECTURE_UPDATE_WIZARD_ATTACHMENT_STEP ? 'check' : 'unset'">
- LECTURE_UPDATE_WIZARD_UNIT_STEP"
- [labelTranslationKey]="'artemisApp.lecture.wizardMode.steps.unitsStepTitle'"
- />
-
-
-
-
-
-
-
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.scss b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.scss
deleted file mode 100644
index 4c77040d9087..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.scss
+++ /dev/null
@@ -1,70 +0,0 @@
-.form-step + .form-step::before {
- content: '';
- display: block;
- height: 1px;
- background: gray;
- margin-bottom: 1rem;
-}
-
-.code-hint-generation-wrapper {
- .planned,
- .finished,
- .check {
- color: var(--success);
- }
-
- .unset {
- color: var(--secondary);
- }
-
- .current {
- color: var(--warning);
- }
-
- li {
- display: list-item !important;
- }
-
- .connector {
- width: 100%;
- min-width: 50px;
- border-top: 2px solid;
- margin-top: 1em;
- }
-}
-
-.status-step {
- width: 25px;
- overflow-x: visible;
- justify-content: flex-start;
- display: flex;
- flex-direction: column;
- align-items: center;
-
- .status-step-content {
- min-width: fit-content;
- white-space: nowrap;
-
- .selected-label {
- font-weight: bold;
- }
- }
-
- .header-icon {
- z-index: 2;
- font-size: 2em;
-
- & > .step-icon {
- cursor: pointer;
- background-color: var(--programming-exercise-instruction-step-wizard-card-header-background);
- width: 30px;
- height: 30px;
- text-align: center;
- padding: 6px 0;
- font-size: 12px;
- line-height: 1.428571429;
- border-radius: 15px;
- border-color: var(--programming-exercise-instruction-step-wizard-btn-border-color);
- }
- }
-}
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts
deleted file mode 100644
index d360ffc962ca..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import { Component, Input, OnInit, ViewChild } from '@angular/core';
-import { ActivatedRoute, Router } from '@angular/router';
-import { CourseManagementService } from 'app/course/manage/course-management.service';
-import { Lecture } from 'app/entities/lecture.model';
-import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils';
-import { faArrowRight, faCheck, faHandshakeAngle } from '@fortawesome/free-solid-svg-icons';
-import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component';
-import { take } from 'rxjs/operators';
-
-@Component({
- selector: 'jhi-lecture-update-wizard',
- templateUrl: './lecture-update-wizard.component.html',
- styleUrls: ['./lecture-update-wizard.component.scss'],
-})
-export class LectureUpdateWizardComponent implements OnInit {
- @Input() toggleModeFunction: () => void;
- @Input() saveLectureFunction: () => void;
- @Input() validateDatesFunction: () => void;
- @Input() lecture: Lecture;
- @Input() isSaving: boolean;
-
- @ViewChild(LectureUpdateWizardUnitsComponent, { static: false }) unitsComponent: LectureUpdateWizardUnitsComponent;
-
- readonly LECTURE_UPDATE_WIZARD_TITLE_STEP = 1;
- readonly LECTURE_UPDATE_WIZARD_PERIOD_STEP = 2;
- readonly LECTURE_UPDATE_WIZARD_ATTACHMENT_STEP = 3;
- readonly LECTURE_UPDATE_WIZARD_UNIT_STEP = 4;
-
- currentStep: number;
-
- // Icons
- faCheck = faCheck;
- faHandShakeAngle = faHandshakeAngle;
- faArrowRight = faArrowRight;
-
- constructor(
- protected courseService: CourseManagementService,
- protected activatedRoute: ActivatedRoute,
- private navigationUtilService: ArtemisNavigationUtilService,
- private router: Router,
- ) {}
-
- /**
- * Life cycle hook called by Angular to indicate that Angular is done creating the component
- */
- ngOnInit() {
- this.isSaving = false;
-
- this.activatedRoute.queryParams.pipe(take(1)).subscribe((params) => {
- if (params.step && !isNaN(+params.step)) {
- this.currentStep = +params.step;
- } else {
- if (this.lecture.id) {
- this.currentStep = this.LECTURE_UPDATE_WIZARD_UNIT_STEP;
- } else if (this.lecture.startDate === undefined && this.lecture.endDate === undefined) {
- this.currentStep = this.LECTURE_UPDATE_WIZARD_TITLE_STEP;
- } else if (!this.lecture.id) {
- this.currentStep = this.LECTURE_UPDATE_WIZARD_PERIOD_STEP;
- }
- }
-
- this.router.navigate([], {
- relativeTo: this.activatedRoute,
- queryParamsHandling: '',
- replaceUrl: true,
- });
- });
- }
-
- /**
- * Progress to the next step of the wizard mode
- */
- next() {
- if (this.currentStep === this.LECTURE_UPDATE_WIZARD_PERIOD_STEP || this.currentStep === this.LECTURE_UPDATE_WIZARD_UNIT_STEP) {
- this.saveLectureFunction();
- return;
- }
-
- this.currentStep++;
- }
-
- /**
- * Called when the lecture has been successfully created in the parent component to advance in the wizard
- */
- onLectureCreationSucceeded() {
- this.currentStep++;
- }
-
- getNextIcon() {
- return this.currentStep < this.LECTURE_UPDATE_WIZARD_UNIT_STEP ? faArrowRight : faCheck;
- }
-
- getNextText() {
- return this.currentStep < this.LECTURE_UPDATE_WIZARD_UNIT_STEP ? 'artemisApp.lecture.home.nextStepLabel' : 'entity.action.finish';
- }
-
- toggleWizardMode() {
- if (this.currentStep <= this.LECTURE_UPDATE_WIZARD_PERIOD_STEP) {
- this.toggleModeFunction();
- } else {
- this.router.navigate(['course-management', this.lecture.course!.id, 'lectures', this.lecture.id]);
- }
- }
-}
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.html
deleted file mode 100644
index 5b58c99bb28f..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.ts
deleted file mode 100644
index 867ad557e393..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Component, Input } from '@angular/core';
-import { Lecture } from 'app/entities/lecture.model';
-
-@Component({
- selector: 'jhi-lecture-update-wizard-attachments',
- templateUrl: './lecture-wizard-attachments.component.html',
-})
-export class LectureUpdateWizardAttachmentsComponent {
- @Input() currentStep: number;
- @Input() lecture: Lecture;
-
- constructor() {}
-}
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.html
deleted file mode 100644
index 9100d8436184..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts
deleted file mode 100644
index e354df72e629..000000000000
--- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Component, Input } from '@angular/core';
-import { Lecture } from 'app/entities/lecture.model';
-import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action';
-
-@Component({
- selector: 'jhi-lecture-update-wizard-title',
- templateUrl: './lecture-wizard-title.component.html',
-})
-export class LectureUpdateWizardTitleComponent {
- @Input() currentStep: number;
- @Input() lecture: Lecture;
-
- domainActionsDescription = [new FormulaAction()];
-}
diff --git a/src/main/webapp/app/shared/exercise-filter/exercise-filter-modal.component.html b/src/main/webapp/app/shared/exercise-filter/exercise-filter-modal.component.html
index 1a87237a4d46..218a925805ca 100644
--- a/src/main/webapp/app/shared/exercise-filter/exercise-filter-modal.component.html
+++ b/src/main/webapp/app/shared/exercise-filter/exercise-filter-modal.component.html
@@ -70,11 +70,6 @@
}
-
-
-
-
-
@if (difficultyFilter?.isDisplayed && difficultyFilter) {
diff --git a/src/main/webapp/i18n/de/lecture.json b/src/main/webapp/i18n/de/lecture.json
index 09357210a6cb..2415d32b6f19 100644
--- a/src/main/webapp/i18n/de/lecture.json
+++ b/src/main/webapp/i18n/de/lecture.json
@@ -4,6 +4,7 @@
"home": {
"title": "Vorlesungen",
"createLabel": "Vorlesung erstellen",
+ "editLabel": "Vorlesung bearbeiten",
"filterLabel": "Filter",
"filterOptions": {
"filterPast": "Vergangene",
@@ -12,9 +13,6 @@
"filterUnspecifiedDates": "Ohne Zeitangaben"
},
"createOrEditLabel": "Vorlesung erstellen oder bearbeiten",
- "switchToGuidedModeLabel": "Zum geführten Modus wechseln",
- "switchToTraditionalModeLabel": "Zum normalen Modus wechseln",
- "nextStepLabel": "Weiter",
"ingestLecturesInPyrisLabel": "Vorlesungen an Iris senden"
},
"created": "Vorlesung erstellt mit ID {{ param }}",
@@ -71,24 +69,18 @@
"loading": "Lade...",
"label": "Vorlesung importieren"
},
- "wizardMode": {
- "steps": {
- "titleStepTitle": "Titel",
- "titleStepMessage": "Gib einen Titel und eine aussagekräftige Beschreibung für die neue Vorlesung ein.",
- "periodStepTitle": "Zeitraum",
- "periodStepMessage": "Lege das Start- und Enddatum der Vorlesung fest.",
- "attachmentsStepTitle": "Anhänge",
- "attachmentsStepMessage": "Lade Anhänge für die Vorlesung hoch.",
- "unitsStepTitle": "Vorlesungseinheiten",
- "unitsStepMessage": "Füge Inhalte zur Vorlesung hinzu, indem du Vorlesungseinheiten erstellst."
- },
- "newLectureUnit": "Neue Vorlesungseinheit",
- "editLectureUnit": "Vorlesungseinheit bearbeiten",
- "newExercise": "Neue Übung erstellen",
- "competencyTitle": "Titel",
- "competencyConnectedUnits": "Verknüpfte Einheiten",
- "competencyNoConnectedUnits": "Keine verknüpften Einheiten"
+ "sections": {
+ "title": "Titel",
+ "titleDescription": "Gib einen Titel und eine aussagekräftige Beschreibung für die Vorlesung ein.",
+ "period": "Zeitraum",
+ "periodDescription": "Lege das Start- und Enddatum der Vorlesung fest.",
+ "attachments": "Anhänge",
+ "attachmentsDescription": "Lade Anhänge für die Vorlesung hoch.",
+ "units": "Vorlesungseinheiten",
+ "unitsDescription": "Füge Inhalte zur Vorlesung hinzu, indem du Vorlesungseinheiten erstellst."
},
+ "newLectureUnit": "Neue Vorlesungseinheit",
+ "editLectureUnit": "Vorlesungseinheit bearbeiten",
"dismissChangesModal": {
"title": "Ungespeicherte Änderungen der Vorlesung verwerfen?",
"message": "Bist du sicher, dass du die ungespeicherten Änderungen verwerfen willst?",
diff --git a/src/main/webapp/i18n/en/lecture.json b/src/main/webapp/i18n/en/lecture.json
index 9c07a5530c0f..cf23b9cea407 100644
--- a/src/main/webapp/i18n/en/lecture.json
+++ b/src/main/webapp/i18n/en/lecture.json
@@ -4,6 +4,7 @@
"home": {
"title": "Lectures",
"createLabel": "Create Lecture",
+ "editLabel": "Edit Lecture",
"filterLabel": "Filter",
"filterOptions": {
"filterPast": "Past",
@@ -12,9 +13,6 @@
"filterUnspecifiedDates": "Unspecified Date(s)"
},
"createOrEditLabel": "Create or Edit Lecture",
- "switchToGuidedModeLabel": "Switch to guided mode",
- "switchToTraditionalModeLabel": "Switch to normal mode",
- "nextStepLabel": "Next",
"ingestLecturesInPyrisLabel": "Send Lectures to Iris"
},
"created": "Created new lecture with identifier {{ param }}",
@@ -71,24 +69,18 @@
"loading": "Loading...",
"label": "Import Lecture"
},
- "wizardMode": {
- "steps": {
- "titleStepTitle": "Title",
- "titleStepMessage": "Add a title and meaningful description to the new lecture.",
- "periodStepTitle": "Period",
- "periodStepMessage": "Specify the begin and end dates of the lecture.",
- "attachmentsStepTitle": "Attachments",
- "attachmentsStepMessage": "Upload attachments to this lecture.",
- "unitsStepTitle": "Units",
- "unitsStepMessage": "Add content to the lecture by creating different kinds of lecture units."
- },
- "newLectureUnit": "New Lecture Unit",
- "editLectureUnit": "Edit Lecture Unit",
- "newExercise": "Create new exercise",
- "competencyTitle": "Title",
- "competencyConnectedUnits": "Connected Units",
- "competencyNoConnectedUnits": "No connected units"
+ "sections": {
+ "title": "Title",
+ "titleDescription": "Add a title and meaningful description to the lecture.",
+ "period": "Period",
+ "periodDescription": "Specify the begin and end dates of the lecture.",
+ "attachments": "Attachments",
+ "attachmentsDescription": "Upload attachments to this lecture.",
+ "units": "Units",
+ "unitsDescription": "Add content to the lecture by creating different kinds of lecture units."
},
+ "newLectureUnit": "New Lecture Unit",
+ "editLectureUnit": "Edit Lecture Unit",
"dismissChangesModal": {
"title": "Discard unsaved lecture changes",
"message": "Are you sure you want to discard your unsaved changes?",
diff --git a/src/test/javascript/spec/component/lecture/close-edit-lecture-modal.component.spec.ts b/src/test/javascript/spec/component/lecture/close-edit-lecture-modal.component.spec.ts
index 93b92f1d9627..e35dfed6baa2 100644
--- a/src/test/javascript/spec/component/lecture/close-edit-lecture-modal.component.spec.ts
+++ b/src/test/javascript/spec/component/lecture/close-edit-lecture-modal.component.spec.ts
@@ -1,6 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ArtemisTestModule } from '../../test.module';
-import { CloseEditLectureModalComponent } from '../../../../../main/webapp/app/lecture/close-edit-lecture-dialog/close-edit-lecture-modal.component';
+import { CloseEditLectureModalComponent } from '../../../../../main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component';
describe('CloseEditLectureModalComponent', () => {
let component: CloseEditLectureModalComponent;
diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-units.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts
similarity index 98%
rename from src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-units.component.spec.ts
rename to src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts
index c850a83f6ecc..fd8a0190e1f9 100644
--- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-units.component.spec.ts
+++ b/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts
@@ -5,13 +5,12 @@ import { VideoUnitService } from 'app/lecture/lecture-unit/lecture-unit-manageme
import { MockComponent, MockProvider } from 'ng-mocks';
import { AlertService } from 'app/core/util/alert.service';
import { ActivatedRoute, Router } from '@angular/router';
-import { MockRouter } from '../../../helpers/mocks/mock-router';
+import { MockRouter } from '../../helpers/mocks/mock-router';
import { of, throwError } from 'rxjs';
import { VideoUnit } from 'app/entities/lecture-unit/videoUnit.model';
import dayjs from 'dayjs/esm';
import { HttpResponse } from '@angular/common/http';
import { By } from '@angular/platform-browser';
-import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component';
import { Lecture } from 'app/entities/lecture.model';
import { TextUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/textUnit.service';
import { OnlineUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service';
@@ -27,7 +26,8 @@ import { Attachment, AttachmentType } from 'app/entities/attachment.model';
import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model';
import { objectToJsonBlob } from 'app/utils/blob-util';
import { CreateExerciseUnitComponent } from 'app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component';
-import { CompetencyLectureUnitLink } from '../../../../../../main/webapp/app/entities/competency.model';
+import { LectureUpdateUnitsComponent } from 'app/lecture/lecture-units/lecture-units.component';
+import { CompetencyLectureUnitLink } from 'app/entities/competency.model';
@Component({ selector: 'jhi-video-unit-form', template: '' })
class VideoUnitFormStubComponent {
@@ -41,9 +41,9 @@ class UnitCreationCardStubComponent {
@Output() onUnitCreationCardClicked: EventEmitter
= new EventEmitter();
}
-describe('LectureWizardUnitComponent', () => {
- let wizardUnitComponentFixture: ComponentFixture;
- let wizardUnitComponent: LectureUpdateWizardUnitsComponent;
+describe('LectureUpdateUnitsComponent', () => {
+ let wizardUnitComponentFixture: ComponentFixture;
+ let wizardUnitComponent: LectureUpdateUnitsComponent;
beforeEach(() => {
TestBed.configureTestingModule({
@@ -51,7 +51,7 @@ describe('LectureWizardUnitComponent', () => {
declarations: [
VideoUnitFormStubComponent,
UnitCreationCardStubComponent,
- LectureUpdateWizardUnitsComponent,
+ LectureUpdateUnitsComponent,
MockComponent(CreateExerciseUnitComponent),
MockComponent(LectureUnitManagementComponent),
],
@@ -72,7 +72,7 @@ describe('LectureWizardUnitComponent', () => {
})
.compileComponents()
.then(() => {
- wizardUnitComponentFixture = TestBed.createComponent(LectureUpdateWizardUnitsComponent);
+ wizardUnitComponentFixture = TestBed.createComponent(LectureUpdateUnitsComponent);
wizardUnitComponent = wizardUnitComponentFixture.componentInstance;
wizardUnitComponent.lecture = new Lecture();
wizardUnitComponent.lecture.id = 1;
diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts
index 384ec2002b9c..754da6e66712 100644
--- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts
+++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts
@@ -8,7 +8,6 @@ import { TranslateService } from '@ngx-translate/core';
import { Lecture } from 'app/entities/lecture.model';
import { LectureUpdateComponent } from 'app/lecture/lecture-update.component';
import { LectureService } from 'app/lecture/lecture.service';
-import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component';
import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component';
import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe';
import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
@@ -23,16 +22,19 @@ import { ArtemisTestModule } from '../../test.module';
import { DocumentationButtonComponent } from 'app/shared/components/documentation-button/documentation-button.component';
import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component';
import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component';
-import { CustomNotIncludedInValidatorDirective } from '../../../../../main/webapp/app/shared/validators/custom-not-included-in-validator.directive';
+import { CustomNotIncludedInValidatorDirective } from 'app/shared/validators/custom-not-included-in-validator.directive';
import { OwlDateTimeModule } from '@danielmoncada/angular-datetime-picker';
-import { TitleChannelNameComponent } from '../../../../../main/webapp/app/shared/form/title-channel-name/title-channel-name.component';
-import { LectureUpdatePeriodComponent } from '../../../../../main/webapp/app/lecture/lecture-period/lecture-period.component';
-import { LectureUnitManagementComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component';
+import { TitleChannelNameComponent } from 'app/shared/form/title-channel-name/title-channel-name.component';
+import { LectureUpdatePeriodComponent } from 'app/lecture/lecture-period/lecture-period.component';
+import { LectureUnitManagementComponent } from 'app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component';
+import { FormStatusBarComponent } from 'app/forms/form-status-bar/form-status-bar.component';
+import { ArtemisSharedModule } from 'app/shared/shared.module';
+import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component';
+import { LectureUpdateUnitsComponent } from 'app/lecture/lecture-units/lecture-units.component';
+import { UnitCreationCardComponent } from 'app/lecture/lecture-unit/lecture-unit-management/unit-creation-card/unit-creation-card.component';
+import { signal } from '@angular/core';
describe('LectureUpdateComponent', () => {
- let lectureUpdateWizardComponentFixture: ComponentFixture;
- let lectureUpdateWizardComponent: LectureUpdateWizardComponent;
-
let lectureService: LectureService;
let lectureUpdateComponentFixture: ComponentFixture;
let lectureUpdateComponent: LectureUpdateComponent;
@@ -50,21 +52,24 @@ describe('LectureUpdateComponent', () => {
pastLecture.endDate = yesterday;
TestBed.configureTestingModule({
- imports: [ArtemisTestModule, FormsModule, MockModule(NgbTooltipModule), MockModule(OwlDateTimeModule)],
+ imports: [ArtemisTestModule, MockModule(ArtemisSharedModule), FormsModule, MockModule(NgbTooltipModule), MockModule(OwlDateTimeModule)],
declarations: [
LectureUpdateComponent,
LectureTitleChannelNameComponent,
TitleChannelNameComponent,
FormDateTimePickerComponent,
+ LectureAttachmentsComponent,
+ LectureUpdateUnitsComponent,
LectureUpdatePeriodComponent,
- MockComponent(LectureUpdateWizardComponent),
MockComponent(LectureUnitManagementComponent),
+ MockComponent(FormStatusBarComponent),
MockComponent(MarkdownEditorMonacoComponent),
MockComponent(DocumentationButtonComponent),
MockPipe(ArtemisTranslatePipe),
MockPipe(ArtemisDatePipe),
MockPipe(HtmlForMarkdownPipe),
MockRouterLinkDirective,
+ MockComponent(UnitCreationCardComponent),
MockDirective(CustomNotIncludedInValidatorDirective),
],
providers: [
@@ -93,9 +98,6 @@ describe('LectureUpdateComponent', () => {
lectureUpdateComponentFixture = TestBed.createComponent(LectureUpdateComponent);
lectureUpdateComponent = lectureUpdateComponentFixture.componentInstance;
- lectureUpdateWizardComponentFixture = TestBed.createComponent(LectureUpdateWizardComponent);
- lectureUpdateWizardComponent = lectureUpdateWizardComponentFixture.componentInstance;
-
lectureService = TestBed.inject(LectureService);
router = TestBed.get(Router);
activatedRoute = TestBed.inject(ActivatedRoute);
@@ -108,7 +110,6 @@ describe('LectureUpdateComponent', () => {
it('should create lecture', () => {
lectureUpdateComponent.lecture.set({ title: 'test1', channelName: 'test1' } as Lecture);
- const navigateSpy = jest.spyOn(router, 'navigate');
const createSpy = jest.spyOn(lectureService, 'create').mockReturnValue(
of(
@@ -127,58 +128,13 @@ describe('LectureUpdateComponent', () => {
lectureUpdateComponent.save();
lectureUpdateComponentFixture.detectChanges();
- const expectedPath = ['course-management', 1, 'lectures', 3];
- expect(navigateSpy).toHaveBeenCalledWith(expectedPath);
-
expect(createSpy).toHaveBeenCalledOnce();
expect(createSpy).toHaveBeenCalledWith({ title: 'test1', channelName: 'test1' });
});
- it('should create lecture in wizard mode', () => {
- lectureUpdateComponent.lecture.set({ title: '', channelName: '' } as Lecture);
- lectureUpdateComponent.isShowingWizardMode = true;
- lectureUpdateComponent.wizardComponent = lectureUpdateWizardComponent;
-
- const createSpy = jest.spyOn(lectureService, 'create').mockReturnValue(
- of>(
- new HttpResponse({
- body: {
- title: 'test1',
- course: {
- id: 1,
- },
- } as Lecture,
- }),
- ),
- );
-
- const findSpy = jest.spyOn(lectureService, 'findWithDetails').mockReturnValue(
- of>(
- new HttpResponse({
- body: {
- id: 3,
- title: 'test1',
- course: {
- id: 1,
- },
- } as Lecture,
- }),
- ),
- );
-
- const onLectureCreationSucceededSpy = jest.spyOn(lectureUpdateWizardComponent, 'onLectureCreationSucceeded');
-
- lectureUpdateComponent.save();
-
- expect(createSpy).toHaveBeenCalledOnce();
- expect(createSpy).toHaveBeenCalledWith({ title: '', channelName: '' });
-
- expect(findSpy).toHaveBeenCalledOnce();
- expect(onLectureCreationSucceededSpy).toHaveBeenCalledOnce();
- });
-
it('should edit a lecture', fakeAsync(() => {
activatedRoute.parent!.data = of({ course: { id: 1 }, lecture: { id: 6 } });
+ const navigateSpy = jest.spyOn(router, 'navigate');
lectureUpdateComponentFixture.detectChanges();
lectureUpdateComponent.lecture.set({ id: 6, title: 'test1Updated', channelName: 'test1Updated' } as Lecture);
@@ -201,32 +157,13 @@ describe('LectureUpdateComponent', () => {
tick();
lectureUpdateComponentFixture.detectChanges();
+ const expectedPath = ['course-management', 1, 'lectures', 6];
+ expect(navigateSpy).toHaveBeenCalledWith(expectedPath);
+
expect(updateSpy).toHaveBeenCalledOnce();
expect(updateSpy).toHaveBeenCalledWith({ id: 6, title: 'test1Updated', channelName: 'test1Updated' });
}));
- it('should switch to wizard mode', fakeAsync(() => {
- lectureUpdateComponent.isShowingWizardMode = false;
- const wizardModeButton = jest.spyOn(lectureUpdateComponent, 'toggleWizardMode');
- lectureUpdateComponent.toggleWizardMode();
- tick();
- expect(wizardModeButton).toHaveBeenCalledOnce();
- expect(lectureUpdateComponent.isShowingWizardMode).toBeTrue();
- }));
-
- it('should be in wizard mode', fakeAsync(() => {
- activatedRoute = TestBed.inject(ActivatedRoute);
- activatedRoute.queryParams = of({
- shouldBeInWizardMode: true,
- });
-
- lectureUpdateComponent.ngOnInit();
- lectureUpdateComponentFixture.detectChanges();
- tick();
-
- expect(lectureUpdateComponent.isShowingWizardMode).toBeTrue();
- }));
-
it('should select process units checkbox', fakeAsync(() => {
lectureUpdateComponent.processUnitMode = false;
const selectProcessUnit = jest.spyOn(lectureUpdateComponent, 'onSelectProcessUnit');
@@ -416,4 +353,79 @@ describe('LectureUpdateComponent', () => {
expect(lectureUpdateComponent.isChangeMadeToPeriodSection()).toBeFalse();
});
});
+
+ describe('updateFormStatusBar', () => {
+ it('should update form status bar correctly in edit mode', () => {
+ lectureUpdateComponent.isEditMode.set(true);
+ lectureUpdateComponent.titleSection = signal({
+ titleChannelNameComponent: () => ({
+ isFormValidSignal: () => true,
+ }),
+ } as any);
+ lectureUpdateComponent.lecturePeriodSection = signal({
+ isPeriodSectionValid: () => true,
+ } as any);
+ lectureUpdateComponent.attachmentsSection = signal({
+ isFormValid: () => true,
+ } as any);
+ lectureUpdateComponent.unitSection = signal({
+ isUnitConfigurationValid: () => true,
+ } as any);
+
+ lectureUpdateComponent.updateFormStatusBar();
+
+ expect(lectureUpdateComponent.formStatusSections).toEqual([
+ { title: 'artemisApp.lecture.sections.title', valid: true },
+ { title: 'artemisApp.lecture.sections.period', valid: true },
+ { title: 'artemisApp.lecture.sections.attachments', valid: true },
+ { title: 'artemisApp.lecture.sections.units', valid: true },
+ ]);
+ });
+
+ it('should update form status bar correctly in create mode', () => {
+ lectureUpdateComponent.isEditMode.set(false);
+ lectureUpdateComponent.titleSection = signal({
+ titleChannelNameComponent: () => ({
+ isFormValidSignal: () => false,
+ }),
+ } as any);
+ lectureUpdateComponent.lecturePeriodSection = signal({
+ isPeriodSectionValid: () => true,
+ } as any);
+
+ lectureUpdateComponent.updateFormStatusBar();
+
+ expect(lectureUpdateComponent.formStatusSections).toEqual([
+ { title: 'artemisApp.lecture.sections.title', valid: false },
+ { title: 'artemisApp.lecture.sections.period', valid: true },
+ ]);
+ });
+
+ it('should handle invalid sections correctly', () => {
+ lectureUpdateComponent.isEditMode.set(true);
+ lectureUpdateComponent.titleSection = signal({
+ titleChannelNameComponent: () => ({
+ isFormValidSignal: () => false,
+ }),
+ } as any);
+ lectureUpdateComponent.lecturePeriodSection = signal({
+ isPeriodSectionValid: () => false,
+ } as any);
+ lectureUpdateComponent.attachmentsSection = signal({
+ isFormValid: () => false,
+ } as any);
+ lectureUpdateComponent.unitSection = signal({
+ isUnitConfigurationValid: () => false,
+ } as any);
+
+ lectureUpdateComponent.updateFormStatusBar();
+
+ expect(lectureUpdateComponent.formStatusSections).toEqual([
+ { title: 'artemisApp.lecture.sections.title', valid: false },
+ { title: 'artemisApp.lecture.sections.period', valid: false },
+ { title: 'artemisApp.lecture.sections.attachments', valid: false },
+ { title: 'artemisApp.lecture.sections.units', valid: false },
+ ]);
+ });
+ });
});
diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-attachments.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-attachments.component.spec.ts
deleted file mode 100644
index 0e3f9909ad52..000000000000
--- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-attachments.component.spec.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MockPipe } from 'ng-mocks';
-import { Lecture } from 'app/entities/lecture.model';
-import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
-import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component';
-
-describe('LectureWizardAttachmentsComponent', () => {
- let wizardAttachmentsComponentFixture: ComponentFixture;
- let wizardAttachmentsComponent: LectureUpdateWizardAttachmentsComponent;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [],
- declarations: [LectureUpdateWizardAttachmentsComponent, MockPipe(ArtemisTranslatePipe)],
- providers: [],
- schemas: [],
- })
- .compileComponents()
- .then(() => {
- wizardAttachmentsComponentFixture = TestBed.createComponent(LectureUpdateWizardAttachmentsComponent);
- wizardAttachmentsComponent = wizardAttachmentsComponentFixture.componentInstance;
- wizardAttachmentsComponent.lecture = new Lecture();
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should initialize', () => {
- wizardAttachmentsComponentFixture.detectChanges();
- expect(wizardAttachmentsComponent).not.toBeNull();
- });
-});
diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts
deleted file mode 100644
index de4b7fc35dc7..000000000000
--- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component';
-import { Lecture } from 'app/entities/lecture.model';
-import { MockComponent, MockDirective, MockModule } from 'ng-mocks';
-import { FormsModule } from '@angular/forms';
-import { MarkdownEditorMonacoComponent } from '../../../../../../main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component';
-import { LectureTitleChannelNameComponent } from '../../../../../../main/webapp/app/lecture/lecture-title-channel-name.component';
-import { CustomNotIncludedInValidatorDirective } from '../../../../../../main/webapp/app/shared/validators/custom-not-included-in-validator.directive';
-import { TitleChannelNameComponent } from '../../../../../../main/webapp/app/shared/form/title-channel-name/title-channel-name.component';
-
-describe('LectureWizardTitleComponent', () => {
- let wizardTitleComponentFixture: ComponentFixture;
- let wizardTitleComponent: LectureUpdateWizardTitleComponent;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [MockModule(FormsModule)],
- declarations: [
- LectureUpdateWizardTitleComponent,
- LectureTitleChannelNameComponent,
- TitleChannelNameComponent,
- MockComponent(MarkdownEditorMonacoComponent),
- MockDirective(CustomNotIncludedInValidatorDirective),
- ],
- providers: [],
- schemas: [],
- })
- .compileComponents()
- .then(() => {
- wizardTitleComponentFixture = TestBed.createComponent(LectureUpdateWizardTitleComponent);
- wizardTitleComponent = wizardTitleComponentFixture.componentInstance;
- wizardTitleComponent.lecture = new Lecture();
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should initialize', () => {
- wizardTitleComponentFixture.detectChanges();
- expect(wizardTitleComponent).not.toBeNull();
- });
-});
diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts
deleted file mode 100644
index e01eea78ab89..000000000000
--- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
-import { MockComponent, MockModule, MockProvider } from 'ng-mocks';
-import { ActivatedRoute, Router } from '@angular/router';
-import { MockRouter } from '../../../helpers/mocks/mock-router';
-import { of } from 'rxjs';
-import { Lecture } from 'app/entities/lecture.model';
-import { MockTranslateService } from '../../../helpers/mocks/service/mock-translate.service';
-import { TranslateService } from '@ngx-translate/core';
-import { Course } from 'app/entities/course.model';
-import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component';
-import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils';
-import { CourseManagementService } from 'app/course/manage/course-management.service';
-import { LectureUpdateWizardStepComponent } from 'app/lecture/wizard-mode/lecture-update-wizard-step.component';
-import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component';
-import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component';
-import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component';
-import dayjs from 'dayjs/esm';
-import { LectureUpdatePeriodComponent } from '../../../../../../main/webapp/app/lecture/lecture-period/lecture-period.component';
-import { ArtemisTestModule } from '../../../test.module';
-import { ArtemisSharedModule } from '../../../../../../main/webapp/app/shared/shared.module';
-import { FormDateTimePickerComponent } from '../../../../../../main/webapp/app/shared/date-time-picker/date-time-picker.component';
-
-describe('LectureWizardComponent', () => {
- let wizardComponentFixture: ComponentFixture;
- let wizardComponent: LectureUpdateWizardComponent;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ArtemisTestModule, MockModule(ArtemisSharedModule)],
- declarations: [
- LectureUpdateWizardComponent,
- LectureUpdatePeriodComponent,
- MockComponent(FormDateTimePickerComponent),
- MockComponent(LectureUpdateWizardTitleComponent),
- MockComponent(LectureUpdateWizardStepComponent),
- MockComponent(LectureUpdateWizardUnitsComponent),
- MockComponent(LectureUpdateWizardAttachmentsComponent),
- ],
- providers: [
- MockProvider(ArtemisNavigationUtilService),
- MockProvider(CourseManagementService),
- { provide: TranslateService, useClass: MockTranslateService },
- { provide: Router, useClass: MockRouter },
- {
- provide: ActivatedRoute,
- useValue: { queryParams: of({}) },
- },
- ],
- schemas: [],
- })
- .compileComponents()
- .then(() => {
- wizardComponentFixture = TestBed.createComponent(LectureUpdateWizardComponent);
- wizardComponent = wizardComponentFixture.componentInstance;
-
- const course = new Course();
- course.id = 2;
-
- wizardComponent.lecture = new Lecture();
- wizardComponent.lecture.id = 1;
- wizardComponent.lecture.course = course;
- });
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- it('should initialize and set step with given lecture', fakeAsync(() => {
- wizardComponentFixture.detectChanges();
- expect(wizardComponent).not.toBeNull();
-
- wizardComponentFixture.whenStable().then(() => {
- expect(wizardComponent.currentStep).toBe(4);
- });
- }));
-
- it('should initialize and set step without given lecture', fakeAsync(() => {
- wizardComponent.lecture.id = undefined;
-
- wizardComponentFixture.detectChanges();
- expect(wizardComponent).not.toBeNull();
-
- tick();
-
- wizardComponentFixture.whenStable().then(() => {
- expect(wizardComponent.currentStep).toBe(1);
- });
- }));
-
- it('should initialize and set step without given lecture but preset date', fakeAsync(() => {
- wizardComponent.lecture.id = undefined;
- wizardComponent.lecture.startDate = dayjs().year(2010).month(3).date(5);
-
- wizardComponentFixture.detectChanges();
- expect(wizardComponent).not.toBeNull();
-
- tick();
-
- wizardComponentFixture.whenStable().then(() => {
- expect(wizardComponent.currentStep).toBe(2);
- });
- }));
-
- it('should initialize and set given step', fakeAsync(() => {
- const route = TestBed.inject(ActivatedRoute);
- route.queryParams = of({ step: 3 });
-
- wizardComponentFixture.detectChanges();
- expect(wizardComponent).not.toBeNull();
-
- wizardComponentFixture.whenStable().then(() => {
- expect(wizardComponent.currentStep).toBe(3);
- });
- }));
-
- it('should increase the step when clicked', fakeAsync(() => {
- const route = TestBed.inject(ActivatedRoute);
- route.queryParams = of({ step: 1 });
-
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- expect(wizardComponent.currentStep).toBe(1);
- wizardComponent.next();
- expect(wizardComponent.currentStep).toBe(2);
- });
- }));
-
- it('should save the lecture when finishing the last step', fakeAsync(() => {
- wizardComponent.saveLectureFunction = () => {};
- const saveStub = jest.spyOn(wizardComponent, 'saveLectureFunction');
-
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- expect(wizardComponent.currentStep).toBe(4);
- wizardComponent.next();
- expect(saveStub).toHaveBeenCalledOnce();
- });
- }));
-
- it('should increase the step after lecture created', fakeAsync(() => {
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- wizardComponent.currentStep = 2;
- wizardComponent.onLectureCreationSucceeded();
- expect(wizardComponent.currentStep).toBe(3);
- });
- }));
-
- it('should return correct icon for last step', fakeAsync(() => {
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- wizardComponent.currentStep = 5;
- const result = wizardComponent.getNextIcon();
- expect(result).toBe(wizardComponent.faCheck);
- });
- }));
-
- it('should return correct icon for not last step', fakeAsync(() => {
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- wizardComponent.currentStep = 3;
- const result = wizardComponent.getNextIcon();
- expect(result).toBe(wizardComponent.faArrowRight);
- });
- }));
-
- it('should return correct text for last step', fakeAsync(() => {
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- wizardComponent.currentStep = 5;
- const result = wizardComponent.getNextText();
- expect(result).toBe('entity.action.finish');
- });
- }));
-
- it('should return correct text for not last step', fakeAsync(() => {
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- wizardComponent.currentStep = 3;
- const result = wizardComponent.getNextText();
- expect(result).toBe('artemisApp.lecture.home.nextStepLabel');
- });
- }));
-
- it('should toggle wizard when lecture not created', fakeAsync(() => {
- wizardComponent.toggleModeFunction = () => {};
- const toggleStub = jest.spyOn(wizardComponent, 'toggleModeFunction');
-
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- wizardComponent.currentStep = 1;
- wizardComponent.toggleWizardMode();
- expect(toggleStub).toHaveBeenCalledOnce();
- });
- }));
-
- it('should navigate when toggling wizard after lecture was created', fakeAsync(() => {
- const router = TestBed.inject(Router);
- const navigateStub = jest.spyOn(router, 'navigate');
-
- wizardComponentFixture.detectChanges();
-
- wizardComponentFixture.whenStable().then(() => {
- wizardComponent.currentStep = 3;
- wizardComponent.toggleWizardMode();
- expect(navigateStub).toHaveBeenCalledTimes(2); // 1 from init to clear the params and 1 from toggling
- });
- }));
-});
diff --git a/src/test/javascript/spec/component/shared/image-cropper/keyboard.util.spec.ts b/src/test/javascript/spec/component/shared/image-cropper/keyboard.util.spec.ts
new file mode 100644
index 000000000000..19b6070aef3d
--- /dev/null
+++ b/src/test/javascript/spec/component/shared/image-cropper/keyboard.util.spec.ts
@@ -0,0 +1,71 @@
+import { getEventForKey, getInvertedPositionForKey, getPositionForKey } from 'app/shared/image-cropper/utils/keyboard.utils';
+
+describe('Keyboard Utils', () => {
+ describe('getPositionForKey', () => {
+ it('should return correct position for ArrowUp', () => {
+ expect(getPositionForKey('ArrowUp')).toBe('top');
+ });
+
+ it('should return correct position for ArrowRight', () => {
+ expect(getPositionForKey('ArrowRight')).toBe('right');
+ });
+
+ it('should return correct position for ArrowDown', () => {
+ expect(getPositionForKey('ArrowDown')).toBe('bottom');
+ });
+
+ it('should return correct position for ArrowLeft', () => {
+ expect(getPositionForKey('ArrowLeft')).toBe('left');
+ });
+
+ it('should return default position for unknown key', () => {
+ expect(getPositionForKey('UnknownKey')).toBe('left');
+ });
+ });
+
+ describe('getInvertedPositionForKey', () => {
+ it('should return correct inverted position for ArrowUp', () => {
+ expect(getInvertedPositionForKey('ArrowUp')).toBe('bottom');
+ });
+
+ it('should return correct inverted position for ArrowRight', () => {
+ expect(getInvertedPositionForKey('ArrowRight')).toBe('left');
+ });
+
+ it('should return correct inverted position for ArrowDown', () => {
+ expect(getInvertedPositionForKey('ArrowDown')).toBe('top');
+ });
+
+ it('should return correct inverted position for ArrowLeft', () => {
+ expect(getInvertedPositionForKey('ArrowLeft')).toBe('right');
+ });
+
+ it('should return default inverted position for unknown key', () => {
+ expect(getInvertedPositionForKey('UnknownKey')).toBe('right');
+ });
+ });
+
+ describe('getEventForKey', () => {
+ const stepSize = 10;
+
+ it('should return correct event for ArrowUp', () => {
+ expect(getEventForKey('ArrowUp', stepSize)).toEqual({ clientX: 0, clientY: -stepSize });
+ });
+
+ it('should return correct event for ArrowRight', () => {
+ expect(getEventForKey('ArrowRight', stepSize)).toEqual({ clientX: stepSize, clientY: 0 });
+ });
+
+ it('should return correct event for ArrowDown', () => {
+ expect(getEventForKey('ArrowDown', stepSize)).toEqual({ clientX: 0, clientY: stepSize });
+ });
+
+ it('should return correct event for ArrowLeft', () => {
+ expect(getEventForKey('ArrowLeft', stepSize)).toEqual({ clientX: -stepSize, clientY: 0 });
+ });
+
+ it('should return default event for unknown key', () => {
+ expect(getEventForKey('UnknownKey', stepSize)).toEqual({ clientX: -stepSize, clientY: 0 });
+ });
+ });
+});
diff --git a/src/test/javascript/spec/util/regex.util.spec.ts b/src/test/javascript/spec/util/regex.util.spec.ts
new file mode 100644
index 000000000000..1501b95a9e4a
--- /dev/null
+++ b/src/test/javascript/spec/util/regex.util.spec.ts
@@ -0,0 +1,34 @@
+import { matchesRegexFully } from 'app/utils/regex.util';
+
+describe('matchesRegexFully', () => {
+ it('should return true if regex is undefined', () => {
+ expect(matchesRegexFully('test', undefined)).toBeTrue();
+ });
+
+ it('should return false if input is undefined', () => {
+ expect(matchesRegexFully(undefined, 'test')).toBeFalse();
+ });
+
+ it('should return true for a full match', () => {
+ expect(matchesRegexFully('test', 'test')).toBeTrue();
+ });
+
+ it('should return false for a partial match', () => {
+ expect(matchesRegexFully('testing', 'test')).toBeFalse();
+ });
+
+ it('should return true for a match with regex special characters', () => {
+ expect(matchesRegexFully('test123', 'test\\d+')).toBeTrue();
+ });
+
+ it('should return false for no match', () => {
+ expect(matchesRegexFully('test', 'no-match')).toBeFalse();
+ });
+
+ it('should handle regex without ^ and $', () => {
+ expect(matchesRegexFully('test', 'test')).toBeTrue();
+ expect(matchesRegexFully('test', '^test')).toBeTrue();
+ expect(matchesRegexFully('test', 'test$')).toBeTrue();
+ expect(matchesRegexFully('test', '^test$')).toBeTrue();
+ });
+});
diff --git a/src/test/playwright/e2e/lecture/LectureManagement.spec.ts b/src/test/playwright/e2e/lecture/LectureManagement.spec.ts
index 74590f2f5e14..c229c40f701d 100644
--- a/src/test/playwright/e2e/lecture/LectureManagement.spec.ts
+++ b/src/test/playwright/e2e/lecture/LectureManagement.spec.ts
@@ -41,10 +41,17 @@ test.describe('Lecture management', { tag: '@fast' }, () => {
const lectureResponse = await lectureCreation.save();
const lecture: Lecture = await lectureResponse.json();
expect(lectureResponse.status()).toBe(201);
+ await expect(page).toHaveURL(`/course-management/${course.id}/lectures/${lecture.id}/edit`);
+
+ const adjustedDescription = description! + 'change to enable save button again';
+ await lectureCreation.typeDescription(adjustedDescription);
+ const lectureResponseFromEdit = await lectureCreation.save();
+ const lectureFromEdit: Lecture = await lectureResponseFromEdit.json();
+ expect(lectureResponseFromEdit.status()).toBe(200);
+ await page.waitForURL(`**/${course.id}/lectures/${lectureFromEdit.id}`);
- await page.waitForURL(`**/${course.id}/lectures/${lecture.id}`);
await expect(lectureManagement.getLectureTitle()).toContainText(lectureData.title);
- await expect(lectureManagement.getLectureDescription()).toContainText(description!);
+ await expect(lectureManagement.getLectureDescription()).toContainText(adjustedDescription!);
await expect(lectureManagement.getLectureVisibleDate()).toContainText(lectureData.visibleDate!.format(dateFormat));
await expect(lectureManagement.getLectureStartDate()).toContainText(lectureData.startDate!.format(dateFormat));
await expect(lectureManagement.getLectureEndDate()).toContainText(lectureData.endDate!.format(dateFormat));