Adds fake sign-in and sign-out components
This commit is contained in:
parent
4cf28994b6
commit
f9a47e36f7
|
@ -0,0 +1,4 @@
|
||||||
|
// tslint:disable-next-line:max-line-length
|
||||||
|
export const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
|
||||||
|
|
||||||
|
export default EMAIL_REGEX;
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {FormControl} from '@angular/forms';
|
||||||
|
import {ValidateEmail} from './email.validator';
|
||||||
|
|
||||||
|
describe('ValidateEmail', () => {
|
||||||
|
const control = new FormControl();
|
||||||
|
|
||||||
|
it('should return an error for an invalid email address', () => {
|
||||||
|
const emailsToTest = [
|
||||||
|
'124535',
|
||||||
|
'not an email address',
|
||||||
|
'@gmail.com',
|
||||||
|
'incomplete@domain',
|
||||||
|
'tooshort@tld.c',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const email of emailsToTest) {
|
||||||
|
control.setValue(email);
|
||||||
|
expect(ValidateEmail(control)).toEqual({email: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not return an error for a valid email address', () => {
|
||||||
|
const emailsToTest = [
|
||||||
|
'short@tld.co',
|
||||||
|
'simple@email.edu',
|
||||||
|
'more+complicated@www.email-server.mail',
|
||||||
|
'this.is.a.valid.email+address@some-random.email-server.com',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const email of emailsToTest) {
|
||||||
|
control.setValue(email);
|
||||||
|
expect(ValidateEmail(control)).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { AbstractControl, ValidationErrors } from '@angular/forms';
|
||||||
|
import EMAIL_REGEX from './email.regex';
|
||||||
|
|
||||||
|
export function ValidateEmail(control: AbstractControl): ValidationErrors {
|
||||||
|
if (!EMAIL_REGEX.test(control.value) && control.value && control.value !== '') {
|
||||||
|
const error: ValidationErrors = { email: true };
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
import {FormControl} from '@angular/forms';
|
||||||
|
import {FormlyFieldConfig} from '@ngx-formly/core';
|
||||||
|
import {FieldType} from '@ngx-formly/material';
|
||||||
|
import * as Validators from './formly.validator';
|
||||||
|
|
||||||
|
describe('Formly Validators', () => {
|
||||||
|
let control: FormControl;
|
||||||
|
let err: Error;
|
||||||
|
let field: FormlyFieldConfig;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
control = new FormControl();
|
||||||
|
err = new Error('some error');
|
||||||
|
field = {
|
||||||
|
type: 'email',
|
||||||
|
templateOptions: {},
|
||||||
|
formControl: control,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate emails', () => {
|
||||||
|
control.setValue('');
|
||||||
|
expect(Validators.EmailValidator(control)).toBeNull();
|
||||||
|
|
||||||
|
control.setValue('a@b');
|
||||||
|
expect(Validators.EmailValidator(control)).toEqual({email: true});
|
||||||
|
|
||||||
|
control.setValue('a@b.c');
|
||||||
|
expect(Validators.EmailValidator(control)).toEqual({email: true});
|
||||||
|
|
||||||
|
control.setValue('a@b.com');
|
||||||
|
expect(Validators.EmailValidator(control)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should email validator message', () => {
|
||||||
|
control.setValue('bad_email');
|
||||||
|
expect(Validators.EmailValidatorMessage(err, field)).toContain('bad_email');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate URL strings', () => {
|
||||||
|
const badUrls = [
|
||||||
|
'bad_url',
|
||||||
|
'http://',
|
||||||
|
'http://bad',
|
||||||
|
'bad.com',
|
||||||
|
];
|
||||||
|
badUrls.forEach(url => {
|
||||||
|
control.setValue(url);
|
||||||
|
expect(Validators.UrlValidator(control)).toEqual({url: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
const goodUrls = [
|
||||||
|
'http://good.url.com',
|
||||||
|
'ftp://who-uses.ftp-these-days.com',
|
||||||
|
'https://this.is.actually:5432/a/valid/url?doncha=know#omg',
|
||||||
|
];
|
||||||
|
goodUrls.forEach(url => {
|
||||||
|
control.setValue(url);
|
||||||
|
expect(Validators.UrlValidator(control)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a url validator message', () => {
|
||||||
|
control.setValue('bad_url');
|
||||||
|
expect(Validators.UrlValidatorMessage(err, field)).toContain('bad_url');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate phone numbers', () => {
|
||||||
|
const badPhones = [
|
||||||
|
'not a phone number',
|
||||||
|
'54321',
|
||||||
|
'3.1415926535897932384...',
|
||||||
|
'(800) CALL-BRAD',
|
||||||
|
'(540) 155-5555',
|
||||||
|
'(999) 654-3210',
|
||||||
|
'1234567890',
|
||||||
|
];
|
||||||
|
badPhones.forEach(phone => {
|
||||||
|
control.setValue(phone);
|
||||||
|
expect(Validators.PhoneValidator(control)).toEqual({phone: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
const goodPhones = [
|
||||||
|
'12345678909',
|
||||||
|
'1-800-555-5555',
|
||||||
|
'(987) 654-3210',
|
||||||
|
'(540) 555-1212 x321',
|
||||||
|
'(540) 555-1212 ext 321',
|
||||||
|
'(540) 555-1212 Ext. 321',
|
||||||
|
'540.555.5555',
|
||||||
|
'540.555.5555extension321',
|
||||||
|
];
|
||||||
|
goodPhones.forEach(phone => {
|
||||||
|
control.setValue(phone);
|
||||||
|
expect(Validators.PhoneValidator(control)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return phone validator message', () => {
|
||||||
|
control.setValue('bad_number');
|
||||||
|
expect(Validators.PhoneValidatorMessage(err, field)).toContain('bad_number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error if at least one checkbox in multicheckbox is not checked', () => {
|
||||||
|
control.setValue({});
|
||||||
|
expect(Validators.MulticheckboxValidator(control)).toEqual({required: true});
|
||||||
|
|
||||||
|
control.setValue({
|
||||||
|
checkbox_a: true,
|
||||||
|
checkbox_b: false,
|
||||||
|
checkbox_c: false,
|
||||||
|
});
|
||||||
|
expect(Validators.MulticheckboxValidator(control)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return multicheckbox validator message', () => {
|
||||||
|
control.setValue({});
|
||||||
|
expect(Validators.MulticheckboxValidatorMessage(err, field))
|
||||||
|
.toEqual('At least one of these checkboxes must be selected.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should min validation message', () => {
|
||||||
|
field.templateOptions.min = 42;
|
||||||
|
expect(Validators.MinValidationMessage(err, field)).toContain('42');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should max validation message', () => {
|
||||||
|
field.templateOptions.max = 42;
|
||||||
|
expect(Validators.MaxValidationMessage(err, field)).toContain('42');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error', () => {
|
||||||
|
expect(Validators.ShowError(field as FieldType)).toBeFalsy();
|
||||||
|
|
||||||
|
control.setErrors({url: true});
|
||||||
|
control.markAsDirty();
|
||||||
|
expect(Validators.ShowError(field as FieldType)).toBeTruthy();
|
||||||
|
|
||||||
|
field.formControl = undefined;
|
||||||
|
expect(Validators.ShowError(field as FieldType)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {FormControl, ValidationErrors} from '@angular/forms';
|
||||||
|
import {FieldType, FormlyFieldConfig} from '@ngx-formly/core';
|
||||||
|
import EMAIL_REGEX from './email.regex';
|
||||||
|
import PHONE_REGEX from './phone.regex';
|
||||||
|
import URL_REGEX from './url.regex';
|
||||||
|
|
||||||
|
export function EmailValidator(control: FormControl): ValidationErrors {
|
||||||
|
return !control.value || EMAIL_REGEX.test(control.value) ? null : {email: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EmailValidatorMessage(err, field: FormlyFieldConfig) {
|
||||||
|
return `"${field.formControl.value}" is not a valid email address`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UrlValidator(control: FormControl): ValidationErrors {
|
||||||
|
return !control.value || URL_REGEX.test(control.value) ? null : {url: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UrlValidatorMessage(err, field: FormlyFieldConfig) {
|
||||||
|
return `We cannot save "${field.formControl.value}". Please provide the full path, including http:// or https://`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PhoneValidator(control: FormControl): ValidationErrors {
|
||||||
|
return !control.value || PHONE_REGEX.test(control.value) ? null : {phone: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PhoneValidatorMessage(err, field: FormlyFieldConfig) {
|
||||||
|
return `"${field.formControl.value}" is not a valid phone number`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MulticheckboxValidator(control: FormControl): ValidationErrors {
|
||||||
|
if (control.value) {
|
||||||
|
for (const key in control.value) {
|
||||||
|
if (control.value[key] === true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {required: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MulticheckboxValidatorMessage(err, field: FormlyFieldConfig) {
|
||||||
|
return 'At least one of these checkboxes must be selected.';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MinValidationMessage(err, field) {
|
||||||
|
return `This value should be more than ${field.templateOptions.min}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MaxValidationMessage(err, field) {
|
||||||
|
return `This value should be less than ${field.templateOptions.max}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShowError(field: FieldType) {
|
||||||
|
return field.formControl &&
|
||||||
|
field.formControl.invalid &&
|
||||||
|
(
|
||||||
|
field.formControl.dirty ||
|
||||||
|
(field.options && field.options.parentForm && field.options.parentForm.submitted) ||
|
||||||
|
(field.field && field.field.validation && field.field.validation.show)
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import {RouterModule, Routes} from '@angular/router';
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
|
import {SessionRedirectComponent} from 'sartography-workflow-lib';
|
||||||
import {ModelerComponent} from './modeler/modeler.component';
|
import {ModelerComponent} from './modeler/modeler.component';
|
||||||
|
import {SignInComponent} from './sign-in/sign-in.component';
|
||||||
|
import {SignOutComponent} from './sign-out/sign-out.component';
|
||||||
import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component';
|
import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component';
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +11,18 @@ const appRoutes: Routes = [
|
||||||
{path: 'modeler/:workflowSpecId', component: ModelerComponent},
|
{path: 'modeler/:workflowSpecId', component: ModelerComponent},
|
||||||
{path: 'modeler/:workflowSpecId/:fileMetaId', component: ModelerComponent},
|
{path: 'modeler/:workflowSpecId/:fileMetaId', component: ModelerComponent},
|
||||||
{path: '', component: WorkflowSpecListComponent},
|
{path: '', component: WorkflowSpecListComponent},
|
||||||
|
{
|
||||||
|
path: 'sign-in',
|
||||||
|
component: SignInComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'sign-out',
|
||||||
|
component: SignOutComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'session/:token',
|
||||||
|
component: SessionRedirectComponent
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -18,4 +32,5 @@ const appRoutes: Routes = [
|
||||||
],
|
],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
})
|
})
|
||||||
export class AppRoutingModule { }
|
export class AppRoutingModule {
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import {HttpClientModule} from '@angular/common/http';
|
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
|
||||||
import {NgModule} from '@angular/core';
|
import {Injectable, NgModule} from '@angular/core';
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
import {MatButtonModule} from '@angular/material/button';
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
import {MatCardModule} from '@angular/material/card';
|
import {MatCardModule} from '@angular/material/card';
|
||||||
import {MatDialogModule} from '@angular/material/dialog';
|
import {MatDialogModule} from '@angular/material/dialog';
|
||||||
import {MatDividerModule} from '@angular/material/divider';
|
import {MatDividerModule} from '@angular/material/divider';
|
||||||
|
import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field';
|
||||||
import {MatIconModule} from '@angular/material/icon';
|
import {MatIconModule} from '@angular/material/icon';
|
||||||
import {MatInputModule} from '@angular/material/input';
|
import {MatInputModule} from '@angular/material/input';
|
||||||
import {MatListModule} from '@angular/material/list';
|
import {MatListModule} from '@angular/material/list';
|
||||||
|
@ -18,7 +19,7 @@ import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {FormlyModule} from '@ngx-formly/core';
|
import {FormlyModule} from '@ngx-formly/core';
|
||||||
import {FormlyMaterialModule} from '@ngx-formly/material';
|
import {FormlyMaterialModule} from '@ngx-formly/material';
|
||||||
import {AppEnvironment} from 'sartography-workflow-lib';
|
import {AppEnvironment, AuthInterceptor} from 'sartography-workflow-lib';
|
||||||
import {environment} from '../environments/environment';
|
import {environment} from '../environments/environment';
|
||||||
import {DeleteFileDialogComponent} from './_dialogs/delete-file-dialog/delete-file-dialog.component';
|
import {DeleteFileDialogComponent} from './_dialogs/delete-file-dialog/delete-file-dialog.component';
|
||||||
import {DeleteWorkflowSpecDialogComponent} from './_dialogs/delete-workflow-spec-dialog/delete-workflow-spec-dialog.component';
|
import {DeleteWorkflowSpecDialogComponent} from './_dialogs/delete-workflow-spec-dialog/delete-workflow-spec-dialog.component';
|
||||||
|
@ -26,12 +27,15 @@ import {FileMetaDialogComponent} from './_dialogs/file-meta-dialog/file-meta-dia
|
||||||
import {NewFileDialogComponent} from './_dialogs/new-file-dialog/new-file-dialog.component';
|
import {NewFileDialogComponent} from './_dialogs/new-file-dialog/new-file-dialog.component';
|
||||||
import {OpenFileDialogComponent} from './_dialogs/open-file-dialog/open-file-dialog.component';
|
import {OpenFileDialogComponent} from './_dialogs/open-file-dialog/open-file-dialog.component';
|
||||||
import {WorkflowSpecDialogComponent} from './_dialogs/workflow-spec-dialog/workflow-spec-dialog.component';
|
import {WorkflowSpecDialogComponent} from './_dialogs/workflow-spec-dialog/workflow-spec-dialog.component';
|
||||||
|
import {EmailValidator, EmailValidatorMessage, ShowError} from './_forms/validators/formly.validator';
|
||||||
import {GetIconCodePipe} from './_pipes/get-icon-code.pipe';
|
import {GetIconCodePipe} from './_pipes/get-icon-code.pipe';
|
||||||
import {AppRoutingModule} from './app-routing.module';
|
import {AppRoutingModule} from './app-routing.module';
|
||||||
import {AppComponent} from './app.component';
|
import {AppComponent} from './app.component';
|
||||||
import {DiagramComponent} from './diagram/diagram.component';
|
import {DiagramComponent} from './diagram/diagram.component';
|
||||||
import {FileListComponent} from './file-list/file-list.component';
|
import {FileListComponent} from './file-list/file-list.component';
|
||||||
import {ModelerComponent} from './modeler/modeler.component';
|
import {ModelerComponent} from './modeler/modeler.component';
|
||||||
|
import {SignInComponent} from './sign-in/sign-in.component';
|
||||||
|
import {SignOutComponent} from './sign-out/sign-out.component';
|
||||||
import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component';
|
import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list.component';
|
||||||
|
|
||||||
export class ThisEnvironment implements AppEnvironment {
|
export class ThisEnvironment implements AppEnvironment {
|
||||||
|
@ -41,6 +45,22 @@ export class ThisEnvironment implements AppEnvironment {
|
||||||
irbUrl = environment.irbUrl;
|
irbUrl = environment.irbUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppFormlyConfig {
|
||||||
|
public static config = {
|
||||||
|
extras: {
|
||||||
|
showError: ShowError,
|
||||||
|
},
|
||||||
|
validators: [
|
||||||
|
{name: 'email', validation: EmailValidator},
|
||||||
|
],
|
||||||
|
validationMessages: [
|
||||||
|
{name: 'email', message: EmailValidatorMessage},
|
||||||
|
{name: 'required', message: 'This field is required.'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
|
@ -53,6 +73,8 @@ export class ThisEnvironment implements AppEnvironment {
|
||||||
ModelerComponent,
|
ModelerComponent,
|
||||||
NewFileDialogComponent,
|
NewFileDialogComponent,
|
||||||
OpenFileDialogComponent,
|
OpenFileDialogComponent,
|
||||||
|
SignInComponent,
|
||||||
|
SignOutComponent,
|
||||||
WorkflowSpecDialogComponent,
|
WorkflowSpecDialogComponent,
|
||||||
WorkflowSpecListComponent,
|
WorkflowSpecListComponent,
|
||||||
],
|
],
|
||||||
|
@ -61,11 +83,7 @@ export class ThisEnvironment implements AppEnvironment {
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
FormlyMaterialModule,
|
FormlyMaterialModule,
|
||||||
FormlyModule.forRoot({
|
FormlyModule.forRoot(AppFormlyConfig.config),
|
||||||
validationMessages: [
|
|
||||||
{name: 'required', message: 'This field is required'},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
FormsModule,
|
FormsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
@ -92,7 +110,15 @@ export class ThisEnvironment implements AppEnvironment {
|
||||||
OpenFileDialogComponent,
|
OpenFileDialogComponent,
|
||||||
WorkflowSpecDialogComponent,
|
WorkflowSpecDialogComponent,
|
||||||
],
|
],
|
||||||
providers: [{provide: 'APP_ENVIRONMENT', useClass: ThisEnvironment}]
|
providers: [
|
||||||
|
{provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {appearance: 'fill'}},
|
||||||
|
{provide: 'APP_ENVIRONMENT', useClass: ThisEnvironment},
|
||||||
|
{
|
||||||
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
useClass: AuthInterceptor,
|
||||||
|
multi: true
|
||||||
|
},
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="full-height" fxLayout="column" fxLayoutAlign="center center">
|
||||||
|
<h1>Fake UVA NetBadge Sign In (for testing only)</h1>
|
||||||
|
<formly-form [fields]="fields" [form]="form" [model]="model">
|
||||||
|
<mat-error *ngIf="error">{{error}}</mat-error>
|
||||||
|
<button
|
||||||
|
(click)="signIn()"
|
||||||
|
[disabled]="form.invalid"
|
||||||
|
color="primary"
|
||||||
|
id="sign_in"
|
||||||
|
mat-flat-button
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</formly-form>
|
||||||
|
</div>
|
|
@ -0,0 +1,9 @@
|
||||||
|
form {
|
||||||
|
min-width: 150px;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
|
import {MatInputModule} from '@angular/material/input';
|
||||||
|
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {ActivatedRoute, convertToParamMap, Router} from '@angular/router';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {FormlyModule} from '@ngx-formly/core';
|
||||||
|
import {FormlyMaterialModule} from '@ngx-formly/material';
|
||||||
|
import {of} from 'rxjs';
|
||||||
|
import {ApiService, MockEnvironment, mockUser} from 'sartography-workflow-lib';
|
||||||
|
import {EmailValidator, EmailValidatorMessage} from '../_forms/validators/formly.validator';
|
||||||
|
import {SignInComponent} from './sign-in.component';
|
||||||
|
|
||||||
|
describe('SignInComponent', () => {
|
||||||
|
let component: SignInComponent;
|
||||||
|
let fixture: ComponentFixture<SignInComponent>;
|
||||||
|
let httpMock: HttpTestingController;
|
||||||
|
const mockRouter = {navigate: jasmine.createSpy('navigate')};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [SignInComponent],
|
||||||
|
imports: [
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
FormlyModule.forRoot({
|
||||||
|
validators: [
|
||||||
|
{name: 'email', validation: EmailValidator},
|
||||||
|
],
|
||||||
|
validationMessages: [
|
||||||
|
{name: 'email', message: EmailValidatorMessage},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
FormlyMaterialModule,
|
||||||
|
HttpClientTestingModule,
|
||||||
|
FormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ApiService,
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {paramMap: of(convertToParamMap({study_id: '0', workflow_id: '0', task_id: '0'}))}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router,
|
||||||
|
useValue: mockRouter
|
||||||
|
},
|
||||||
|
{provide: 'APP_ENVIRONMENT', useClass: MockEnvironment},
|
||||||
|
],
|
||||||
|
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
httpMock = TestBed.get(HttpTestingController);
|
||||||
|
fixture = TestBed.createComponent(SignInComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
httpMock.verify();
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fake sign in during testing', () => {
|
||||||
|
const openSessionSpy = spyOn((component as any).api, 'openSession').and.stub();
|
||||||
|
(component as any).environment.production = false;
|
||||||
|
component.model = mockUser;
|
||||||
|
component.signIn();
|
||||||
|
expect(openSessionSpy).toHaveBeenCalledWith(mockUser);
|
||||||
|
expect(component.error).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display an error if sign in is called on production', () => {
|
||||||
|
const openSessionSpy = spyOn((component as any).api, 'openSession').and.stub();
|
||||||
|
(component as any).environment.production = true;
|
||||||
|
component.signIn();
|
||||||
|
expect(openSessionSpy).not.toHaveBeenCalled();
|
||||||
|
expect(component.error).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify the user and redirect to home page on production', () => {
|
||||||
|
const getUserSpy = spyOn((component as any).api, 'getUser').and.returnValue(of(mockUser));
|
||||||
|
(component as any).environment.production = true;
|
||||||
|
(component as any)._redirectOnProduction();
|
||||||
|
expect(getUserSpy).toHaveBeenCalled();
|
||||||
|
expect(mockRouter.navigate).toHaveBeenCalledWith(['/']);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,93 @@
|
||||||
|
import {Component, Inject, OnInit} from '@angular/core';
|
||||||
|
import {FormGroup} from '@angular/forms';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
import {FormlyFieldConfig} from '@ngx-formly/core';
|
||||||
|
import {ApiService, AppEnvironment, User} from 'sartography-workflow-lib';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sign-in',
|
||||||
|
templateUrl: './sign-in.component.html',
|
||||||
|
styleUrls: ['./sign-in.component.scss']
|
||||||
|
})
|
||||||
|
export class SignInComponent implements OnInit {
|
||||||
|
form = new FormGroup({});
|
||||||
|
model: any = {};
|
||||||
|
fields: FormlyFieldConfig[] = [
|
||||||
|
{
|
||||||
|
key: 'uid',
|
||||||
|
type: 'input',
|
||||||
|
defaultValue: 'czn1z',
|
||||||
|
templateOptions: {
|
||||||
|
required: true,
|
||||||
|
label: 'UVA Computing ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'email_address',
|
||||||
|
type: 'input',
|
||||||
|
defaultValue: 'czn1z@virginia.edu',
|
||||||
|
templateOptions: {
|
||||||
|
required: true,
|
||||||
|
type: 'email',
|
||||||
|
label: 'UVA Email Address',
|
||||||
|
},
|
||||||
|
validators: {validation: ['email']},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'first_name',
|
||||||
|
type: 'input',
|
||||||
|
defaultValue: 'Cordi',
|
||||||
|
templateOptions: {
|
||||||
|
label: 'First Name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'last_name',
|
||||||
|
type: 'input',
|
||||||
|
defaultValue: 'Nator',
|
||||||
|
templateOptions: {
|
||||||
|
label: 'First Name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
error: Error;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject('APP_ENVIRONMENT') private environment: AppEnvironment,
|
||||||
|
private router: Router,
|
||||||
|
private api: ApiService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this._redirectOnProduction();
|
||||||
|
}
|
||||||
|
|
||||||
|
signIn() {
|
||||||
|
this.error = undefined;
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
|
||||||
|
// For testing purposes, create a user to simulate login.
|
||||||
|
if (!this.environment.production) {
|
||||||
|
this.api.openSession(this.model);
|
||||||
|
} else {
|
||||||
|
this.error = new Error('This feature does not work in production.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is production, verify the user and redirect to home page.
|
||||||
|
private _redirectOnProduction() {
|
||||||
|
if (this.environment.production) {
|
||||||
|
this.api.getUser().subscribe((user: User) => {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}, e => {
|
||||||
|
this.error = e;
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="full-height" fxLayout="column" fxLayoutAlign="center center">
|
||||||
|
<h1>You have been signed out.</h1>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
color="accent"
|
||||||
|
(click)="goHome()">Ok</button>
|
||||||
|
</div>
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||||
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
import {ApiService, MockEnvironment} from 'sartography-workflow-lib';
|
||||||
|
|
||||||
|
import {SignOutComponent} from './sign-out.component';
|
||||||
|
|
||||||
|
describe('SignOutComponent', () => {
|
||||||
|
let component: SignOutComponent;
|
||||||
|
let fixture: ComponentFixture<SignOutComponent>;
|
||||||
|
let httpMock: HttpTestingController;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [SignOutComponent],
|
||||||
|
imports: [
|
||||||
|
HttpClientTestingModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ApiService,
|
||||||
|
{provide: 'APP_ENVIRONMENT', useClass: MockEnvironment},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
httpMock = TestBed.get(HttpTestingController);
|
||||||
|
fixture = TestBed.createComponent(SignOutComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
httpMock.verify();
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should go home', () => {
|
||||||
|
const openUrlSpy = spyOn((component as any).api, 'openUrl').and.stub();
|
||||||
|
component.goHome();
|
||||||
|
expect(openUrlSpy).toHaveBeenCalledWith('/');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,21 @@
|
||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {ApiService} from 'sartography-workflow-lib';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sign-out',
|
||||||
|
templateUrl: './sign-out.component.html',
|
||||||
|
styleUrls: ['./sign-out.component.scss']
|
||||||
|
})
|
||||||
|
export class SignOutComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(private api: ApiService) {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
goHome() {
|
||||||
|
this.api.openUrl('/');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue