diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts index a2f1e2174227..22876044bd8c 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.ts @@ -270,6 +270,7 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { exercise.buildConfig = this.programmingExerciseBuildConfig; return [ this.getExerciseDetailsGeneralSection(exercise), + this.getExerciseDetailsBuildDetailsSection(exercise), this.getExerciseDetailsModeSection(exercise), this.getExerciseDetailsLanguageSection(exercise), this.getExerciseDetailsProblemSection(exercise), @@ -310,54 +311,10 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { }; } - getExerciseDetailsModeSection(exercise: ProgrammingExercise): DetailOverviewSection { + getExerciseDetailsBuildDetailsSection(exercise: ProgrammingExercise): DetailOverviewSection { return { - headline: 'artemisApp.programmingExercise.wizardMode.detailedSteps.difficultyStepTitle', + headline: 'artemisApp.programmingExercise.wizardMode.detailedSteps.buildDetailsStepTitle', details: [ - { - type: DetailType.Text, - title: 'artemisApp.exercise.difficulty', - data: { text: exercise.difficulty }, - }, - { - type: DetailType.Text, - title: 'artemisApp.exercise.mode', - data: { text: exercise.mode }, - }, - exercise.teamAssignmentConfig && { - type: DetailType.Text, - title: 'artemisApp.exercise.teamAssignmentConfig.teamSize', - data: { text: `Min. ${exercise.teamAssignmentConfig.minTeamSize}, Max. ${exercise.teamAssignmentConfig.maxTeamSize}` }, - }, - { - type: DetailType.Boolean, - title: 'artemisApp.programmingExercise.allowOfflineIde.title', - data: { boolean: exercise.allowOfflineIde }, - }, - { - type: DetailType.Boolean, - title: 'artemisApp.programmingExercise.allowOnlineEditor.title', - data: { boolean: exercise.allowOnlineEditor }, - }, - ], - }; - } - - getExerciseDetailsLanguageSection(exercise: ProgrammingExercise): DetailOverviewSection { - this.checkAndSetWindFile(exercise); - return { - headline: 'artemisApp.programmingExercise.wizardMode.detailedSteps.languageStepTitle', - details: [ - { - type: DetailType.Text, - title: 'artemisApp.programmingExercise.programmingLanguage', - data: { text: exercise.programmingLanguage?.toUpperCase() }, - }, - { - type: DetailType.Boolean, - title: 'artemisApp.programmingExercise.sequentialTestRuns.title', - data: { boolean: exercise.buildConfig?.sequentialTestRuns }, - }, { type: DetailType.ProgrammingRepositoryButtons, title: 'artemisApp.programmingExercise.templateRepositoryUri', @@ -455,6 +412,58 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { gitDiffReport: exercise.gitDiffReport, }, }, + ], + }; + } + + getExerciseDetailsModeSection(exercise: ProgrammingExercise): DetailOverviewSection { + return { + headline: 'artemisApp.programmingExercise.wizardMode.detailedSteps.difficultyStepTitle', + details: [ + { + type: DetailType.Text, + title: 'artemisApp.exercise.difficulty', + data: { text: exercise.difficulty }, + }, + { + type: DetailType.Text, + title: 'artemisApp.exercise.mode', + data: { text: exercise.mode }, + }, + exercise.teamAssignmentConfig && { + type: DetailType.Text, + title: 'artemisApp.exercise.teamAssignmentConfig.teamSize', + data: { text: `Min. ${exercise.teamAssignmentConfig.minTeamSize}, Max. ${exercise.teamAssignmentConfig.maxTeamSize}` }, + }, + { + type: DetailType.Boolean, + title: 'artemisApp.programmingExercise.allowOfflineIde.title', + data: { boolean: exercise.allowOfflineIde }, + }, + { + type: DetailType.Boolean, + title: 'artemisApp.programmingExercise.allowOnlineEditor.title', + data: { boolean: exercise.allowOnlineEditor }, + }, + ], + }; + } + + getExerciseDetailsLanguageSection(exercise: ProgrammingExercise): DetailOverviewSection { + this.checkAndSetWindFile(exercise); + return { + headline: 'artemisApp.programmingExercise.wizardMode.detailedSteps.languageStepTitle', + details: [ + { + type: DetailType.Text, + title: 'artemisApp.programmingExercise.programmingLanguage', + data: { text: exercise.programmingLanguage?.toUpperCase() }, + }, + { + type: DetailType.Boolean, + title: 'artemisApp.programmingExercise.sequentialTestRuns.title', + data: { boolean: exercise.buildConfig?.sequentialTestRuns }, + }, !!exercise.buildConfig?.buildScript && !!exercise.buildConfig?.windfile?.metadata?.docker?.image && { type: DetailType.Text, @@ -747,7 +756,7 @@ export class ProgrammingExerciseDetailComponent implements OnInit, OnDestroy { } /** - * Checks if the build configuration is available and sets the windfile if it is, helpful for reliably displaying + * Checks if the build configuration is available and sets the Windfile if it is, helpful for reliably displaying * the build configuration in the UI * @param exercise the programming exercise to check */ diff --git a/src/main/webapp/app/exercises/programming/manage/update/add-auxiliary-repository-button.component.ts b/src/main/webapp/app/exercises/programming/manage/update/add-auxiliary-repository-button.component.ts index 2b9ece99aff9..b6b98dbd87f4 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/add-auxiliary-repository-button.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/add-auxiliary-repository-button.component.ts @@ -3,6 +3,7 @@ import { ButtonSize, ButtonType } from 'app/shared/components/button.component'; import { ProgrammingExercise } from 'app/entities/programming-exercise.model'; import { AuxiliaryRepository } from 'app/entities/programming-exercise-auxiliary-repository-model'; import { faPlus } from '@fortawesome/free-solid-svg-icons'; +import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; @Component({ selector: 'jhi-add-auxiliary-repository-button', @@ -15,23 +16,24 @@ import { faPlus } from '@fortawesome/free-solid-svg-icons'; (onClick)="addAuxiliaryRepositoryRow()" /> `, + standalone: true, + imports: [ArtemisSharedComponentModule], }) export class AddAuxiliaryRepositoryButtonComponent { - ButtonType = ButtonType; - ButtonSize = ButtonSize; + protected readonly ButtonType = ButtonType; + protected readonly ButtonSize = ButtonSize; - @Input() programmingExercise: ProgrammingExercise; + protected readonly faPlus = faPlus; - @Output() onRefresh: EventEmitter = new EventEmitter(); + @Input({ required: true }) programmingExercise: ProgrammingExercise; - // Icons - faPlus = faPlus; + @Output() onRefresh: EventEmitter = new EventEmitter(); /** * Adds a new auxiliary repository, which is displayed as a new row, to the respective programming exercise and activates the angular change detection. */ addAuxiliaryRepositoryRow() { - if (this.programmingExercise.auxiliaryRepositories === undefined) { + if (!this.programmingExercise.auxiliaryRepositories) { this.programmingExercise.auxiliaryRepositories = []; } const newAuxiliaryRepository = new AuxiliaryRepository(); diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.html b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.html index 264d317dcab7..824247108cde 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.html +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.html @@ -18,6 +18,12 @@

+
+
diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts index e48803fc9c81..b77c49b593db 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts @@ -38,6 +38,7 @@ import { ProgrammingExerciseLanguageComponent } from 'app/exercises/programming/ import { ProgrammingExerciseGradingComponent } from 'app/exercises/programming/manage/update/update-components/programming-exercise-grading.component'; import { ExerciseUpdatePlagiarismComponent } from 'app/exercises/shared/plagiarism/exercise-update-plagiarism/exercise-update-plagiarism.component'; import { ImportOptions } from 'app/types/programming-exercises'; +import { ProgrammingExerciseBuildDetailsComponent } from 'app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component'; @Component({ selector: 'jhi-programming-exercise-update', @@ -46,6 +47,7 @@ import { ImportOptions } from 'app/types/programming-exercises'; }) export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDestroy, OnInit { @ViewChild(ProgrammingExerciseInformationComponent) exerciseInfoComponent?: ProgrammingExerciseInformationComponent; + @ViewChild(ProgrammingExerciseBuildDetailsComponent) exerciseBuildDetailsComponent?: ProgrammingExerciseBuildDetailsComponent; @ViewChild(ProgrammingExerciseDifficultyComponent) exerciseDifficultyComponent?: ProgrammingExerciseDifficultyComponent; @ViewChild(ProgrammingExerciseLanguageComponent) exerciseLanguageComponent?: ProgrammingExerciseLanguageComponent; @ViewChild(ProgrammingExerciseGradingComponent) exerciseGradingComponent?: ProgrammingExerciseGradingComponent; @@ -476,6 +478,7 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest ngAfterViewInit() { this.inputFieldSubscriptions.push(this.exerciseInfoComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); + this.inputFieldSubscriptions.push(this.exerciseBuildDetailsComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); this.inputFieldSubscriptions.push(this.exerciseDifficultyComponent?.teamConfigComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); this.inputFieldSubscriptions.push(this.exerciseLanguageComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); this.inputFieldSubscriptions.push(this.exerciseGradingComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); @@ -494,6 +497,10 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.generalInfoStepTitle', valid: this.exerciseInfoComponent?.formValid ?? false, }, + { + title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.buildDetailsStepTitle', + valid: this.exerciseBuildDetailsComponent?.formValid ?? false, + }, { title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.difficultyStepTitle', valid: (this.exerciseDifficultyComponent?.teamConfigComponent.formValid && this.validIdeSelection()) ?? false, diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.module.ts b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.module.ts index 8c5fa972cd92..c2dd662016ab 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.module.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.module.ts @@ -11,10 +11,8 @@ import { ArtemisCategorySelectorModule } from 'app/shared/category-selector/cate import { StructuredGradingCriterionModule } from 'app/exercises/shared/structured-grading-criterion/structured-grading-criterion.module'; import { ArtemisIncludedInOverallScorePickerModule } from 'app/exercises/shared/included-in-overall-score-picker/included-in-overall-score-picker.module'; import { ArtemisProgrammingExerciseLifecycleModule } from 'app/exercises/programming/shared/lifecycle/programming-exercise-lifecycle.module'; -import { AddAuxiliaryRepositoryButtonComponent } from 'app/exercises/programming/manage/update/add-auxiliary-repository-button.component'; import { NgxDatatableModule } from '@flaviosantoro92/ngx-datatable'; import { ArtemisTableModule } from 'app/shared/table/table.module'; -import { RemoveAuxiliaryRepositoryButtonComponent } from 'app/exercises/programming/manage/update/remove-auxiliary-repository-button.component'; import { SubmissionPolicyUpdateModule } from 'app/exercises/shared/submission-policy/submission-policy-update.module'; import { ArtemisModePickerModule } from 'app/exercises/shared/mode-picker/mode-picker.module'; import { ProgrammingExerciseInformationComponent } from 'app/exercises/programming/manage/update/update-components/programming-exercise-information.component'; @@ -32,6 +30,7 @@ import { FormsModule } from 'app/forms/forms.module'; import { ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-build-plan-checkout-directories.component'; import { ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component'; import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; +import { ProgrammingExerciseBuildDetailsComponent } from 'app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component'; @NgModule({ imports: [ @@ -57,6 +56,7 @@ import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.modul ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent, ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent, MonacoEditorModule, + ProgrammingExerciseBuildDetailsComponent, ], declarations: [ ProgrammingExerciseUpdateComponent, @@ -68,8 +68,6 @@ import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.modul ProgrammingExerciseLanguageComponent, ProgrammingExerciseGradingComponent, ProgrammingExerciseProblemComponent, - AddAuxiliaryRepositoryButtonComponent, - RemoveAuxiliaryRepositoryButtonComponent, ], exports: [ProgrammingExerciseUpdateComponent], }) diff --git a/src/main/webapp/app/exercises/programming/manage/update/remove-auxiliary-repository-button.component.ts b/src/main/webapp/app/exercises/programming/manage/update/remove-auxiliary-repository-button.component.ts index dcd21a30e18c..7d920d53c1bc 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/remove-auxiliary-repository-button.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/remove-auxiliary-repository-button.component.ts @@ -3,25 +3,27 @@ import { ButtonSize, ButtonType } from 'app/shared/components/button.component'; import { ProgrammingExercise } from 'app/entities/programming-exercise.model'; import { AuxiliaryRepository } from 'app/entities/programming-exercise-auxiliary-repository-model'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; @Component({ selector: 'jhi-remove-auxiliary-repository-button', template: ` `, + standalone: true, + imports: [ArtemisSharedComponentModule], }) export class RemoveAuxiliaryRepositoryButtonComponent { - ButtonType = ButtonType; - ButtonSize = ButtonSize; + protected readonly ButtonType = ButtonType; + protected readonly ButtonSize = ButtonSize; - @Input() programmingExercise: ProgrammingExercise; + protected readonly faTrash = faTrash; - @Input() row: AuxiliaryRepository; + @Input({ required: true }) programmingExercise: ProgrammingExercise; - @Output() onRefresh: EventEmitter = new EventEmitter(); + @Input({ required: true }) row: AuxiliaryRepository; - // Icons - faTrash = faTrash; + @Output() onRefresh: EventEmitter = new EventEmitter(); /** * Removes the auxiliary repository of the selected row from the respective programming exercise. diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component.html b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component.html new file mode 100644 index 000000000000..580a51da8a68 --- /dev/null +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component.html @@ -0,0 +1,171 @@ +
+

+

+
+
+ + +
+ + @for (error of shortName.errors! | keyvalue | removekeys: ['required']; track error) { + @if (shortName.invalid && (shortName.dirty || shortName.touched)) { +
+
+
+ } + } +
+ @if (programmingExercise.shortName && !shortName.invalid) { +
+
+ + +
+ + @if (!programmingExerciseCreationConfig.isImportFromExistingExercise && programmingExerciseCreationConfig.auxiliaryRepositoriesSupported) { +
+ @if (programmingExercise.auxiliaryRepositories && programmingExercise.auxiliaryRepositories.length) { + + + + + + + + + + + + + + + + + + + + + + + } + @if (programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateNames || programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateDirectories) { + + @if (programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateNames) { + + } + @if (programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateDirectories) { + + } + + } + @if (programmingExerciseCreationConfig && !isLocal) { + + + + } + + +
+ } +
+ } +
+ @if (programmingExerciseCreationConfig.isImportFromExistingExercise) { +
+ +
+ } + @if ( + programmingExerciseCreationConfig.isImportFromExistingExercise && + programmingExercise.projectType !== ProjectType.PLAIN_GRADLE && + programmingExercise.projectType !== ProjectType.GRADLE_GRADLE + ) { +
+ +
+ } +
+
diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component.ts b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component.ts new file mode 100644 index 000000000000..3e433891c2a1 --- /dev/null +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component.ts @@ -0,0 +1,103 @@ +import { AfterViewInit, Component, Input, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { NgModel } from '@angular/forms'; +import { Subject, Subscription } from 'rxjs'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; +import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; +import { ArtemisTableModule } from 'app/shared/table/table.module'; +import { ProgrammingExerciseCreationConfig } from 'app/exercises/programming/manage/update/programming-exercise-creation-config'; +import { ProgrammingExercise, ProjectType } from 'app/entities/programming-exercise.model'; +import { TableEditableFieldComponent } from 'app/shared/table/table-editable-field.component'; +import { every } from 'lodash-es'; +import { AddAuxiliaryRepositoryButtonComponent } from 'app/exercises/programming/manage/update/add-auxiliary-repository-button.component'; +import { RemoveAuxiliaryRepositoryButtonComponent } from 'app/exercises/programming/manage/update/remove-auxiliary-repository-button.component'; +import { NgxDatatableModule } from '@flaviosantoro92/ngx-datatable'; +import { ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-build-plan-checkout-directories.component'; +import { ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component'; +import { ImportOptions } from 'app/types/programming-exercises'; + +@Component({ + selector: 'jhi-programming-exercise-build-details', + templateUrl: './programming-exercise-build-details.component.html', + styleUrls: ['../../programming-exercise-form.scss'], + standalone: true, + imports: [ + ArtemisSharedModule, + ArtemisSharedComponentModule, + ArtemisTableModule, + AddAuxiliaryRepositoryButtonComponent, + RemoveAuxiliaryRepositoryButtonComponent, + NgxDatatableModule, + ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent, + ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent, + ], +}) +export class ProgrammingExerciseBuildDetailsComponent implements AfterViewInit { + protected readonly ProjectType = ProjectType; + + formValid: boolean; + formValidChanges = new Subject(); + + inputFieldSubscriptions: (Subscription | undefined)[] = []; + + @Input() isLocal: boolean; + @Input() programmingExercise: ProgrammingExercise; + @Input() programmingExerciseCreationConfig: ProgrammingExerciseCreationConfig; + @Input() importOptions: ImportOptions; + + @ViewChild('shortName') shortNameField: NgModel; + @ViewChildren(TableEditableFieldComponent) tableEditableFields?: QueryList; + @ViewChild('checkoutSolutionRepository') checkoutSolutionRepositoryField?: NgModel; + @ViewChild('recreateBuildPlans') recreateBuildPlansField?: NgModel; + @ViewChild('updateTemplateFiles') updateTemplateFilesField?: NgModel; + + ngAfterViewInit() { + this.inputFieldSubscriptions.push(this.shortNameField.valueChanges?.subscribe(() => this.calculateFormValid())); + this.tableEditableFields?.changes.subscribe((fields: QueryList) => { + fields.toArray().forEach((field) => this.inputFieldSubscriptions.push(field.editingInput.valueChanges?.subscribe(() => this.calculateFormValid()))); + }); + this.inputFieldSubscriptions.push(this.checkoutSolutionRepositoryField?.valueChanges?.subscribe(() => this.calculateFormValid())); + this.inputFieldSubscriptions.push(this.recreateBuildPlansField?.valueChanges?.subscribe(() => this.calculateFormValid())); + this.inputFieldSubscriptions.push(this.updateTemplateFilesField?.valueChanges?.subscribe(() => this.calculateFormValid())); + } + + calculateFormValid() { + this.formValid = + !this.shortNameField.invalid && + this.isCheckoutSolutionRepositoryValid() && + this.areAuxiliaryRepositoriesValid() && + this.isRecreateBuildPlansValid() && + this.isUpdateTemplateFilesValid(); + this.formValidChanges.next(this.formValid); + } + + private areAuxiliaryRepositoriesValid(): boolean { + return ( + (every(this.tableEditableFields?.map((field) => field.editingInput.valid)) && + !this.programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateDirectories && + !this.programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateNames) || + !this.programmingExercise.auxiliaryRepositories?.length + ); + } + + private isCheckoutSolutionRepositoryValid(): boolean { + return ( + this.checkoutSolutionRepositoryField?.valid || + this.programmingExercise.id !== undefined || + !this.programmingExercise.programmingLanguage || + !this.programmingExerciseCreationConfig.checkoutSolutionRepositoryAllowed + ); + } + + private isUpdateTemplateFilesValid(): boolean { + return ( + this.updateTemplateFilesField?.valid || + !this.programmingExerciseCreationConfig.isImportFromExistingExercise || + this.programmingExercise.projectType === ProjectType.PLAIN_GRADLE || + this.programmingExercise.projectType === ProjectType.GRADLE_GRADLE + ); + } + + private isRecreateBuildPlansValid(): boolean { + return this.recreateBuildPlansField?.valid || !this.programmingExerciseCreationConfig.isImportFromExistingExercise; + } +} diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-difficulty.component.ts b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-difficulty.component.ts index 77e30693bff9..d8f7a9b9af22 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-difficulty.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-difficulty.component.ts @@ -10,13 +10,15 @@ import { TeamConfigFormGroupComponent } from 'app/exercises/shared/team-config-f styleUrls: ['../../programming-exercise-form.scss'], }) export class ProgrammingExerciseDifficultyComponent { - @Input() programmingExercise: ProgrammingExercise; - @Input() programmingExerciseCreationConfig: ProgrammingExerciseCreationConfig; - @ViewChild(TeamConfigFormGroupComponent) teamConfigComponent: TeamConfigFormGroupComponent; + protected readonly ProjectType = ProjectType; - @Output() triggerValidation = new EventEmitter(); + protected readonly faQuestionCircle = faQuestionCircle; - protected readonly ProjectType = ProjectType; + @Input({ required: true }) programmingExercise: ProgrammingExercise; - faQuestionCircle = faQuestionCircle; + @Input({ required: true }) programmingExerciseCreationConfig: ProgrammingExerciseCreationConfig; + + @ViewChild(TeamConfigFormGroupComponent) teamConfigComponent: TeamConfigFormGroupComponent; + + @Output() triggerValidation = new EventEmitter(); } diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-information.component.html b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-information.component.html index 78be8ae11ee0..69a702dc5197 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-information.component.html +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-information.component.html @@ -19,164 +19,6 @@ [isImport]="isImport" /> -
-
- - -
- - @for (error of shortName.errors! | keyvalue | removekeys: ['required']; track error) { - @if (shortName.invalid && (shortName.dirty || shortName.touched)) { -
-
-
- } - } -
- @if (programmingExercise.shortName && !shortName.invalid) { -
-
- - -
- - @if (!programmingExerciseCreationConfig.isImportFromExistingExercise && programmingExerciseCreationConfig.auxiliaryRepositoriesSupported) { -
- @if (programmingExercise.auxiliaryRepositories && programmingExercise.auxiliaryRepositories.length > 0) { - - - - - - - - - - - - - - - - - - - - - - - } - @if (programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateNames || programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateDirectories) { - - @if (programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateNames) { - - } - @if (programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateDirectories) { - - } - - } - @if (programmingExerciseCreationConfig && !isLocal) { - - - - } - - -
- } -
- } -
- @if (programmingExerciseCreationConfig.isImportFromExistingExercise) { -
- -
- } - @if ( - programmingExerciseCreationConfig.isImportFromExistingExercise && - programmingExercise.projectType !== ProjectType.PLAIN_GRADLE && - programmingExercise.projectType !== ProjectType.GRADLE_GRADLE - ) { -
- -
- } -
@if (!programmingExerciseCreationConfig.isExamMode) {
diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-information.component.ts b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-information.component.ts index b1bd35bca30a..eecc0d417bd8 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-information.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/programming-exercise-information.component.ts @@ -1,12 +1,8 @@ -import { AfterViewInit, Component, Input, OnDestroy, QueryList, ViewChild, ViewChildren } from '@angular/core'; -import { NgModel } from '@angular/forms'; +import { AfterViewInit, Component, Input, OnDestroy, ViewChild } from '@angular/core'; import { ProgrammingExercise, ProjectType } from 'app/entities/programming-exercise.model'; import { ProgrammingExerciseCreationConfig } from 'app/exercises/programming/manage/update/programming-exercise-creation-config'; import { ExerciseTitleChannelNameComponent } from 'app/exercises/shared/exercise-title-channel-name/exercise-title-channel-name.component'; import { Subject, Subscription } from 'rxjs'; -import { TableEditableFieldComponent } from 'app/shared/table/table-editable-field.component'; -import { every } from 'lodash-es'; -import { ImportOptions } from 'app/types/programming-exercises'; @Component({ selector: 'jhi-programming-exercise-info', @@ -14,36 +10,23 @@ import { ImportOptions } from 'app/types/programming-exercises'; styleUrls: ['../../programming-exercise-form.scss', 'programming-exercise-information.component.scss'], }) export class ProgrammingExerciseInformationComponent implements AfterViewInit, OnDestroy { - @Input() isImport: boolean; - @Input() isExamMode: boolean; - @Input() programmingExercise: ProgrammingExercise; - @Input() programmingExerciseCreationConfig: ProgrammingExerciseCreationConfig; - @Input() isLocal: boolean; - @Input() importOptions: ImportOptions; + protected readonly ProjectType = ProjectType; + + @Input() isImport: boolean = false; + @Input() isExamMode: boolean = false; + @Input({ required: true }) programmingExercise: ProgrammingExercise; + @Input({ required: true }) programmingExerciseCreationConfig: ProgrammingExerciseCreationConfig; + @Input() isLocal: boolean = false; @ViewChild(ExerciseTitleChannelNameComponent) exerciseTitleChannelComponent: ExerciseTitleChannelNameComponent; - @ViewChildren(TableEditableFieldComponent) tableEditableFields?: QueryList; - @ViewChild('shortName') shortNameField: NgModel; - @ViewChild('checkoutSolutionRepository') checkoutSolutionRepositoryField?: NgModel; - @ViewChild('recreateBuildPlans') recreateBuildPlansField?: NgModel; - @ViewChild('updateTemplateFiles') updateTemplateFilesField?: NgModel; formValid: boolean; formValidChanges = new Subject(); inputFieldSubscriptions: (Subscription | undefined)[] = []; - protected readonly ProjectType = ProjectType; - ngAfterViewInit() { this.inputFieldSubscriptions.push(this.exerciseTitleChannelComponent.titleChannelNameComponent?.formValidChanges.subscribe(() => this.calculateFormValid())); - this.inputFieldSubscriptions.push(this.shortNameField.valueChanges?.subscribe(() => this.calculateFormValid())); - this.inputFieldSubscriptions.push(this.checkoutSolutionRepositoryField?.valueChanges?.subscribe(() => this.calculateFormValid())); - this.inputFieldSubscriptions.push(this.recreateBuildPlansField?.valueChanges?.subscribe(() => this.calculateFormValid())); - this.inputFieldSubscriptions.push(this.updateTemplateFilesField?.valueChanges?.subscribe(() => this.calculateFormValid())); - this.tableEditableFields?.changes.subscribe((fields: QueryList) => { - fields.toArray().forEach((field) => this.inputFieldSubscriptions.push(field.editingInput.valueChanges?.subscribe(() => this.calculateFormValid()))); - }); } ngOnDestroy(): void { @@ -53,52 +36,7 @@ export class ProgrammingExerciseInformationComponent implements AfterViewInit, O } calculateFormValid() { - const isCheckoutSolutionRepositoryValid = this.isCheckoutSolutionRepositoryValid(); - const isRecreateBuildPlansValid = this.isRecreateBuildPlansValid(); - const isUpdateTemplateFilesValid = this.isUpdateTemplateFilesValid(); - const areAuxiliaryRepositoriesValid = this.areAuxiliaryRepositoriesValid(); - this.formValid = Boolean( - this.exerciseTitleChannelComponent.titleChannelNameComponent?.formValid && - !this.shortNameField.invalid && - isCheckoutSolutionRepositoryValid && - isRecreateBuildPlansValid && - isUpdateTemplateFilesValid && - areAuxiliaryRepositoriesValid, - ); + this.formValid = this.exerciseTitleChannelComponent.titleChannelNameComponent?.formValid; this.formValidChanges.next(this.formValid); } - - areAuxiliaryRepositoriesValid(): boolean { - return ( - (every( - this.tableEditableFields?.map((field) => field.editingInput.valid), - Boolean, - ) && - !this.programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateDirectories && - !this.programmingExerciseCreationConfig.auxiliaryRepositoryDuplicateNames) || - !this.programmingExercise.auxiliaryRepositories?.length - ); - } - - isUpdateTemplateFilesValid(): boolean { - return ( - this.updateTemplateFilesField?.valid || - !this.programmingExerciseCreationConfig.isImportFromExistingExercise || - this.programmingExercise.projectType === ProjectType.PLAIN_GRADLE || - this.programmingExercise.projectType === ProjectType.GRADLE_GRADLE - ); - } - - isRecreateBuildPlansValid(): boolean { - return this.recreateBuildPlansField?.valid || !this.programmingExerciseCreationConfig.isImportFromExistingExercise; - } - - isCheckoutSolutionRepositoryValid(): boolean { - return Boolean( - this.checkoutSolutionRepositoryField?.valid || - this.programmingExercise.id || - !this.programmingExercise.programmingLanguage || - !this.programmingExerciseCreationConfig.checkoutSolutionRepositoryAllowed, - ); - } } diff --git a/src/main/webapp/app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component.ts b/src/main/webapp/app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component.ts index 30021b146f48..bed76e8e3cc7 100644 --- a/src/main/webapp/app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component.ts +++ b/src/main/webapp/app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component.ts @@ -16,9 +16,9 @@ import { ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent } from 'app/ex imports: [ArtemisSharedComponentModule, ArtemisSharedCommonModule, ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent], }) export class ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent implements OnInit, OnChanges, OnDestroy { - @Input() programmingExercise: ProgrammingExercise; + @Input({ required: true }) programmingExercise: ProgrammingExercise; @Input() programmingLanguage?: ProgrammingLanguage; - @Input() isLocal: boolean; + @Input() isLocal: boolean = false; @Input() checkoutSolutionRepository?: boolean = true; constructor(private programmingExerciseService: ProgrammingExerciseService) {} diff --git a/src/main/webapp/app/shared/components/button.component.ts b/src/main/webapp/app/shared/components/button.component.ts index 5f488b280767..e4e952305706 100644 --- a/src/main/webapp/app/shared/components/button.component.ts +++ b/src/main/webapp/app/shared/components/button.component.ts @@ -48,6 +48,8 @@ export enum TooltipPlacement { templateUrl: './button.component.html', }) export class ButtonComponent { + protected readonly faCircleNotch = faCircleNotch; + @Input() btnType = ButtonType.PRIMARY; @Input() btnSize = ButtonSize.MEDIUM; // Fa-icon name. @@ -65,7 +67,4 @@ export class ButtonComponent { @Input() shouldToggle = false; @Output() onClick = new EventEmitter(); - - // Icons - readonly faCircleNotch = faCircleNotch; } diff --git a/src/main/webapp/i18n/de/programmingExercise.json b/src/main/webapp/i18n/de/programmingExercise.json index ae25116ddc7a..b7811a7537d5 100644 --- a/src/main/webapp/i18n/de/programmingExercise.json +++ b/src/main/webapp/i18n/de/programmingExercise.json @@ -63,15 +63,18 @@ }, "detailedSteps": { "generalInfoStepTitle": "Allgemein", - "generalInfoStepMessage": "Eingabe von Informationen, die die Grundlage für die Programmieraufgabe bilden", + "generalInfoStepMessage": "Eingabe von Informationen, die die Grundlage für die Programmieraufgabe bilden.", "difficultyStepTitle": "Modus", - "difficultyStepMessage": "Konfiguration der Einstellungen bezüglich der Bearbeitung und Kollaboration der Aufgabe", + "difficultyStepMessage": "Konfiguration der Einstellungen bezüglich der Bearbeitung und Kollaboration der Aufgabe.", + "buildDetailsStepTitle": "Build Details", + "buildDetailsStepMessage": "Konfiguration der Repositories für die Aufgabe.", + "buildDetailsStepMessageLocalCI": "Konfiguration der Repositories und Checkout Directories für die Aufgabe.", "languageStepTitle": "Sprache", - "languageStepMessage": "Wähle die gewünschte Programmiersprache aus und konfiguriere zusätzliche Funktionen bezüglich der Build Umgebung", + "languageStepMessage": "Wähle die gewünschte Programmiersprache aus und konfiguriere zusätzliche Funktionen bezüglich der Build Umgebung.", "problemStepTitle": "Aufgabe", - "problemStepMessage": "Erstelle eine detaillierte Aufgabenstellung für die Übung mit klaren Schritten und Erwartungen an die Studierenden", + "problemStepMessage": "Erstelle eine detaillierte Aufgabenstellung für die Übung mit klaren Schritten und Erwartungen an die Studierenden.", "gradingStepTitle": "Bewertung", - "gradingStepMessage": "Definiere die Bewertungskriterien für diese Aufgabe und wie sich das Ergebnis auf die Gesamtnote des Kurses auswirkt" + "gradingStepMessage": "Definiere die Bewertungskriterien für diese Aufgabe und wie sich das Ergebnis auf die Gesamtnote des Kurses auswirkt." }, "gradingLabels": { "points": "Es gibt insgesamt maximal {maxPoints} Punkte bei dieser {exerciseType} zu erreichen.", diff --git a/src/main/webapp/i18n/en/programmingExercise.json b/src/main/webapp/i18n/en/programmingExercise.json index e199355c4a82..fd7382e88a90 100644 --- a/src/main/webapp/i18n/en/programmingExercise.json +++ b/src/main/webapp/i18n/en/programmingExercise.json @@ -74,12 +74,15 @@ }, "detailedSteps": { "generalInfoStepTitle": "General", - "generalInfoStepMessage": "Input essential information that forms the foundation of your programming exercise", + "generalInfoStepMessage": "Input essential information that forms the foundation of your programming exercise.", "difficultyStepTitle": "Mode", "buildPlansTitle": "Build Plans", - "difficultyStepMessage": "Configure the settings related to the execution and collaboration aspects of the programming exercise", + "difficultyStepMessage": "Configure the settings related to the execution and collaboration aspects of the programming exercise.", + "buildDetailsStepTitle": "Build Details", + "buildDetailsStepMessage": "Configure the repositories for the exercise.", + "buildDetailsStepMessageLocalCI": "Configure the repositories and checkout directories for the exercise.", "languageStepTitle": "Language", - "languageStepMessage": "Configure the specific details related to the programming language and build mechanism used in the exercise", + "languageStepMessage": "Configure the specific details related to the programming language and build mechanism used in the exercise.", "problemStepTitle": "Problem", "problemStepMessage": "Create a comprehensive problem statement for the new exercise, including detailed tasks that clearly outline the required actions and expectations for students.", "gradingStepTitle": "Grading", diff --git a/src/test/javascript/spec/component/programming-exercise/programming-exercise-update.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/programming-exercise-update.component.spec.ts index c4f40f4b79dc..9c6c7f02186f 100644 --- a/src/test/javascript/spec/component/programming-exercise/programming-exercise-update.component.spec.ts +++ b/src/test/javascript/spec/component/programming-exercise/programming-exercise-update.component.spec.ts @@ -67,6 +67,7 @@ import { AlertService, AlertType } from 'app/core/util/alert.service'; import { FormStatusBarComponent } from 'app/forms/form-status-bar/form-status-bar.component'; import { FormFooterComponent } from 'app/forms/form-footer/form-footer.component'; import { ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component'; +import { ProgrammingExerciseBuildDetailsComponent } from 'app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component'; describe('ProgrammingExerciseUpdateComponent', () => { const courseId = 1; @@ -127,6 +128,7 @@ describe('ProgrammingExerciseUpdateComponent', () => { MockComponent(ModePickerComponent), MockComponent(ExerciseUpdateNotificationComponent), MockComponent(ExerciseUpdatePlagiarismComponent), + MockComponent(ProgrammingExerciseBuildDetailsComponent), ], providers: [ { provide: LocalStorageService, useClass: MockSyncStorage }, @@ -991,12 +993,13 @@ describe('ProgrammingExerciseUpdateComponent', () => { formValid: true, }, } as ProgrammingExerciseDifficultyComponent; + comp.exerciseBuildDetailsComponent = { formValidChanges: new Subject(), formValid: true } as ProgrammingExerciseBuildDetailsComponent; comp.exerciseLanguageComponent = { formValidChanges: new Subject(), formValid: true } as ProgrammingExerciseLanguageComponent; comp.exerciseGradingComponent = { formValidChanges: new Subject(), formValid: true } as ProgrammingExerciseGradingComponent; comp.exercisePlagiarismComponent = { formValidChanges: new Subject(), formValid: true } as ExerciseUpdatePlagiarismComponent; comp.ngAfterViewInit(); - expect(comp.inputFieldSubscriptions).toHaveLength(5); + expect(comp.inputFieldSubscriptions).toHaveLength(6); comp.calculateFormStatusSections(); for (const section of comp.formStatusSections) { @@ -1018,10 +1021,10 @@ describe('ProgrammingExerciseUpdateComponent', () => { comp.programmingExercise.allowOfflineIde = false; comp.programmingExercise.allowOnlineEditor = false; comp.calculateFormStatusSections(); - expect(comp.formStatusSections[1].valid).toBeFalse(); + expect(comp.formStatusSections[2].valid).toBeFalse(); comp.programmingExercise.allowOnlineEditor = true; comp.calculateFormStatusSections(); - expect(comp.formStatusSections[1].valid).toBeTrue(); + expect(comp.formStatusSections[2].valid).toBeTrue(); comp.ngOnDestroy(); diff --git a/src/test/javascript/spec/component/programming-exercise/update-components/programming-exercise-build-details.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/update-components/programming-exercise-build-details.component.spec.ts new file mode 100644 index 000000000000..98e7c5db2fd2 --- /dev/null +++ b/src/test/javascript/spec/component/programming-exercise/update-components/programming-exercise-build-details.component.spec.ts @@ -0,0 +1,85 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockComponent, MockModule, MockPipe } from 'ng-mocks'; +import { ActivatedRoute } from '@angular/router'; +import { Subject, of } from 'rxjs'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; +import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/programming-exercise-difficulty.component'; +import { CheckboxControlValueAccessor, DefaultValueAccessor, NgModel, NumberValueAccessor, SelectControlValueAccessor } from '@angular/forms'; +import { DifficultyPickerComponent } from 'app/exercises/shared/difficulty-picker/difficulty-picker.component'; +import { TeamConfigFormGroupComponent } from 'app/exercises/shared/team-config-form-group/team-config-form-group.component'; +import { programmingExerciseCreationConfigMock } from './programming-exercise-creation-config-mock'; +import { ProgrammingExercise } from 'app/entities/programming-exercise.model'; +import { ProgrammingExerciseBuildDetailsComponent } from 'app/exercises/programming/manage/update/update-components/programming-exercise-build-details.component'; +import { AddAuxiliaryRepositoryButtonComponent } from 'app/exercises/programming/manage/update/add-auxiliary-repository-button.component'; +import { RemoveAuxiliaryRepositoryButtonComponent } from 'app/exercises/programming/manage/update/remove-auxiliary-repository-button.component'; +import { ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-build-plan-checkout-directories.component'; +import { ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; +import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; +import { ArtemisTableModule } from 'app/shared/table/table.module'; +import { TableEditableFieldComponent } from 'app/shared/table/table-editable-field.component'; +import { QueryList } from '@angular/core'; + +describe('ProgrammingExerciseBuildDetailsComponent', () => { + let fixture: ComponentFixture; + let comp: ProgrammingExerciseBuildDetailsComponent; + + beforeEach(() => { + let NgxDatatableModule; + TestBed.configureTestingModule({ + imports: [MockModule(ArtemisSharedModule), MockModule(ArtemisSharedComponentModule), MockModule(ArtemisTableModule), NgxDatatableModule], + declarations: [ + CheckboxControlValueAccessor, + DefaultValueAccessor, + SelectControlValueAccessor, + NumberValueAccessor, + NgModel, + ProgrammingExerciseDifficultyComponent, + MockComponent(DifficultyPickerComponent), + MockComponent(TeamConfigFormGroupComponent), + MockPipe(ArtemisTranslatePipe), + MockComponent(AddAuxiliaryRepositoryButtonComponent), + MockComponent(RemoveAuxiliaryRepositoryButtonComponent), + MockComponent(ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent), + MockComponent(ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent), + ProgrammingExerciseBuildDetailsComponent, + ], + providers: [ + { + provide: ActivatedRoute, + useValue: { queryParams: of({}) }, + }, + ], + schemas: [], + }) + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(ProgrammingExerciseBuildDetailsComponent); + comp = fixture.componentInstance; + comp.programmingExercise = new ProgrammingExercise(undefined, undefined); + comp.programmingExerciseCreationConfig = programmingExerciseCreationConfigMock; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should should calculate Form Sections correctly', () => { + const calculateFormValidSpy = jest.spyOn(comp, 'calculateFormValid'); + const editableField = { editingInput: { valueChanges: new Subject(), valid: true } } as any as TableEditableFieldComponent; + comp.shortNameField = { valueChanges: new Subject(), valid: true } as any as NgModel; + comp.checkoutSolutionRepositoryField = { valueChanges: new Subject(), valid: true } as any as NgModel; + comp.recreateBuildPlansField = { valueChanges: new Subject(), valid: true } as any as NgModel; + comp.updateTemplateFilesField = { valueChanges: new Subject(), valid: true } as any as NgModel; + comp.tableEditableFields = { changes: new Subject() } as any as QueryList; + comp.ngAfterViewInit(); + (comp.tableEditableFields?.changes as Subject).next({ toArray: () => [editableField] } as any as QueryList); + (comp.shortNameField.valueChanges as Subject).next(false); + (comp.checkoutSolutionRepositoryField.valueChanges as Subject).next(false); + (comp.recreateBuildPlansField.valueChanges as Subject).next(false); + (comp.updateTemplateFilesField.valueChanges as Subject).next(false); + (editableField.editingInput.valueChanges as Subject).next(false); + expect(calculateFormValidSpy).toHaveBeenCalledTimes(5); + }); +}); diff --git a/src/test/javascript/spec/component/programming-exercise/update-components/programming-exercise-information.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/update-components/programming-exercise-information.component.spec.ts index 30259859d720..d7f6f539a424 100644 --- a/src/test/javascript/spec/component/programming-exercise/update-components/programming-exercise-information.component.spec.ts +++ b/src/test/javascript/spec/component/programming-exercise/update-components/programming-exercise-information.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockComponent, MockPipe } from 'ng-mocks'; import { ActivatedRoute } from '@angular/router'; import { Subject, of } from 'rxjs'; @@ -12,8 +12,6 @@ import { CategorySelectorComponent } from 'app/shared/category-selector/category import { AddAuxiliaryRepositoryButtonComponent } from 'app/exercises/programming/manage/update/add-auxiliary-repository-button.component'; import { programmingExerciseCreationConfigMock } from './programming-exercise-creation-config-mock'; import { ExerciseTitleChannelNameComponent } from 'app/exercises/shared/exercise-title-channel-name/exercise-title-channel-name.component'; -import { TableEditableFieldComponent } from 'app/shared/table/table-editable-field.component'; -import { QueryList } from '@angular/core'; import { ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component'; describe('ProgrammingExerciseInformationComponent', () => { @@ -58,28 +56,11 @@ describe('ProgrammingExerciseInformationComponent', () => { jest.restoreAllMocks(); }); - it('should initialize', fakeAsync(() => { - fixture.detectChanges(); - expect(comp).not.toBeNull(); - })); - it('should should calculate Form Sections correctly', () => { const calculateFormValidSpy = jest.spyOn(comp, 'calculateFormValid'); - const editableField = { editingInput: { valueChanges: new Subject(), valid: true } } as any as TableEditableFieldComponent; comp.exerciseTitleChannelComponent = { titleChannelNameComponent: { formValidChanges: new Subject(), formValid: true } } as ExerciseTitleChannelNameComponent; - comp.shortNameField = { valueChanges: new Subject(), valid: true } as any as NgModel; - comp.checkoutSolutionRepositoryField = { valueChanges: new Subject(), valid: true } as any as NgModel; - comp.recreateBuildPlansField = { valueChanges: new Subject(), valid: true } as any as NgModel; - comp.updateTemplateFilesField = { valueChanges: new Subject(), valid: true } as any as NgModel; - comp.tableEditableFields = { changes: new Subject() } as any as QueryList; comp.ngAfterViewInit(); - (comp.tableEditableFields.changes as Subject).next({ toArray: () => [editableField] } as any as QueryList); comp.exerciseTitleChannelComponent.titleChannelNameComponent.formValidChanges.next(false); - (comp.shortNameField.valueChanges as Subject).next(false); - (comp.checkoutSolutionRepositoryField.valueChanges as Subject).next(false); - (comp.recreateBuildPlansField.valueChanges as Subject).next(false); - (comp.updateTemplateFilesField.valueChanges as Subject).next(false); - (editableField.editingInput.valueChanges as Subject).next(false); - expect(calculateFormValidSpy).toHaveBeenCalledTimes(6); + expect(calculateFormValidSpy).toHaveBeenCalledOnce(); }); });