diff --git a/src/assets/wise5/components/match/match-student/match-student-choice-reuse/match-student-choice-reuse.ts b/src/assets/wise5/components/match/match-student/match-student-choice-reuse/match-student-choice-reuse.ts index c919e817a14..e69c1ea5753 100644 --- a/src/assets/wise5/components/match/match-student/match-student-choice-reuse/match-student-choice-reuse.ts +++ b/src/assets/wise5/components/match/match-student/match-student-choice-reuse/match-student-choice-reuse.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { MatchStudentDefault } from '../match-student-default/match-student-default.component'; +import { MatchStudentDefaultComponent } from '../match-student-default/match-student-default.component'; import { moveItem } from '../move-item'; import { MatchCdkDragDrop } from '../MatchCdkDragDrop'; import { Container } from '../container'; @@ -10,7 +10,7 @@ import { copy } from '../../../../common/object/object'; styleUrls: ['../match-student-default/match-student-default.component.scss'], templateUrl: '../match-student-default/match-student-default.component.html' }) -export class MatchStudentChoiceReuse extends MatchStudentDefault { +export class MatchStudentChoiceReuse extends MatchStudentDefaultComponent { protected drop(event: MatchCdkDragDrop): void { moveItem(event); event.container.element.nativeElement.classList.remove('primary-bg'); diff --git a/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.html b/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.html index b9953c4e561..a33ea97671e 100644 --- a/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.html +++ b/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.html @@ -1,4 +1,4 @@ - +

-
+ @if (componentContent.canCreateChoices) { -
+ }
    [cdkDropListData]="{ isSourceBucket: true, items: buckets[0].items }" (cdkDropListDropped)="drop($event)" > -
  • - -
  • -
-
-
-
-
-
-
-

-
-
    + @for (item of buckets[0].items; track item.id; let position = $index) {
  • (onStudentDataChanged)="studentDataChanged()" />
  • -
-
+ } +
+
+ @for (bucket of buckets.slice(1); track bucket.id) { +
+
+
+

+
+
    + @for (item of bucket.items; track item.id; let position = $index) { +
  • + +
  • + } +
+
+
+ } +
[isLatestComponentStateSubmit]="isLatestComponentStateSubmit" [submitCounter]="submitCounter" /> - - - - +@if (isSaveOrSubmitButtonVisible) { + +} +@if (mode === 'student') { + +} diff --git a/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.spec.ts b/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.spec.ts index ae7d4a588e8..01770d89b85 100644 --- a/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.spec.ts +++ b/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.spec.ts @@ -1,16 +1,15 @@ // @ts-nocheck -import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { StudentTeacherCommonServicesModule } from '../../../../../../app/student-teacher-common-services.module'; import { Component } from '../../../../common/Component'; import { copy } from '../../../../common/object/object'; import { ClickToSnipImageService } from '../../../../services/clickToSnipImageService'; import { ProjectService } from '../../../../services/projectService'; -import { MatchStudentDefault } from './match-student-default.component'; +import { MatchStudentDefaultComponent } from './match-student-default.component'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -let component: MatchStudentDefault; -let fixture: ComponentFixture; +let component: MatchStudentDefaultComponent; +let fixture: ComponentFixture; let bucket1: any; let bucket2: any; let bucket3: any; @@ -49,12 +48,10 @@ let starterBucketLabel = 'Starter Choices'; describe('MatchStudentDefaultComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [MatchStudentDefault], - schemas: [NO_ERRORS_SCHEMA], - imports: [StudentTeacherCommonServicesModule], + imports: [MatchStudentDefaultComponent, StudentTeacherCommonServicesModule], providers: [provideHttpClient(withInterceptorsFromDi())] }); - fixture = TestBed.createComponent(MatchStudentDefault); + fixture = TestBed.createComponent(MatchStudentDefaultComponent); component = fixture.componentInstance; choice1 = createChoice(choiceId1, choiceValue1); choice2 = createChoice(choiceId2, choiceValue2); @@ -94,6 +91,7 @@ describe('MatchStudentDefaultComponent', () => { }; component.component = new Component(componentContent, nodeId); spyOn(TestBed.inject(ProjectService), 'getComponent').and.returnValue(copy(componentContent)); + spyOn(TestBed.inject(ProjectService), 'getThemeSettings').and.returnValue({}); spyOn(component, 'subscribeToSubscriptions').and.callFake(() => {}); spyOn(component, 'broadcastDoneRenderingComponent').and.callFake(() => {}); spyOn(component, 'isAddToNotebookEnabled').and.callFake(() => { @@ -381,32 +379,27 @@ function getCorrectness() { it('should get correctness from feedback object when it is true', () => { const isCorrect = true; const feedbackObject = createFeedback(choiceId1, '', isCorrect); - expect(component.getCorrectness(feedbackObject, true, 0)).toEqual(isCorrect); + expect(component.getCorrectness(feedbackObject, 0)).toEqual(isCorrect); }); it('should get correctness from feedback object when it is false', () => { const isCorrect = false; const feedbackObject = createFeedback(choiceId1, '', isCorrect); - expect(component.getCorrectness(feedbackObject, true, 0)).toEqual(isCorrect); + expect(component.getCorrectness(feedbackObject, 0)).toEqual(isCorrect); }); it(`should get correctness from feedback object when position matters and it is in the correct position`, () => { const isCorrect = true; const feedbackObject = createFeedback(choiceId1, '', isCorrect, 1); - expect(component.getCorrectness(feedbackObject, true, 1)).toEqual(isCorrect); + expect(component.getCorrectness(feedbackObject, 1)).toEqual(isCorrect); }); it(`should get correctness from feedback object when position matters and it is not in the correct position`, () => { const isCorrect = false; const feedbackObject = createFeedback(choiceId1, '', isCorrect, 1); - expect(component.getCorrectness(feedbackObject, true, 2)).toEqual(isCorrect); - }); - - it('should get correctness from feedback object there is not correct answer', () => { - const feedbackObject = createFeedback(choiceId1, '', false, 1); - expect(component.getCorrectness(feedbackObject, false, 1)).toEqual(null); + expect(component.getCorrectness(feedbackObject, 2)).toEqual(isCorrect); }); }); } diff --git a/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.ts b/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.ts index 5050da6fdd9..3e96acc73d1 100644 --- a/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.ts +++ b/src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.ts @@ -1,51 +1,69 @@ +import { AddChoiceButtonComponent } from '../add-choice-button/add-choice-button.component'; +import { AddMatchChoiceDialogComponent } from '../add-match-choice-dialog/add-match-choice-dialog'; +import { AnnotationService } from '../../../../services/annotationService'; +import { Bucket, mergeBucket } from '../../bucket'; import { CdkDragEnter, CdkDragExit, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; +import { Choice, createChoiceFromNotebookItem } from '../../choice'; +import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; +import { ComponentAnnotationsComponent } from '../../../../directives/componentAnnotations/component-annotations.component'; +import { ComponentHeaderComponent } from '../../../../directives/component-header/component-header.component'; +import { ComponentSaveSubmitButtonsComponent } from '../../../../directives/component-save-submit-buttons/component-save-submit-buttons.component'; +import { ComponentService } from '../../../componentService'; +import { ComponentStudent } from '../../../component-student.component'; +import { ConfigService } from '../../../../services/configService'; +import { Container } from '../container'; +import { copy } from '../../../../common/object/object'; +import { CRaterService } from '../../../../services/cRaterService'; +import { CRaterRubric, getUniqueIdeas } from '../../../common/cRater/CRaterRubric'; +import { DragDropModule } from '@angular/cdk/drag-drop'; import { filter } from 'rxjs'; -import { NotebookItem } from '../../../../common/notebook/notebookItem'; +import { FlexLayoutModule } from '@angular/flex-layout'; import { generateRandomKey } from '../../../../common/string/string'; -import { AnnotationService } from '../../../../services/annotationService'; -import { ConfigService } from '../../../../services/configService'; +import { hasConnectedComponent } from '../../../../common/ComponentContent'; +import { Item } from '../item'; +import { MatchCdkDragDrop } from '../MatchCdkDragDrop'; +import { MatchChoiceItemComponent } from '../../match-choice-item/match-choice-item.component'; +import { MatchFeedbackSectionComponent } from '../match-feedback-section/match-feedback-section.component'; +import { MatchService } from '../../matchService'; +import { MatDialog } from '@angular/material/dialog'; import { NodeService } from '../../../../services/nodeService'; +import { NotebookItem } from '../../../../common/notebook/notebookItem'; import { NotebookService } from '../../../../services/notebookService'; import { ProjectService } from '../../../../services/projectService'; import { StudentAssetService } from '../../../../services/studentAssetService'; import { StudentDataService } from '../../../../services/studentDataService'; -import { ComponentStudent } from '../../../component-student.component'; -import { ComponentService } from '../../../componentService'; -import { Choice, createChoiceFromNotebookItem } from '../../choice'; -import { MatchService } from '../../matchService'; -import { AddMatchChoiceDialogComponent } from '../add-match-choice-dialog/add-match-choice-dialog'; -import { copy } from '../../../../common/object/object'; -import { MatchCdkDragDrop } from '../MatchCdkDragDrop'; -import { Container } from '../container'; -import { Item } from '../item'; -import { hasConnectedComponent } from '../../../../common/ComponentContent'; -import { Bucket, mergeBucket } from '../../bucket'; -import { CRaterService } from '../../../../services/cRaterService'; -import { CRaterRubric, getUniqueIdeas } from '../../../common/cRater/CRaterRubric'; @Component({ - templateUrl: 'match-student-default.component.html', - styleUrl: 'match-student-default.component.scss' + imports: [ + AddChoiceButtonComponent, + CommonModule, + ComponentAnnotationsComponent, + ComponentHeaderComponent, + ComponentSaveSubmitButtonsComponent, + DragDropModule, + FlexLayoutModule, + MatchChoiceItemComponent, + MatchFeedbackSectionComponent + ], + standalone: true, + styleUrl: 'match-student-default.component.scss', + templateUrl: 'match-student-default.component.html' }) -export class MatchStudentDefault extends ComponentStudent { - autoScroll: any = require('dom-autoscroller'); - buckets: any[] = []; - bucketStyle: string = ''; - bucketWidth: number = 100; - choices: Choice[] = []; - choiceStyle: any = ''; - hasCorrectAnswer: boolean = false; - isCorrect: boolean = false; - isLatestComponentStateSubmit: boolean = false; - sourceBucket: any; - sourceBucketId: string = '0'; +export class MatchStudentDefaultComponent extends ComponentStudent { + private autoScroll: any = require('dom-autoscroller'); + protected buckets: any[] = []; + protected choices: Choice[] = []; + protected hasCorrectAnswer: boolean = false; + protected isCorrect: boolean = false; + protected isLatestComponentStateSubmit: boolean = false; + private sourceBucket: any; + protected sourceBucketId: string = '0'; constructor( protected annotationService: AnnotationService, @@ -81,14 +99,16 @@ export class MatchStudentDefault extends ComponentStudent { this.subscribeToNewNotes(); } this.initializeBuckets(); - if (hasConnectedComponent(this.componentContent, 'showWork')) { + if ( + hasConnectedComponent(this.componentContent, 'showWork') || + this.component.hasConnectedComponent() + ) { this.handleConnectedComponents(); - } else if ( + } + if ( this.matchService.componentStateHasStudentWork(this.componentState, this.componentContent) ) { this.setStudentWork(this.componentState); - } else if (this.component.hasConnectedComponent()) { - this.handleConnectedComponents(); } this.isLatestComponentStateSubmit = this.componentState != null && this.componentState.isSubmit; this.tryDisableComponent(); @@ -143,7 +163,7 @@ export class MatchStudentDefault extends ComponentStudent { ); } - addNotebookItemToSourceBucket(notebookItem: NotebookItem): void { + private addNotebookItemToSourceBucket(notebookItem: NotebookItem): void { const choice = createChoiceFromNotebookItem(notebookItem); this.choices.push(choice); this.getBucketById(this.sourceBucketId).items.push(choice); @@ -183,7 +203,7 @@ export class MatchStudentDefault extends ComponentStudent { private addComponentStateChoicesToBuckets(componentState: any): void { const choiceIds = this.choices.map((choice) => choice.id); - for (const componentStateBucket of componentState.studentData.buckets) { + componentState.studentData.buckets.forEach((componentStateBucket) => { if (this.buckets.some((bucket) => bucket.id === componentStateBucket.id)) { const bucket = this.getBucketById(componentStateBucket.id); componentStateBucket.items.forEach((componentStateChoice) => { @@ -194,7 +214,8 @@ export class MatchStudentDefault extends ComponentStudent { } }); } - } + }); + const sourceBucket = this.getBucketById(this.sourceBucketId); choiceIds.forEach((choiceId) => this.addAuthoredChoiceToBucket(choiceId, sourceBucket)); } @@ -287,7 +308,7 @@ export class MatchStudentDefault extends ComponentStudent { protected getUpdatedChoicesSinceLastSubmit(latestSubmitComponentState: any): string[] { const updatedChoices = []; const previousBuckets = latestSubmitComponentState.studentData.buckets; - for (const currentBucket of this.buckets) { + this.buckets.forEach((currentBucket) => { const { currentBucketChoiceIds, previousBucketChoiceIds } = this.getPreviousAndCurrentChoiceIds(previousBuckets, currentBucket); for ( @@ -301,7 +322,7 @@ export class MatchStudentDefault extends ComponentStudent { updatedChoices.push(currentBucketChoiceIds[currentChoiceIndex]); } } - } + }); return updatedChoices; } @@ -355,30 +376,24 @@ export class MatchStudentDefault extends ComponentStudent { buckets: any[] = this.buckets ): void { let isCorrect = true; - for (const bucket of buckets) { + buckets.forEach((bucket) => { const bucketId = bucket.id; const items = bucket.items; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - const position = i + 1; + + let i = 0; + items.forEach((item) => { + const position = i++ + 1; const choiceId = item.id; - if ( - choiceIdsExcludedFromFeedback.includes(choiceId) || - this.getFeedbackObject(bucketId, choiceId) == null - ) { - item.feedback = null; - } else { - const isChoiceCorrect = this.checkAnswerAndDisplayFeedback( - bucketId, - item, - position, - this.hasCorrectAnswer - ); - isCorrect &&= isChoiceCorrect; - } - this.matchService.setItemStatus(item, this.hasCorrectAnswer); - } - } + isCorrect = this.checkItemAnswer( + choiceIdsExcludedFromFeedback, + choiceId, + bucketId, + item, + position, + isCorrect + ); + }); + }); if (this.hasCorrectAnswer) { this.isCorrect = isCorrect; @@ -387,7 +402,33 @@ export class MatchStudentDefault extends ComponentStudent { } } - checkAnswerAndDisplayFeedback( + private checkItemAnswer( + choiceIdsExcludedFromFeedback: string[], + choiceId: any, + bucketId: any, + item: any, + position: number, + isCorrect: boolean + ): boolean { + if ( + choiceIdsExcludedFromFeedback.includes(choiceId) || + this.getFeedbackObject(bucketId, choiceId) == null + ) { + item.feedback = null; + } else { + const isChoiceCorrect = this.checkAnswerAndDisplayFeedback( + bucketId, + item, + position, + this.hasCorrectAnswer + ); + isCorrect &&= isChoiceCorrect; + } + this.matchService.setItemStatus(item, this.hasCorrectAnswer); + return isCorrect; + } + + private checkAnswerAndDisplayFeedback( bucketId: string, choice: any, position: number, @@ -395,7 +436,7 @@ export class MatchStudentDefault extends ComponentStudent { ): boolean { const feedbackObject = this.getFeedbackObject(bucketId, choice.id); choice.feedback = this.getFeedback(feedbackObject, hasCorrectAnswer, position); - const isCorrect = this.getCorrectness(feedbackObject, hasCorrectAnswer, position); + const isCorrect = hasCorrectAnswer ? this.getCorrectness(feedbackObject, position) : null; choice.isCorrect = isCorrect; if (this.doesPositionMatter(feedbackObject.position)) { choice.isIncorrectPosition = feedbackObject.position !== position; @@ -445,17 +486,13 @@ export class MatchStudentDefault extends ComponentStudent { return feedbackText; } - getCorrectness(feedbackObject: any, hasCorrectAnswer: boolean, position: number): boolean { - if (!hasCorrectAnswer) { - return null; - } else if (this.doesPositionMatter(feedbackObject.position)) { - return feedbackObject.position === position; - } else { - return feedbackObject.isCorrect; - } + private getCorrectness(feedbackObject: any, position: number): boolean { + return this.doesPositionMatter(feedbackObject.position) + ? feedbackObject.position === position + : feedbackObject.isCorrect; } - getFeedbackObject(bucketId: string, choiceId: string): any { + private getFeedbackObject(bucketId: string, choiceId: string): any { return ( this.componentContent.feedback .find((bucketFeedback) => bucketFeedback.bucketId === bucketId) @@ -495,7 +532,7 @@ export class MatchStudentDefault extends ComponentStudent { }); } - createComponentStateObject(action: string): any { + private createComponentStateObject(action: string): any { const componentState: any = this.createNewComponentState(); componentState.componentType = 'Match'; componentState.nodeId = this.nodeId; @@ -524,13 +561,13 @@ export class MatchStudentDefault extends ComponentStudent { * @param buckets */ private cleanBuckets(originalComponentContent: any, buckets: any): any { - for (const bucket of buckets) { + buckets.forEach((bucket) => { bucket.value = this.getCleanedValue(originalComponentContent, bucket); - for (const item of bucket.items) { + bucket.items.forEach((item) => { item.value = this.getCleanedValue(originalComponentContent, item); delete item.status; - } - } + }); + }); return buckets; } @@ -539,7 +576,7 @@ export class MatchStudentDefault extends ComponentStudent { * injected into it such as onclick attributes and absolute asset paths. * @param matchObj */ - getCleanedValue(originalComponentContent: any, matchObj: any): string { + private getCleanedValue(originalComponentContent: any, matchObj: any): string { return ( originalComponentContent.buckets .concat(originalComponentContent.choices) @@ -560,7 +597,7 @@ export class MatchStudentDefault extends ComponentStudent { * @param {string} choiceId the choice id * @return {boolean} whether the choice has a correct position in any bucket */ - isAuthorHasSpecifiedACorrectPosition(choiceId: string): boolean { + private isAuthorHasSpecifiedACorrectPosition(choiceId: string): boolean { return this.componentContent.feedback.some((feedbackBucket) => feedbackBucket.choices.some( (choice) => choice.choiceId === choiceId && choice.position != null @@ -572,9 +609,7 @@ export class MatchStudentDefault extends ComponentStudent { const mergedBuckets = []; componentStates.forEach((componentState) => { if (componentState.componentType === 'Match') { - for (const bucket of componentState.studentData.buckets) { - mergeBucket(mergedBuckets, bucket); - } + componentState.studentData.buckets.forEach((bucket) => mergeBucket(mergedBuckets, bucket)); } else if (componentState.componentType === 'DialogGuidance') { this.addIdeasToSourceBucket( componentState.studentData.responses, diff --git a/src/assets/wise5/components/match/match-student/match-student.component.ts b/src/assets/wise5/components/match/match-student/match-student.component.ts index 15df25e774b..5e86c9b337d 100644 --- a/src/assets/wise5/components/match/match-student/match-student.component.ts +++ b/src/assets/wise5/components/match/match-student/match-student.component.ts @@ -10,7 +10,7 @@ import { createComponent } from '@angular/core'; import { MatchStudentChoiceReuse } from './match-student-choice-reuse/match-student-choice-reuse'; -import { MatchStudentDefault } from './match-student-default/match-student-default.component'; +import { MatchStudentDefaultComponent } from './match-student-default/match-student-default.component'; import { MatchContent } from '../MatchContent'; @Component({ @@ -26,13 +26,16 @@ export class MatchStudent { @Output() saveComponentStateEvent: EventEmitter = new EventEmitter(); @ViewChild('component') private componentElementRef: ElementRef; - constructor(private applicationRef: ApplicationRef, private injector: EnvironmentInjector) {} + constructor( + private applicationRef: ApplicationRef, + private injector: EnvironmentInjector + ) {} ngAfterViewInit(): void { this.componentRef = createComponent( (this.component.content as MatchContent).choiceReuseEnabled ? MatchStudentChoiceReuse - : MatchStudentDefault, + : MatchStudentDefaultComponent, { hostElement: this.componentElementRef.nativeElement, environmentInjector: this.injector diff --git a/src/assets/wise5/components/match/match-student/match-student.module.ts b/src/assets/wise5/components/match/match-student/match-student.module.ts index 24fbab686ad..5f0bc1d991e 100644 --- a/src/assets/wise5/components/match/match-student/match-student.module.ts +++ b/src/assets/wise5/components/match/match-student/match-student.module.ts @@ -5,16 +5,17 @@ import { MatchStudent } from './match-student.component'; import { StudentComponentModule } from '../../../../../app/student/student.component.module'; import { MatchCommonModule } from '../match-common.module'; import { MatchStudentChoiceReuse } from './match-student-choice-reuse/match-student-choice-reuse'; -import { MatchStudentDefault } from './match-student-default/match-student-default.component'; +import { MatchStudentDefaultComponent } from './match-student-default/match-student-default.component'; @NgModule({ - declarations: [MatchStudent, MatchStudentDefault, MatchStudentChoiceReuse], + declarations: [MatchStudent, MatchStudentChoiceReuse], imports: [ AddChoiceButtonComponent, MatchCommonModule, + MatchStudentDefaultComponent, StudentComponentModule, AddMatchChoiceDialogComponent ], - exports: [MatchStudent, AddMatchChoiceDialogComponent] + exports: [MatchStudent, MatchStudentDefaultComponent, AddMatchChoiceDialogComponent] }) export class MatchStudentModule {} diff --git a/src/messages.xlf b/src/messages.xlf index 46d7ad4a2ae..07a78c66038 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -10630,7 +10630,7 @@ Click "Cancel" to keep the invalid JSON open so you can fix it. src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.ts - 107 + 127 @@ -19525,14 +19525,14 @@ Warning: This will delete all existing choices and buckets in this component.Correct bucket but wrong position src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.ts - 434 + 475 Correct src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.ts - 441 + 482 src/assets/wise5/directives/summary-display/summary-display.component.ts @@ -19547,7 +19547,7 @@ Warning: This will delete all existing choices and buckets in this component.Incorrect src/assets/wise5/components/match/match-student/match-student-default/match-student-default.component.ts - 441 + 482 src/assets/wise5/directives/summary-display/summary-display.component.ts