All users must create their own account. Teachers must create a teacher account and students
must create a student account. Users can create an account by going to the
WISE Home Page and clicking on the
Register link at the upper right of the
screen.
-
Here are instructions on how to create a teacher account.
+
Here are instructions on how to create a teacher account.
Choose the number of students per team. Choosing 1-3 will allow students to work together in a
team.
-
+
Choose the start date. Students will not be able to use the project until this date.
Click "Create Run".
@@ -63,10 +60,10 @@
How do I use a unit with my class?
Students will need to use this access code to work on the run you created.
-
-
Student Management
+
+
Student Management
Should I register my students for WISE or have them do it themselves?
-
+
WISE makes student registration simple and intuitive -- direct your students to the register
page by having them go to the WISE home page and clicking the "Register" link at the upper
right. They should be able to register in 10 minutes or less. However, you can also opt to
@@ -128,14 +125,14 @@
How do I change a student team after they've started a project run?
- Warning 1:
+ Warning 1:
If you move Student A into an established team, student A loses all of their current work
and inherits the current work of the established team.
- Warning 2:
+ Warning 2:
If you move Student A into a newly created (blank) team, student A will lose all of their
work.How do I change the period for a student?
Choose a different period.
Click "Save Changes".
- Warning:
+ Warning:
If you move a student to a different period, they will lose all of their work.
-
+
I do not remember my teacher access code that students need to create their account for my new
students.
@@ -172,8 +169,8 @@
Find the run in your Teacher Home Page.
The Access Code will be displayed below the run title.
-
-
Project Management
+
+
Project Management
When should I set up my project run?
@@ -238,7 +235,7 @@
How do I delete a period after creating a run?
Find the period you want to delete and click the "Delete" button next to it.
Note: You are not allowed to delete a period that has students in it.
-
+
What if I run out of computer lab time but some of my students are not finished with the
project?
@@ -249,8 +246,8 @@
computer, per the teacher's discretion.
-
-
Assessment of Student Work
+
+
Assessment of Student Work
How do I review and grade student work?
Sign into WISE with your teacher account.
@@ -295,7 +292,7 @@
How can I encourage my students to review the graded notes and comments
some sample responses and have the class critique the work.
-
How do I find time to grade all of the student work?
+
How do I find time to grade all of the student work?
We recommend that you go through the project and select a few steps that you think best
@@ -305,8 +302,8 @@
How do I find time to grade all of the st
very time consuming and unpractical.
-
-
Real Time Classroom Monitor
+
+
Real Time Classroom Monitor
What is the Real Time Classroom Monitor?
@@ -318,7 +315,7 @@
What is the Real Time Classroom Monitor?
Does the Real Time Classroom Monitor work on a tablet like the iPad?
-
Yes it does.
+
Yes it does.
Can I use the Real Time Classroom Monitor to pause student screens?
@@ -327,8 +324,8 @@
Can I use the Real Time Classroom Monitor to pause student screens?
-
-
Technical Questions
+
+
Technical Questions
The WISE web site won't load on my web browser. What do I do?
diff --git a/src/app/modules/shared/edit-password/edit-password.component.spec.ts b/src/app/modules/shared/edit-password/edit-password.component.spec.ts
index 1b6c6eececb..e671f168d3d 100644
--- a/src/app/modules/shared/edit-password/edit-password.component.spec.ts
+++ b/src/app/modules/shared/edit-password/edit-password.component.spec.ts
@@ -1,7 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditPasswordComponent } from './edit-password.component';
import { UserService } from '../../../services/user.service';
-import { BehaviorSubject, Observable } from 'rxjs';
+import { BehaviorSubject, Observable, Subscriber } from 'rxjs';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';
import { NO_ERRORS_SCHEMA } from '@angular/core';
@@ -10,13 +10,15 @@ import { By } from '@angular/platform-browser';
import { User } from '../../../domain/user';
import { MatDialogModule } from '@angular/material/dialog';
import { PasswordModule } from '../../../password/password.module';
+import { MatIconModule } from '@angular/material/icon';
+import { PasswordErrors } from '../../../domain/password/password-errors';
+import { PasswordRequirementComponent } from '../../../password/password-requirement/password-requirement.component';
const CORRECT_OLD_PASSWORD = 'correctOldPassword123';
const INCORRECT_OLD_PASSWORD = 'incorrectOldPassword123';
-const INVALID_PASSWORD_TOO_SHORT = 'Abcd123';
-const INVALID_PASSWORD_PATTERN = 'abcd1234';
-const NEW_PASSWORD_1 = 'Abcd1111';
-const NEW_PASSWORD_2 = 'Abcd2222';
+const INVALID_PASSWORD = PasswordRequirementComponent.INVALID_PASSWORD_TOO_SHORT;
+const NEW_PASSWORD_1 = PasswordRequirementComponent.VALID_PASSWORD;
+const NEW_PASSWORD_2 = PasswordRequirementComponent.VALID_PASSWORD + '!';
export class MockUserService {
getUser(): BehaviorSubject {
@@ -54,6 +56,7 @@ describe('EditPasswordComponent', () => {
imports: [
BrowserAnimationsModule,
MatDialogModule,
+ MatIconModule,
MatSnackBarModule,
PasswordModule,
ReactiveFormsModule
@@ -70,121 +73,126 @@ describe('EditPasswordComponent', () => {
validForm_enableSubmitButton();
passwordMismatch_disableSubmitButtonAndInvalidateForm();
oldPasswordIncorrect_disableSubmitButtonAndShowError();
- saveChanges_newPasswordTooShort_ShowError();
saveChanges_newPasswordPatternInvalid_ShowError();
formSubmit_disableSubmitButton();
notGoogleUser_showUnlinkOption();
unlinkGoogleButtonClick_showDialog();
- invalidPasswordTooShort_showError();
- invalidPasswordPattern_showError();
+ invalidPassword_showError();
});
function initialState_disableSubmitButton() {
- it('should disable submit button and invalidate form on initial state', () => {
- expect(component.changePasswordFormGroup.valid).toBeFalsy();
- expectSubmitButtonDisabled();
+ describe('the form is first loaded', () => {
+ it('disables the submit button', () => {
+ expect(component.changePasswordFormGroup.valid).toBeFalsy();
+ expectSubmitButtonDisabled();
+ });
});
}
function validForm_enableSubmitButton() {
- it('should enable submit button when form is valid', () => {
- setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1);
- expectSubmitButtonEnabled();
- expect(component.changePasswordFormGroup.valid).toBeTruthy();
+ describe('the form is valid', () => {
+ it('enables the submit button', () => {
+ setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1);
+ expectSubmitButtonEnabled();
+ expect(component.changePasswordFormGroup.valid).toBeTruthy();
+ });
});
}
function passwordMismatch_disableSubmitButtonAndInvalidateForm() {
- it(`should disable submit button and invalidate form when new password and confirm new password
- fields do not match`, () => {
- setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_2);
- expectSubmitButtonDisabled();
- expect(component.changePasswordFormGroup.valid).toBeFalsy();
+ describe('new password and confirm new password do not match', () => {
+ it(`disables the submit button`, () => {
+ setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_2);
+ expect(component.changePasswordFormGroup.valid).toBeFalsy();
+ expectSubmitButtonDisabled();
+ });
});
}
function oldPasswordIncorrect_disableSubmitButtonAndShowError() {
- it(`should disable submit button and set incorrectPassword error when old password is
- incorrect`, async () => {
- spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue(
- generateObservableResponse('incorrectPassword', false)
- );
- setPasswords(INCORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1);
- submitForm();
- expectSubmitButtonDisabled();
- expect(component.changePasswordFormGroup.get('oldPassword').getError('incorrectPassword')).toBe(
- true
- );
+ describe('server returns response that says old password is incorrect', () => {
+ it('shows the incorrect password error and disables the submit button', async () => {
+ spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue(
+ generateErrorObservable('incorrectPassword')
+ );
+ setPasswords(INCORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1);
+ submitForm();
+ expect(
+ component.changePasswordFormGroup.get('oldPassword').getError('incorrectPassword')
+ ).toBe(true);
+ expectSubmitButtonDisabled();
+ });
});
}
-function saveChanges_newPasswordTooShort_ShowError() {
- it(`should set minlength error when changePassword response returns new password is not long
- enough error`, async () => {
- spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue(
- generateObservableResponse('invalidPasswordLength', false)
- );
- setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD_TOO_SHORT, INVALID_PASSWORD_TOO_SHORT);
- component.saveChanges();
- expect(component.newPasswordFormGroup.get('newPassword').getError('minlength')).toBe(true);
+function saveChanges_newPasswordPatternInvalid_ShowError() {
+ describe('server returns response that says new password is not valid', () => {
+ it('shows the new password error messages and disables the submit button', async () => {
+ const passwordErrors = new PasswordErrors(false, false, true);
+ spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue(
+ generateErrorObservable(passwordErrors)
+ );
+ setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD, INVALID_PASSWORD);
+ component.saveChanges();
+ expectPasswordErrors(false, false, true);
+ expectSubmitButtonDisabled();
+ });
});
}
-function saveChanges_newPasswordPatternInvalid_ShowError() {
- it(`should set pattern error when changePassword response returns new password is not valid
- pattern error`, async () => {
- spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue(
- generateObservableResponse('invalidPasswordPattern', false)
- );
- setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD_PATTERN, INVALID_PASSWORD_PATTERN);
- component.saveChanges();
- expect(component.newPasswordFormGroup.get('newPassword').getError('pattern')).toBe(true);
- });
+function expectPasswordErrors(
+ missingLetter: boolean,
+ missingNumber: boolean,
+ tooShort: boolean
+): void {
+ expect(component.newPasswordFormGroup.get('newPassword').getError('missingLetter')).toBe(
+ missingLetter
+ );
+ expect(component.newPasswordFormGroup.get('newPassword').getError('missingNumber')).toBe(
+ missingNumber
+ );
+ expect(component.newPasswordFormGroup.get('newPassword').getError('tooShort')).toBe(tooShort);
}
function formSubmit_disableSubmitButton() {
- it('should disable submit button when form is successfully submitted', async () => {
- spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue(
- generateObservableResponse('passwordChanged', true)
- );
- setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1);
- submitForm();
- expectSubmitButtonDisabled();
+ describe('form is successfully submitted', () => {
+ it('disables the submit button', async () => {
+ spyOn(TestBed.inject(UserService), 'changePassword').and.returnValue(
+ generateSuccessObservable('passwordChanged')
+ );
+ setPasswords(CORRECT_OLD_PASSWORD, NEW_PASSWORD_1, NEW_PASSWORD_1);
+ submitForm();
+ expectSubmitButtonDisabled();
+ });
});
}
function notGoogleUser_showUnlinkOption() {
- it('should hide show option to unlink google account if the user is not a google user', () => {
- expect(getUnlinkGoogleAccountButton()).toBeNull();
+ describe('user is not a google user', () => {
+ it('hides the option to unlink google acccount', () => {
+ expect(getUnlinkGoogleAccountButton()).toBeNull();
+ });
});
}
function unlinkGoogleButtonClick_showDialog() {
- it('clicking on unlink google account link should open a dialog', () => {
- const dialogSpy = spyOn(component.dialog, 'open');
- setGoogleUser();
- getUnlinkGoogleAccountButton().click();
- expect(dialogSpy).toHaveBeenCalled();
- });
-}
-
-function invalidPasswordTooShort_showError() {
- it(`should disable submit button and set min length error when new password is too
- short`, async () => {
- setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD_TOO_SHORT, INVALID_PASSWORD_TOO_SHORT);
- expectSubmitButtonDisabled();
- expect(component.newPasswordFormGroup.get('newPassword').getError('minlength')).toBeTruthy();
- expect(component.newPasswordFormGroup.get('newPassword').getError('pattern')).toBeFalsy();
+ describe('user is a google user and clicks unlink google account button', () => {
+ it('opens dialog', () => {
+ const dialogSpy = spyOn(component.dialog, 'open');
+ setGoogleUser();
+ getUnlinkGoogleAccountButton().click();
+ expect(dialogSpy).toHaveBeenCalled();
+ });
});
}
-function invalidPasswordPattern_showError() {
- it(`should disable submit button and set pattern error when new password does not satisfy the
- pattern requirements`, async () => {
- setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD_PATTERN, INVALID_PASSWORD_PATTERN);
- expectSubmitButtonDisabled();
- expect(component.newPasswordFormGroup.get('newPassword').getError('minlength')).toBeFalsy();
- expect(component.newPasswordFormGroup.get('newPassword').getError('pattern')).toBeTruthy();
+function invalidPassword_showError() {
+ describe('new password does not satisfy the requirements', () => {
+ it('shows password errors and disables submit button', async () => {
+ setPasswords(CORRECT_OLD_PASSWORD, INVALID_PASSWORD, INVALID_PASSWORD);
+ expect(component.newPasswordFormGroup.get('newPassword').errors).not.toBeNull();
+ expectSubmitButtonDisabled();
+ });
});
}
@@ -213,16 +221,27 @@ function setPasswords(oldPass: string, newPass: string, newPassConfirm: string)
fixture.detectChanges();
}
-function generateObservableResponse(messageCode: string, isSuccess: boolean): Observable {
- if (isSuccess) {
- return new Observable((observer) => {
- observer.next({ messageCode: messageCode });
- observer.complete();
- });
- } else {
- return new Observable((observer) => {
- observer.error({ error: { messageCode: messageCode } });
- observer.complete();
- });
- }
+function generateSuccessObservable(arg: string | any): Observable {
+ return generateResponseObservable(arg, true);
+}
+
+function generateSuccessResponseValue(arg: string | any): any {
+ return typeof arg === 'string' ? { messageCode: arg } : arg;
+}
+
+function generateErrorObservable(arg: string | any): Observable {
+ return generateResponseObservable(arg, false);
+}
+
+function generateResponseObservable(arg: string | any, isSuccess: boolean): Observable {
+ return new Observable((observer: Subscriber) => {
+ isSuccess
+ ? observer.next(generateSuccessResponseValue(arg))
+ : observer.error(generateErrorResponseValue(arg));
+ observer.complete();
+ });
+}
+
+function generateErrorResponseValue(arg: string | any): any {
+ return typeof arg === 'string' ? { error: { messageCode: arg } } : { error: arg };
}
diff --git a/src/app/modules/shared/edit-password/edit-password.component.ts b/src/app/modules/shared/edit-password/edit-password.component.ts
index 9264c09c292..8bbf0316e1d 100644
--- a/src/app/modules/shared/edit-password/edit-password.component.ts
+++ b/src/app/modules/shared/edit-password/edit-password.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Component, ViewChild } from '@angular/core';
+import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
@@ -6,13 +6,14 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { UserService } from '../../../services/user.service';
import { UnlinkGoogleAccountConfirmComponent } from '../unlink-google-account-confirm/unlink-google-account-confirm.component';
import { NewPasswordAndConfirmComponent } from '../../../password/new-password-and-confirm/new-password-and-confirm.component';
+import { changePasswordError } from '../../../common/password-helper';
@Component({
selector: 'app-edit-password',
templateUrl: './edit-password.component.html',
styleUrls: ['./edit-password.component.scss']
})
-export class EditPasswordComponent {
+export class EditPasswordComponent implements OnInit {
@ViewChild('changePasswordForm', { static: false }) changePasswordForm;
isSaving: boolean = false;
isGoogleUser: boolean = false;
@@ -77,29 +78,16 @@ export class EditPasswordComponent {
private changePasswordSuccess(): void {
this.resetForm();
- this.snackBar.open($localize`Password changed.`);
+ this.snackBar.open($localize`Successfully changed password.`);
}
private changePasswordError(error: any): void {
- const formError: any = {};
- switch (error.messageCode) {
- case 'incorrectPassword':
- formError.incorrectPassword = true;
- this.changePasswordFormGroup.get('oldPassword').setErrors(formError);
- break;
- case 'invalidPasswordLength':
- formError.minlength = true;
- this.newPasswordFormGroup
- .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME)
- .setErrors(formError);
- break;
- case 'invalidPasswordPattern':
- formError.pattern = true;
- this.newPasswordFormGroup
- .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME)
- .setErrors(formError);
- break;
- }
+ changePasswordError(
+ error,
+ this.changePasswordFormGroup,
+ this.newPasswordFormGroup,
+ 'oldPassword'
+ );
}
unlinkGoogleAccount(): void {
diff --git a/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts b/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts
index f8fe636454e..23e50a439af 100644
--- a/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts
+++ b/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.spec.ts
@@ -9,6 +9,7 @@ import { of } from 'rxjs';
import { PasswordModule } from '../../../password/password.module';
import { UserService } from '../../../services/user.service';
import { UnlinkGoogleAccountPasswordComponent } from './unlink-google-account-password.component';
+import { PasswordRequirementComponent } from '../../../password/password-requirement/password-requirement.component';
class MockUserService {
unlinkGoogleUser(newPassword: string) {
@@ -26,11 +27,11 @@ describe('UnlinkGoogleAccountPasswordComponent', () => {
declarations: [UnlinkGoogleAccountPasswordComponent],
imports: [
BrowserAnimationsModule,
+ MatDialogModule,
MatFormFieldModule,
MatInputModule,
- ReactiveFormsModule,
PasswordModule,
- MatDialogModule
+ ReactiveFormsModule
],
providers: [{ provide: UserService, useValue: userService }],
schemas: [NO_ERRORS_SCHEMA]
@@ -45,7 +46,7 @@ describe('UnlinkGoogleAccountPasswordComponent', () => {
function formSubmit_callUserServiceUnlinkGoogleUserFunction() {
it('should call UserService.UnlinkGoogleUserFunction when form is submitted', () => {
const unlinkFunctionSpy = spyOn(userService, 'unlinkGoogleUser').and.returnValue(of({}));
- const newPassword = 'Abcd1234';
+ const newPassword = PasswordRequirementComponent.VALID_PASSWORD;
component.newPasswordFormGroup.setValue({
newPassword: newPassword,
confirmNewPassword: newPassword
diff --git a/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts b/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts
index 3bd16b6704c..4e55902151c 100644
--- a/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts
+++ b/src/app/modules/shared/unlink-google-account-password/unlink-google-account-password.component.ts
@@ -4,6 +4,7 @@ import { MatDialog } from '@angular/material/dialog';
import { NewPasswordAndConfirmComponent } from '../../../password/new-password-and-confirm/new-password-and-confirm.component';
import { UserService } from '../../../services/user.service';
import { UnlinkGoogleAccountSuccessComponent } from '../unlink-google-account-success/unlink-google-account-success.component';
+import { injectPasswordErrors } from '../../../common/password-helper';
@Component({
styleUrls: ['./unlink-google-account-password.component.scss'],
@@ -51,20 +52,8 @@ export class UnlinkGoogleAccountPasswordComponent {
private error(error: any): void {
this.isSaving = false;
- const formError: any = {};
- switch (error.messageCode) {
- case 'invalidPasswordLength':
- formError.minlength = true;
- this.newPasswordFormGroup
- .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME)
- .setErrors(formError);
- break;
- case 'invalidPasswordPattern':
- formError.pattern = true;
- this.newPasswordFormGroup
- .get(NewPasswordAndConfirmComponent.NEW_PASSWORD_FORM_CONTROL_NAME)
- .setErrors(formError);
- break;
+ if (error.messageCode === 'invalidPassword') {
+ injectPasswordErrors(this.newPasswordFormGroup, error);
}
}
}
diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html
index 2178d65eee1..0836004faba 100644
--- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html
+++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html
@@ -1,25 +1,55 @@
-
-
+
+ {{ passwordLabel }}
-
+
Password required
-
- Password must be at least 8 characters
-
-
- Password must have at least one lowercase, one uppercase, and one number character
-
-
+
+
+