Skip to content

Commit

Permalink
refactor(Authoring): Create select component component
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffreykwan committed Apr 28, 2024
1 parent 0830204 commit 1b9b54b
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<mat-form-field>
<mat-label i18n>Component</mat-label>
<mat-select [(ngModel)]="componentId" (ngModelChange)="componentChangedEvent.emit($event)">
<mat-option
*ngFor="let component of components; index as componentIndex"
[value]="component.id"
[disabled]="!componentToIsAllowed.get(component.id) || component.id === thisComponentId"
>
<span>{{ componentIndex + 1 }}. {{ component.type }}</span>
<span *ngIf="component.id === thisComponentId" i18n>(This Component)</span>
</mat-option>
</mat-select>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SelectComponentComponent } from './select-component.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { StudentTeacherCommonServicesModule } from '../../student-teacher-common-services.module';
import { SelectStepComponent } from '../select-step/select-step.component';

describe('SelectComponentComponent', () => {
let component: SelectComponentComponent;
let fixture: ComponentFixture<SelectComponentComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
BrowserAnimationsModule,
HttpClientTestingModule,
SelectStepComponent,
StudentTeacherCommonServicesModule
]
});
fixture = TestBed.createComponent(SelectComponentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { ProjectService } from '../../../assets/wise5/services/projectService';
import { ComponentContent } from '../../../assets/wise5/common/ComponentContent';

@Component({
selector: 'select-component',
templateUrl: './select-component.component.html',
standalone: true,
imports: [CommonModule, FormsModule, MatFormFieldModule, MatSelectModule]
})
export class SelectComponentComponent {
@Input() allowedComponentTypes: string[] = [];
@Output() componentChangedEvent: EventEmitter<string> = new EventEmitter<string>();
@Input() componentId: string;
protected components: ComponentContent[] = [];
protected componentToIsAllowed: Map<string, boolean> = new Map<string, boolean>();
@Input() nodeId: string;
@Input() thisComponentId: string;

constructor(private projectService: ProjectService) {}

ngOnChanges(changes: SimpleChanges): void {
if (changes.nodeId) {
this.nodeId = changes.nodeId.currentValue;
this.componentId = null;
this.calculateComponents(this.nodeId);
this.automaticallySetComponentIfPossible(this.nodeId);
}
}

private calculateComponents(nodeId: string): void {
this.components = this.projectService.getComponents(nodeId);
for (const component of this.components) {
this.componentToIsAllowed.set(
component.id,
this.allowedComponentTypes.includes(component.type)
);
}
}

private automaticallySetComponentIfPossible(nodeId: string): void {
let numAllowedComponents = 0;
let allowedComponent = null;
for (const component of this.projectService.getComponents(nodeId)) {
if (
this.allowedComponentTypes.includes(component.type) &&
component.id !== this.thisComponentId
) {
numAllowedComponents += 1;
allowedComponent = component;
}
}
if (numAllowedComponents === 1) {
this.componentId = allowedComponent.id;
this.componentChanged();
}
}

protected componentChanged(): void {
this.componentChangedEvent.emit(this.componentId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ComponentHarness } from '@angular/cdk/testing';
import { MatSelectHarness } from '@angular/material/select/testing';

export class SelectComponentHarness extends ComponentHarness {
static hostSelector = 'select-component';
getSelect = this.locatorFor(MatSelectHarness);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,10 @@
[nodeId]="referenceComponent.nodeId"
(stepChangedEvent)="stepChanged($event)"
></select-step>
<mat-form-field>
<mat-label i18n>Component</mat-label>
<mat-select [(ngModel)]="referenceComponent.componentId" (ngModelChange)="componentChanged()">
<mat-option
*ngFor="let component of components; index as componentIndex"
[value]="component.id"
[disabled]="!componentToIsAllowed.get(component.id) || component.id === thisComponentId"
>
<span>{{ componentIndex + 1 }}. {{ component.type }} </span>
<span *ngIf="component.id === thisComponentId" i18n>(This Component)</span>
</mat-option>
</mat-select>
</mat-form-field>
<select-component
[allowedComponentTypes]="allowedComponentTypes"
[nodeId]="referenceComponent.nodeId"
[componentId]="referenceComponent.componentId"
[thisComponentId]="thisComponentId"
(componentChangedEvent)="componentChanged($event)"
></select-component>
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import { StudentTeacherCommonServicesModule } from '../../student-teacher-common
import { SelectStepAndComponentComponent } from './select-step-and-component.component';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatSelectHarness } from '@angular/material/select/testing';
import { SelectStepAndComponentHarness } from './select-step-and-component.harness';
import { SelectStepComponent } from '../select-step/select-step.component';

let component: SelectStepAndComponentComponent;
const componentId1 = 'component1';
Expand All @@ -33,14 +31,14 @@ describe('SelectStepAndComponentComponent', () => {
HttpClientTestingModule,
SelectStepAndComponentComponent,
StudentTeacherCommonServicesModule
],
providers: [ProjectService]
]
}).compileComponents();
fixture = TestBed.createComponent(SelectStepAndComponentComponent);
loader = TestbedHarnessEnvironment.loader(fixture);
component = fixture.componentInstance;
component.referenceComponent = new ReferenceComponent(null, null);
component.allowedComponentTypes = ['OpenResponse'];
component.thisComponentId = componentId1;
projectService = TestBed.inject(ProjectService);
spyOn(projectService, 'getStepNodeIds').and.returnValue(nodeIds);
spyOn(projectService, 'getFlattenedProjectAsNodeIds').and.returnValue(nodeIds);
Expand All @@ -58,41 +56,45 @@ describe('SelectStepAndComponentComponent', () => {
SelectStepAndComponentHarness
);
});
selectComponent();
stepChanged();
selectStep();
});

function selectComponent() {
it('should disable certain options in the select component', async () => {
setUpThreeComponentsSpy('OpenResponse', 'Graph', 'OpenResponse');
component.referenceComponent.nodeId = nodeId1;
component.thisComponentId = componentId1;
component.ngOnInit();
const selects = await loader.getAllHarnesses(MatSelectHarness);
const selectComponent = selects[1];
await selectComponent.open();
const options = await selectComponent.getOptions();
expect(await options[0].isDisabled()).toBeTrue();
expect(await options[1].isDisabled()).toBeTrue();
expect(await options[2].isDisabled()).toBeFalse();
});
}

function stepChanged() {
describe('stepChanged()', () => {
it('should handle step changed when there are no allowed components', async () => {
await setComponentsAndCallStepChanged('Draw', 'Graph', 'Table');
expectReferenceComponentValues(nodeId1, null);
function selectStep() {
describe('selecting a step', () => {
describe('when the step has compnents that can and cannot be selected', () => {
it('disables certain options in the select component', async () => {
setUpThreeComponentsSpy('OpenResponse', 'Graph', 'OpenResponse');
const selectStepHarness = await harness.getSelectStep();
const selectStep = await selectStepHarness.getSelect();
await selectStep.open();
await selectStep.clickOptions({ text: nodeTitle1 });
const selectComponentHarness = await harness.getSelectComponent();
const selectComponent = await selectComponentHarness.getSelect();
await selectComponent.open();
const options = await selectComponent.getOptions();
expect(await options[0].isDisabled()).toBeTrue();
expect(await options[1].isDisabled()).toBeTrue();
expect(await options[2].isDisabled()).toBeFalse();
});
});

it('should handle step changed when there is one allowed component', async () => {
await setComponentsAndCallStepChanged('Draw', 'OpenResponse', 'Table');
expectReferenceComponentValues(nodeId1, componentId2);
describe('when the step has no components that can be selected', () => {
it('does not automatically select a component', async () => {
await setComponentsAndCallStepChanged('Draw', 'Graph', 'Table');
expectReferenceComponentValues(nodeId1, null);
});
});

it('should handle step changed when there are multiple allowed components', async () => {
await setComponentsAndCallStepChanged('Draw', 'OpenResponse', 'OpenResponse');
expectReferenceComponentValues(nodeId1, null);
describe('when the step has one component that can be selected', () => {
it('automatically selects the one allowed component', async () => {
await setComponentsAndCallStepChanged('Draw', 'OpenResponse', 'Table');
expectReferenceComponentValues(nodeId1, componentId2);
});
});
describe('when the step has many components that can be selected', () => {
it('doesn not automatically select a component', async () => {
await setComponentsAndCallStepChanged('Draw', 'OpenResponse', 'OpenResponse');
expectReferenceComponentValues(nodeId1, null);
});
});
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,76 +1,33 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ProjectService } from '../../../assets/wise5/services/projectService';
import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { ReferenceComponent } from '../../domain/referenceComponent';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { SelectStepComponent } from '../select-step/select-step.component';
import { SelectComponentComponent } from '../select-component/select-component.component';

@Component({
selector: 'select-step-and-component',
templateUrl: './select-step-and-component.component.html',
styleUrls: ['./select-step-and-component.component.scss'],
standalone: true,
imports: [CommonModule, FormsModule, MatFormFieldModule, MatSelectModule, SelectStepComponent]
imports: [SelectComponentComponent, SelectStepComponent]
})
export class SelectStepAndComponentComponent implements OnInit {
export class SelectStepAndComponentComponent {
@Input() allowedComponentTypes: string[] = [];
@Output() componentChange: EventEmitter<ReferenceComponent> = new EventEmitter();
protected components: any[] = [];
protected componentToIsAllowed: Map<string, boolean> = new Map<string, boolean>();
@Input() referenceComponent: ReferenceComponent;
@Output() stepChange: EventEmitter<ReferenceComponent> = new EventEmitter();
@Input() thisComponentId: string;

constructor(private projectService: ProjectService) {}

ngOnInit(): void {
if (this.referenceComponent.nodeId != null) {
this.calculateComponents(this.referenceComponent.nodeId);
if (this.referenceComponent.componentId == null) {
this.automaticallySetComponentIfPossible(this.referenceComponent.nodeId);
}
}
}

private calculateComponents(nodeId: string): void {
this.components = this.projectService.getComponents(nodeId);
for (const component of this.components) {
this.componentToIsAllowed.set(
component.id,
this.allowedComponentTypes.includes(component.type)
);
}
}
constructor(private changeDetector: ChangeDetectorRef) {}

protected stepChanged(nodeId: string): void {
this.referenceComponent.nodeId = nodeId;
this.referenceComponent.componentId = null;
this.automaticallySetComponentIfPossible(nodeId);
this.calculateComponents(nodeId);
this.changeDetector.detectChanges();
this.stepChange.emit(this.referenceComponent);
}

protected componentChanged(): void {
protected componentChanged(componentId: string): void {
this.referenceComponent.componentId = componentId;
this.changeDetector.detectChanges();
this.componentChange.emit(this.referenceComponent);
}

private automaticallySetComponentIfPossible(nodeId: string): void {
let numAllowedComponents = 0;
let allowedComponent = null;
for (const component of this.projectService.getComponents(nodeId)) {
if (
this.allowedComponentTypes.includes(component.type) &&
component.id !== this.thisComponentId
) {
numAllowedComponents += 1;
allowedComponent = component;
}
}
if (numAllowedComponents === 1) {
this.referenceComponent.componentId = allowedComponent.id;
this.componentChanged();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ComponentHarness } from '@angular/cdk/testing';
import { SelectStepHarness } from '../select-step/select-step.harness';
import { SelectComponentHarness } from '../select-component/select-component.harness';

export class SelectStepAndComponentHarness extends ComponentHarness {
static hostSelector = 'select-step-and-component';
getSelectComponent = this.locatorFor(SelectComponentHarness);
getSelectStep = this.locatorFor(SelectStepHarness);
}
8 changes: 4 additions & 4 deletions src/messages.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1641,8 +1641,8 @@ Click &quot;Cancel&quot; to keep the invalid JSON open so you can fix it.</sourc
<trans-unit id="e78170d2eb79d84d7927b8df23950715263ae478" datatype="html">
<source>Component</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/authoring-tool/select-step-and-component/select-step-and-component.component.html</context>
<context context-type="linenumber">7</context>
<context context-type="sourcefile">src/app/authoring-tool/select-component/select-component.component.html</context>
<context context-type="linenumber">2</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/assets/wise5/components/animation/animation-authoring/animation-authoring.component.html</context>
Expand All @@ -1660,8 +1660,8 @@ Click &quot;Cancel&quot; to keep the invalid JSON open so you can fix it.</sourc
<trans-unit id="5d2007bc11d50873e2e87582f6c13c80f37b65e5" datatype="html">
<source>(This Component)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/authoring-tool/select-step-and-component/select-step-and-component.component.html</context>
<context context-type="linenumber">15</context>
<context context-type="sourcefile">src/app/authoring-tool/select-component/select-component.component.html</context>
<context context-type="linenumber">10</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/assets/wise5/components/openResponse/edit-open-response-advanced/edit-open-response-advanced.component.html</context>
Expand Down

0 comments on commit 1b9b54b

Please sign in to comment.