forked from oppia/oppia
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix part of oppia#18384: Add goals modal for learner dashboard redesi…
…gn (oppia#21143) * Feature flag and title * Add button * Added modal html file * Added ts for modal * Added modal functionality * I18N keys * Add goals styling * Test cases for add goals modal * Added test cases for add goals modal * Added goals with api * Added test cases in goals tab for openModal * Corrected object type and flippedobject key and value * Updated openModal with API calls andcompleted goals * Corrected add goal modal tests and fixed bug in goals tab * Updated goals tab tests * Fixed new test cases for openModal and missing tag in goals tab * Small screen UI modal errors and current goals not being modified properly * Linter fixes * Lint errors * Type errors * Linter fixes * Fixed copyright notes in add-goals-modal.spec * Rearranged I18N keys alphabetically for goals section * Removed duplicate goal tab heading which is handled in getDashboardTabHeading and username variable * Renamed ngFor variable in add-goals-modal
- Loading branch information
1 parent
7225eff
commit b704e4b
Showing
12 changed files
with
766 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
core/templates/pages/learner-dashboard-page/add-goals-modal/add-goals-modal.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<div class="align-items-center d-flex flex-column h-100"> | ||
<h3 class="oppia-learner-dash-goals-modal-title" mat-dialog-title> {{ 'I18N_LEARNER_DASHBOARD_GOALS_SECTION_MODAL_HEADING' | translate }} </h3> | ||
<p class="oppia-learner-dash-goals-modal-info"> {{ 'I18N_LEARNER_DASHBOARD_GOALS_SECTION_MODAL_INSTRUCTIONS' | translate }} </p> | ||
<div class="oppia-learner-dash-goals-modal-content pt-1" mat-dialog-content> | ||
<!--TODO(#18384): What if all the goals are completed, should they be disabled and checked?--> | ||
<!--TODO(#18384): Topic name translate keys--> | ||
<ng-container *ngFor="let topic of topics | keyvalue"> | ||
<mat-checkbox color="primary" | ||
class="oppia-learner-dash-goals-checkbox" | ||
(change)="onChange(topic.key)" | ||
[checked]="checkedTopics.has(topic.key)" | ||
[disabled]="checkedTopics.length === 5 && !checkedTopics.has(topic.key) || completedTopics.has(topic.key)"> | ||
{{ topic.value }} | ||
</mat-checkbox> | ||
</ng-container> | ||
</div> | ||
<div class="oppia-learner-dash-goals-modal-actions w-100" mat-dialog-actions> | ||
<button (click)="onClose()" class="oppia-learner-dash-button--inverse oppia-learner-dash-button-sm oppia-learner-dash-goals-button--modal" mat-button> {{ 'I18N_LEARNER_DASHBOARD_GOALS_SECTION_MODAL_CANCEL' | translate }} </button> | ||
<button (click)="onSubmit()" class="oppia-learner-dash-button--default oppia-learner-dash-button-sm oppia-learner-dash-goals-button--modal" mat-button> {{ 'I18N_LEARNER_DASHBOARD_GOALS_SECTION_MODAL_SAVE' | translate }} </button> | ||
</div> | ||
</div> | ||
|
||
<style> | ||
.oppia-learner-dash-goals-button--modal { | ||
height: 44px; | ||
width: 178px; | ||
} | ||
|
||
.oppia-learner-dash-goals-checkbox { | ||
font-size: 16px; | ||
gap: 0 12px; | ||
} | ||
|
||
.oppia-learner-dash-goals-modal-content { | ||
display: grid; | ||
gap: 0 4px; | ||
grid-template-columns: repeat(3, 1fr); | ||
grid-template-rows: auto; | ||
height: 100%; | ||
padding-top: 12px; | ||
width: 100%; | ||
} | ||
|
||
.oppia-learner-dash-goals-modal-actions { | ||
display: flex; | ||
gap: 0 12px; | ||
justify-content: end; | ||
} | ||
|
||
.oppia-learner-dash-goals-modal-title { | ||
font-size: 20px; | ||
font-weight: 500; | ||
text-align: center; | ||
} | ||
|
||
.oppia-learner-dash-goals-modal-info { | ||
font-size: 14px; | ||
text-align: center; | ||
} | ||
|
||
|
||
@media screen and (max-width: 576px) { | ||
.oppia-learner-dash-goals-modal-actions { | ||
gap: 0; | ||
} | ||
|
||
.oppia-learner-dash-goals-button--modal { | ||
max-width: 45%; | ||
width: 147px; | ||
} | ||
|
||
.oppia-learner-dash-goals-modal-content { | ||
align-items: center; | ||
display: flex; | ||
flex-direction: column; | ||
gap: 12px 0; | ||
height: fit-content; | ||
padding: 12px; | ||
} | ||
} | ||
|
||
@media screen and (max-width: 767px) { | ||
.oppia-learner-dash-goals-modal-actions { | ||
flex-grow: 1; | ||
justify-content: center; | ||
} | ||
|
||
.oppia-learner-dash-goals-modal-content { | ||
grid-template-columns: repeat(2, 1fr); | ||
} | ||
} | ||
|
||
</style> |
223 changes: 223 additions & 0 deletions
223
.../templates/pages/learner-dashboard-page/add-goals-modal/add-goals-modal.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
// Copyright 2024 The Oppia Authors. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS-IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
/** | ||
* @fileoverview Unit tests for AddGoalsModal | ||
*/ | ||
|
||
import {HttpClientTestingModule} from '@angular/common/http/testing'; | ||
import {FormsModule} from '@angular/forms'; | ||
import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog'; | ||
import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing'; | ||
import {MockTranslatePipe} from 'tests/unit-test-utils'; | ||
import {AddGoalsModalComponent} from './add-goals-modal.component'; | ||
import {NO_ERRORS_SCHEMA} from '@angular/core'; | ||
import {By} from '@angular/platform-browser'; | ||
import {of} from 'rxjs'; | ||
const data = { | ||
checkedTopics: new Set(), | ||
completedTopics: new Set(), | ||
topics: { | ||
0: 'Addition', | ||
1: 'Subtraction', | ||
2: 'Multiplication', | ||
3: 'Divsion', | ||
4: 'Fractions', | ||
5: 'Exponents', | ||
}, | ||
}; | ||
|
||
describe('AddGoalsModalComponent', () => { | ||
let component: AddGoalsModalComponent; | ||
let fixture: ComponentFixture<AddGoalsModalComponent>; | ||
let matDialogSpy: jasmine.SpyObj<MatDialogRef<AddGoalsModalComponent>>; | ||
beforeEach(waitForAsync(() => { | ||
matDialogSpy = jasmine.createSpyObj('MatDialogRef', [ | ||
'close', | ||
'afterClosed', | ||
]); | ||
matDialogSpy.afterClosed.and.returnValue(of(true)); | ||
TestBed.configureTestingModule({ | ||
imports: [FormsModule, HttpClientTestingModule], | ||
providers: [ | ||
{ | ||
provide: MatDialogRef, | ||
useValue: matDialogSpy, | ||
}, | ||
{ | ||
provide: MAT_DIALOG_DATA, | ||
useValue: data, | ||
}, | ||
], | ||
declarations: [AddGoalsModalComponent, MockTranslatePipe], | ||
schemas: [NO_ERRORS_SCHEMA], | ||
}).compileComponents(); | ||
})); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(AddGoalsModalComponent); | ||
component = fixture.componentInstance; | ||
data.checkedTopics = new Set(); | ||
data.completedTopics = new Set(); | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create a component', () => { | ||
expect(component).toBeTruthy(); | ||
expect(component.checkedTopics).toEqual(new Set()); | ||
expect(component.completedTopics).toEqual(new Set()); | ||
expect(component.topics).toEqual({ | ||
0: 'Addition', | ||
1: 'Subtraction', | ||
2: 'Multiplication', | ||
3: 'Divsion', | ||
4: 'Fractions', | ||
5: 'Exponents', | ||
}); | ||
const checkboxes = fixture.debugElement.queryAll(By.css('mat-checkbox')); | ||
|
||
checkboxes.forEach(box => expect(box.nativeElement.checked).toBeFalse()); | ||
}); | ||
|
||
it('should intialize checked boxes of previously selected goals', () => { | ||
component.checkedTopics = new Set(['0', '2']); | ||
fixture.detectChanges(); | ||
|
||
const checkboxes = fixture.debugElement.queryAll(By.css('mat-checkbox')); | ||
expect(checkboxes[0].nativeElement.checked).toBeTrue(); | ||
expect(checkboxes[2].nativeElement.checked).toBeTrue(); | ||
}); | ||
|
||
it('should add a goal id when checking an unchecked box', () => { | ||
const firstCheckbox = fixture.debugElement.query( | ||
By.css('mat-checkbox:first-child') | ||
); | ||
expect(firstCheckbox.nativeElement.checked).toBeFalse(); | ||
spyOn(component, 'onChange').and.callThrough(); | ||
firstCheckbox.triggerEventHandler('change', {target: {checked: true}}); | ||
|
||
fixture.detectChanges(); | ||
|
||
expect(component.onChange).toHaveBeenCalledWith('0'); | ||
expect(component.checkedTopics.has('0')).toBeTrue(); | ||
expect(firstCheckbox.nativeElement.checked).toBeTrue(); | ||
}); | ||
|
||
it('should remove a goal id when checking a checked box', () => { | ||
component.checkedTopics = new Set(['0']); | ||
const firstCheckbox = fixture.debugElement.query( | ||
By.css('mat-checkbox:first-child') | ||
); | ||
fixture.detectChanges(); | ||
expect(firstCheckbox.nativeElement.checked).toBeTrue(); | ||
|
||
spyOn(component, 'onChange').and.callThrough(); | ||
firstCheckbox.triggerEventHandler('change', {target: {checked: false}}); | ||
fixture.detectChanges(); | ||
|
||
expect(component.onChange).toHaveBeenCalledWith('0'); | ||
expect(component.checkedTopics.has('0')).toBeFalse(); | ||
expect(firstCheckbox.nativeElement.checked).toBeFalse(); | ||
}); | ||
|
||
it('should disable checkbox if completed', () => { | ||
component.completedTopics = new Set(['0']); | ||
const firstCheckbox = fixture.debugElement.query( | ||
By.css('mat-checkbox:first-child') | ||
); | ||
fixture.detectChanges(); | ||
|
||
expect(firstCheckbox.nativeElement.checked).toBeFalse(); | ||
expect(firstCheckbox.nativeElement.disabled).toBeTrue(); | ||
}); | ||
|
||
it('should close modal when cancel is clicked', () => { | ||
expect(component).toBeTruthy(); | ||
const cancelButton = fixture.debugElement.query( | ||
By.css( | ||
'.oppia-learner-dash-goals-button--modal.oppia-learner-dash-button--inverse' | ||
) | ||
); | ||
cancelButton.triggerEventHandler('click', null); | ||
|
||
expect(matDialogSpy.close).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should add and return new goals and close modal when save is clicked', () => { | ||
expect(component).toBeTruthy(); | ||
expect(component.checkedTopics).toEqual(new Set()); | ||
|
||
const checkboxes = fixture.debugElement.queryAll(By.css('mat-checkbox')); | ||
spyOn(component, 'onChange').and.callThrough(); | ||
checkboxes[1].triggerEventHandler('change', {target: {checked: true}}); | ||
checkboxes[2].triggerEventHandler('change', {target: {checked: true}}); | ||
checkboxes[3].triggerEventHandler('change', {target: {checked: true}}); | ||
fixture.detectChanges(); | ||
|
||
expect(component.onChange).toHaveBeenCalledTimes(3); | ||
|
||
expect(component.onChange).toHaveBeenCalledWith('1'); | ||
expect(component.onChange).toHaveBeenCalledWith('2'); | ||
expect(component.onChange).toHaveBeenCalledWith('3'); | ||
|
||
const saveButton = fixture.debugElement.query( | ||
By.css( | ||
'.oppia-learner-dash-goals-button--modal.oppia-learner-dash-button--default' | ||
) | ||
); | ||
saveButton.triggerEventHandler('click', null); | ||
|
||
expect(matDialogSpy.close).toHaveBeenCalledWith(jasmine.any(Set)); | ||
const actualSet = matDialogSpy.close.calls.mostRecent() | ||
.args[0] as Set<string>; | ||
expect(actualSet).toEqual(new Set(['1', '2', '3'])); | ||
}); | ||
|
||
it('should return previously selected goals if there is no change when save is clicked', () => { | ||
component.checkedTopics = new Set(['0', '2']); | ||
fixture.detectChanges(); | ||
|
||
const saveButton = fixture.debugElement.query( | ||
By.css( | ||
'.oppia-learner-dash-goals-button--modal.oppia-learner-dash-button--default' | ||
) | ||
); | ||
saveButton.triggerEventHandler('click', null); | ||
|
||
const actualSet = matDialogSpy.close.calls.mostRecent() | ||
.args[0] as Set<string>; | ||
expect(actualSet).toEqual(new Set(['0', '2'])); | ||
}); | ||
|
||
it('should remove a goal id and return goals without it when save is clicked', () => { | ||
component.checkedTopics = new Set(['0', '2']); | ||
fixture.detectChanges(); | ||
|
||
const checkboxes = fixture.debugElement.queryAll(By.css('mat-checkbox')); | ||
spyOn(component, 'onChange').and.callThrough(); | ||
checkboxes[2].triggerEventHandler('change', {target: {checked: false}}); | ||
fixture.detectChanges(); | ||
|
||
const saveButton = fixture.debugElement.query( | ||
By.css( | ||
'.oppia-learner-dash-goals-button--modal.oppia-learner-dash-button--default' | ||
) | ||
); | ||
saveButton.triggerEventHandler('click', null); | ||
|
||
const actualSet = matDialogSpy.close.calls.mostRecent() | ||
.args[0] as Set<string>; | ||
expect(actualSet).toEqual(new Set(['0'])); | ||
}); | ||
}); |
Oops, something went wrong.