Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions modules/ui/src/app/app.store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ import { TestRunService } from './services/test-run.service';
import SpyObj = jasmine.SpyObj;
import { device } from './mocks/device.mock';
import {
fetchRiskProfiles,
fetchSystemStatus,
setDevices,
setRiskProfiles,
} from './store/actions';
import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/testrun.mock';
import { PROFILE_MOCK } from './mocks/profile.mock';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NotificationService } from './services/notification.service';

Expand Down Expand Up @@ -66,10 +65,7 @@ describe('AppStore', () => {
let mockNotificationService: SpyObj<NotificationService>;

beforeEach(() => {
mockService = jasmine.createSpyObj('mockService', [
'fetchDevices',
'fetchProfiles',
]);
mockService = jasmine.createSpyObj('mockService', ['fetchDevices']);
mockNotificationService = jasmine.createSpyObj('mockNotificationService', [
'notify',
]);
Expand Down Expand Up @@ -183,18 +179,10 @@ describe('AppStore', () => {
});

describe('fetchProfiles', () => {
const riskProfiles = [PROFILE_MOCK];

beforeEach(() => {
mockService.fetchProfiles.and.returnValue(of(riskProfiles));
});

it('should dispatch action setRiskProfiles', () => {
it('should dispatch action fetchRiskProfiles', () => {
appStore.getRiskProfiles();

expect(store.dispatch).toHaveBeenCalledWith(
setRiskProfiles({ riskProfiles })
);
expect(store.dispatch).toHaveBeenCalledWith(fetchRiskProfiles());
});
});

Expand Down
11 changes: 3 additions & 8 deletions modules/ui/src/app/app.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ import {
setDevices,
setIsOpenStartTestrun,
fetchSystemStatus,
setRiskProfiles,
fetchRiskProfiles,
} from './store/actions';
import { TestrunStatus } from './model/testrun-status';
import { Profile } from './model/profile';
import { SettingMissedError, SystemInterfaces } from './model/setting';

export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN';
Expand Down Expand Up @@ -108,12 +107,8 @@ export class AppStore extends ComponentStore<AppComponentState> {

getRiskProfiles = this.effect(trigger$ => {
return trigger$.pipe(
exhaustMap(() => {
return this.testRunService.fetchProfiles().pipe(
tap((riskProfiles: Profile[]) => {
this.store.dispatch(setRiskProfiles({ riskProfiles }));
})
);
tap(() => {
this.store.dispatch(fetchRiskProfiles());
})
);
});
Expand Down
22 changes: 22 additions & 0 deletions modules/ui/src/app/mocks/profile.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,25 @@ export const PROFILE_FORM: ProfileFormat[] = [
},
},
];

export const NEW_PROFILE_MOCK = {
status: 'Valid',
name: 'test',
questions: [
{ question: 'Email', answer: 'a@test.te;b@test.te, c@test.te' },
{
question: 'What type of device do you need reviewed?',
answer: 'test',
},
{
question:
'Has this device already been through a criticality assessment with testrun?',
answer: 'test',
},
{
question: 'What features does the device have?',
answer: [0, 1, 2],
},
{ question: 'Comments', answer: 'test' },
],
};
11 changes: 11 additions & 0 deletions modules/ui/src/app/model/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface Profile {
export interface ProfileRequestBody {
name: string;
questions: Question[];
status?: string;
}

export interface Question {
Expand Down Expand Up @@ -60,3 +61,13 @@ export interface ProfileFormat {
default?: string;
validation?: Validation;
}

export interface ProfileRequestBody {
name: string;
questions: Question[];
}

export interface Question {
question?: string;
answer?: string | number[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
mat-flat-button
color="primary"
class="save-profile-button"
[disabled]="!profileForm.valid">
[disabled]="!profileForm.valid"
(click)="onSaveClick()">
Save Profile
</button>
<button mat-button class="save-draft-button" [disabled]="!nameControl.valid">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileFormComponent } from './profile-form.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
NEW_PROFILE_MOCK,
PROFILE_FORM,
PROFILE_MOCK,
PROFILE_MOCK_2,
Expand Down Expand Up @@ -307,20 +308,42 @@ describe('ProfileFormComponent', () => {
});

describe('Save button', () => {
it('should be enabled when required fields are present', () => {
beforeEach(() => {
component.nameControl.setValue('test');
component.getControl('0').setValue('a@test.te;b@test.te, c@test.te');
component.getControl('1').setValue('test');
component.getControl('2').setValue('test');
component.getControl('3').setValue({ 0: true, 1: true, 2: true });
component.getControl('4').setValue('test');
fixture.detectChanges();
});

it('should be enabled when required fields are present', () => {
const saveButton = compiled.querySelector(
'.save-profile-button'
) as HTMLButtonElement;

expect(saveButton.disabled).toBeFalse();
});

it('should emit new profile', () => {
const emitSpy = spyOn(component.saveProfile, 'emit');
const saveButton = compiled.querySelector(
'.save-profile-button'
) as HTMLButtonElement;
saveButton.click();

expect(emitSpy).toHaveBeenCalledWith(NEW_PROFILE_MOCK);
});
});
});

describe('Class tests', () => {
it('should reset form on save', () => {
const spyOnReset = spyOn(component.profileForm, 'reset');
component.onSaveClick();

expect(spyOnReset).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import { TextFieldModule } from '@angular/cdk/text-field';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatError, MatFormFieldModule } from '@angular/material/form-field';
Expand All @@ -40,6 +42,8 @@ import {
FormControlType,
Profile,
ProfileFormat,
ProfileRequestBody,
Question,
Validation,
} from '../../../model/profile';
import { ProfileValidators } from './profile.validators';
Expand All @@ -63,10 +67,13 @@ import { ProfileValidators } from './profile.validators';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProfileFormComponent implements OnInit {
private readonly VALID_STATUS = 'Valid';
public readonly FormControlType = FormControlType;
@Input() profileFormat!: ProfileFormat[];
profileForm: FormGroup = this.fb.group({});
@Input() profiles!: Profile[];
@Output() saveProfile = new EventEmitter<ProfileRequestBody>();

constructor(
private deviceValidators: DeviceValidators,
private profileValidators: ProfileValidators,
Expand Down Expand Up @@ -141,4 +148,52 @@ export class ProfileFormComponent implements OnInit {
getFormGroup(name: string): FormGroup {
return this.profileForm?.controls[name] as FormGroup;
}

onSaveClick() {
const response = this.buildResponseFromForm(
this.profileFormat,
this.profileForm,
true
);
this.profileForm.reset();
this.saveProfile.emit(response);
}

buildResponseFromForm(
initialQuestions: ProfileFormat[],
profileForm: FormGroup,
isValid?: boolean
): ProfileRequestBody {
const request: ProfileRequestBody = {
name: this.nameControl.value?.trim(),
questions: [],
};
const questions: Question[] = [];

initialQuestions.forEach((initialQuestion, index) => {
const question: Question = {};
question.question = initialQuestion.question;

if (initialQuestion.type === FormControlType.SELECT_MULTIPLE) {
const answer: number[] = [];
initialQuestion.options?.forEach((_, idx) => {
const value = profileForm.value[index][idx];
if (value) {
answer.push(idx);
}
});
question.answer = answer;
} else {
question.answer = profileForm.value[index]?.trim();
}
questions.push(question);
});

request.questions = questions;

if (isValid) {
request.status = this.VALID_STATUS;
}
return request;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class ProfileValidators {

public textRequired(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value.trim()) {
if (!control.value?.trim()) {
return { required: true };
}
return null;
Expand Down Expand Up @@ -82,7 +82,7 @@ export class ProfileValidators {
profiles: Profile[]
): boolean {
return (
profiles.some(profile => profile.name === profileName.trim()) || false
profiles.some(profile => profile.name === profileName?.trim()) || false
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ <h2 class="title">Risk assessment</h2>
<div class="main-content">
<app-profile-form
[profiles]="vm.profiles"
[profileFormat]="vm.profileFormat"></app-profile-form>
[profileFormat]="vm.profileFormat"
(saveProfile)="saveProfile($event)"></app-profile-form>
</div>
</mat-drawer-content>
</mat-drawer-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('RiskAssessmentComponent', () => {
'deleteProfile',
'setFocus',
'getProfilesFormat',
'saveProfile',
]);

await TestBed.configureTestingModule({
Expand Down Expand Up @@ -201,6 +202,23 @@ describe('RiskAssessmentComponent', () => {
).toHaveBeenCalled();
});
});

describe('#saveProfile', () => {
it('should call store saveProfile', () => {
component.saveProfile({ name: 'test', questions: [] });

expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalledWith({
name: 'test',
questions: [],
});
});

it('should close the form', () => {
component.saveProfile({ name: 'test', questions: [] });

expect(component.isOpenProfileForm).toBeFalse();
});
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Subject, takeUntil } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';
import { FocusManagerService } from '../../services/focus-manager.service';
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { ProfileRequestBody } from '../../model/profile';

@Component({
selector: 'app-risk-assessment',
Expand Down Expand Up @@ -83,6 +84,11 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy {
});
}

saveProfile(profile: ProfileRequestBody): void {
this.store.saveProfile(profile);
this.isOpenProfileForm = false;
}

private setFocus(index: number): void {
const nextItem = window.document.querySelector(
`.profile-item-${index + 1}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import SpyObj = jasmine.SpyObj;
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RiskAssessmentStore } from './risk-assessment.store';
import {
NEW_PROFILE_MOCK,
PROFILE_FORM,
PROFILE_MOCK,
PROFILE_MOCK_2,
} from '../../mocks/profile.mock';
import { FocusManagerService } from '../../services/focus-manager.service';
import { AppState } from '../../store/state';
import { selectRiskProfiles } from '../../store/selectors';
import { setRiskProfiles } from '../../store/actions';
import { fetchRiskProfiles, setRiskProfiles } from '../../store/actions';

describe('RiskAssessmentStore', () => {
let riskAssessmentStore: RiskAssessmentStore;
Expand All @@ -41,7 +42,9 @@ describe('RiskAssessmentStore', () => {
'fetchProfiles',
'deleteProfile',
'fetchProfilesFormat',
'saveProfile',
]);
mockService.saveProfile.and.returnValue(of(true));
mockFocusManagerService = jasmine.createSpyObj([
'focusFirstElementInContainer',
]);
Expand Down Expand Up @@ -171,5 +174,13 @@ describe('RiskAssessmentStore', () => {
riskAssessmentStore.getProfilesFormat();
});
});

describe('saveProfile', () => {
it('should dispatch fetchRiskProfiles', () => {
riskAssessmentStore.saveProfile(NEW_PROFILE_MOCK);

expect(store.dispatch).toHaveBeenCalledWith(fetchRiskProfiles());
});
});
});
});
Loading