From 16d466db63e69454fd861f2dac175e78a857c510 Mon Sep 17 00:00:00 2001 From: kurilova Date: Wed, 12 Jun 2024 12:52:25 +0000 Subject: [PATCH 1/3] Form from json init commit --- modules/ui/src/app/mocks/profile.mock.ts | 44 ++++ modules/ui/src/app/model/profile.ts | 32 +++ .../profile-form/profile-form.component.html | 200 +++++++++++++++++- .../profile-form/profile-form.component.scss | 24 ++- .../profile-form/profile-form.component.ts | 69 ++++-- .../risk-assessment.component.html | 2 +- .../risk-assessment.component.scss | 22 +- .../risk-assessment.component.ts | 13 +- .../risk-assessment/risk-assessment.store.ts | 25 ++- .../ui/src/app/services/test-run.service.ts | 6 +- 10 files changed, 407 insertions(+), 30 deletions(-) diff --git a/modules/ui/src/app/mocks/profile.mock.ts b/modules/ui/src/app/mocks/profile.mock.ts index f5a9120b0..207f6e3f1 100644 --- a/modules/ui/src/app/mocks/profile.mock.ts +++ b/modules/ui/src/app/mocks/profile.mock.ts @@ -15,6 +15,7 @@ */ import { Profile } from '../model/profile'; +import { FormControlType, ProfileFormat } from '../model/profile'; export const PROFILE_MOCK: Profile = { name: 'Profile name', @@ -25,3 +26,46 @@ export const PROFILE_MOCK_2: Profile = { name: 'Second profile name', sections: [], }; + +export const PROFILE_FORM: ProfileFormat[] = [ + { + question: 'Email', + type: FormControlType.TEXT, + validation: { + required: true, + }, + }, + { + question: 'What type of device do you need reviewed?', + type: FormControlType.TEXT, + validation: { + required: true, + }, + description: 'This tells us about the device', + }, + { + question: + 'Has this device already been through a criticality assessment with testrun?', + type: FormControlType.SELECT, + options: [], + validation: { + max: '128', + required: true, + }, + }, + { + question: 'What features does the device have?', + description: + 'This tells us about the data your device will collectThis tells us about the data your device will collect', + type: FormControlType.SELECT_MULTIPLE, + options: ['Wi-fi', 'Bluetooth', 'ZigBee / Z-Wave / Thread / Matter'], + validation: { + required: true, + }, + }, + { + question: 'Comments', + type: FormControlType.TEXT, + description: 'Please enter any comments here', + }, +]; diff --git a/modules/ui/src/app/model/profile.ts b/modules/ui/src/app/model/profile.ts index 28bf4bd1c..9d62b5eb6 100644 --- a/modules/ui/src/app/model/profile.ts +++ b/modules/ui/src/app/model/profile.ts @@ -28,3 +28,35 @@ export interface Profile { name: string; sections: ProfileSection[]; } + +export interface ProfileRequestBody { + name: string; + questions: Question[]; +} + +export interface Question { + question?: string; + answer?: string | number[]; +} + +export enum FormControlType { + SELECT = 'select', + TEXTAREA = 'text-long', + EMAIL_MULTIPLE = 'email-multiple', + SELECT_MULTIPLE = 'select-multiple', + TEXT = 'text', +} + +export interface Validation { + required?: boolean; + max?: string; +} + +export interface ProfileFormat { + question: string; + type: FormControlType; + description?: string; + options?: string[]; + default?: string; + validation?: Validation; +} diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html index 33c70f809..8558cdb61 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html @@ -16,7 +16,7 @@

Profile name *

- + Specify risk assessment profile name + + + +
@@ -52,3 +68,185 @@ Discard
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ description }} + + + + + + + {{ description }} + + + + + + + {{ description }} + + + + +
+

+ + {{ option }} + +

+ {{ + description + }} +
+
+ + + + + + {{ option }} + + + {{ + description + }} + + diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss index 003ed84a8..7157afcdd 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.scss @@ -21,7 +21,6 @@ display: flex; flex-direction: column; align-items: flex-start; - gap: 16px; padding: 8px 16px 8px 24px; } .field-label { @@ -29,19 +28,40 @@ color: $grey-800; font-size: 18px; line-height: 24px; + padding-top: 24px; + padding-bottom: 16px; + &:first-child { + padding-top: 0; + } } mat-form-field { width: 100%; } + .field-hint { + font-family: $font-secondary; + font-size: 12px; + font-weight: 400; + line-height: 16px; + text-align: left; + padding-top: 8px; + } } .form-actions { display: flex; gap: 16px; - padding: 0 24px; + padding: 8px 24px 24px 24px; } .save-draft-button, .discard-button { color: $primary; } + +.field-select-multiple { + .field-select-checkbox:first-of-type { + margin-top: 0; + } .field-select-checkbox:last-of-type { + margin-bottom: 8px; + } +} diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts index bb8e5b12e..fa2dca057 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.ts @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - ChangeDetectionStrategy, - Component, - Input, - OnInit, -} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { CommonModule } from '@angular/common'; import { AbstractControl, + FormControl, FormBuilder, FormGroup, ReactiveFormsModule, @@ -34,6 +33,9 @@ import { Profile } from '../../../model/profile'; import { ProfileValidators } from './profile.validators'; import { MatError } from '@angular/material/form-field'; +import { PROFILE_FORM } from '../../../mocks/profile.mock'; +import { FormControlType, ProfileFormat } from '../../../model/profile'; + @Component({ selector: 'app-profile-form', standalone: true, @@ -43,38 +45,65 @@ import { MatError } from '@angular/material/form-field'; ReactiveFormsModule, MatInputModule, MatError, + MatFormFieldModule, + MatSelectModule, + MatCheckboxModule, ], templateUrl: './profile-form.component.html', styleUrl: './profile-form.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProfileFormComponent implements OnInit { - profileForm!: FormGroup; + public readonly FormControlType = FormControlType; + @Input() profileFormat!: ProfileFormat[]; + profileForm: FormGroup = this.fb.group({}); @Input() profiles!: Profile[]; constructor( private deviceValidators: DeviceValidators, private profileValidators: ProfileValidators, private fb: FormBuilder - ) {} + ) { + + } + + ngOnInit() { + this.profileForm = this.createProfileForm(this.profileFormat); + } get nameControl() { return this.profileForm.get('name') as AbstractControl; } - ngOnInit() { - this.createProfileForm(); + createProfileForm(questions: ProfileFormat[]): FormGroup { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const group: any = {}; + + group['name'] = new FormControl('', [ + Validators.required, + this.deviceValidators.deviceStringFormat(), + this.profileValidators.differentProfileName(this.profiles), + ]); + + questions.forEach((question, index) => { + if (question.type === FormControlType.SELECT_MULTIPLE) { + group[index] = this.getMultiSelectGroup(question); + } else { + group[index] = new FormControl(question.default || ''); + } + }); + return new FormGroup(group); } - createProfileForm() { - this.profileForm = this.fb.group({ - name: [ - '', - [ - Validators.required, - this.deviceValidators.deviceStringFormat(), - this.profileValidators.differentProfileName(this.profiles), - ], - ], + getMultiSelectGroup(question: ProfileFormat): FormGroup { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const group: any = {}; + question.options?.forEach((option, index) => { + group[index] = false; }); + return this.fb.group(group); + } + + getFormGroup(name: string): FormGroup { + return this.profileForm?.controls[name] as FormGroup; } } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html index 8975b819b..06425e474 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.html @@ -21,7 +21,7 @@

Risk assessment

- +
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss index 0fc2c3784..c8417256b 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss @@ -16,6 +16,11 @@ @import 'src/theming/colors'; @import 'src/theming/variables'; +:host { + overflow: hidden; + display: flex; +} + .risk-assessment-content-empty { height: 100%; width: calc(100%); @@ -32,17 +37,22 @@ .risk-assessment-container, .risk-assessment-content { - height: 100%; background-color: $white; } +.risk-assessment-container { + flex: 1; +} + .risk-assessment-content { + container-name: risk-assessment-content; + container-type: inline-size; display: flex; flex-direction: column; gap: 14px; - width: calc(100% - $profiles-drawer-width); box-sizing: border-box; padding-right: 94px; + overflow: hidden; } .risk-assessment-toolbar { @@ -53,6 +63,14 @@ .main-content { padding: 16px 32px; + overflow: scroll; + max-width: 694px; +} + +@container risk-assessment-content (width < 1078px) { + .main-content { + width: calc(100% - $profiles-drawer-width); + } } .profiles-drawer { diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts index ca5725a59..69c6aa4f9 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + OnDestroy, + OnInit, +} from '@angular/core'; import { RiskAssessmentStore } from './risk-assessment.store'; import { DeleteFormComponent } from '../../components/delete-form/delete-form.component'; import { Subject, takeUntil } from 'rxjs'; @@ -26,7 +31,7 @@ import { MatDialog } from '@angular/material/dialog'; providers: [RiskAssessmentStore], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class RiskAssessmentComponent implements OnDestroy { +export class RiskAssessmentComponent implements OnInit, OnDestroy { viewModel$ = this.store.viewModel$; isOpenProfileForm = false; private destroy$: Subject = new Subject(); @@ -35,6 +40,10 @@ export class RiskAssessmentComponent implements OnDestroy { public dialog: MatDialog ) {} + ngOnInit() { + this.store.getProfilesFormat(); + } + ngOnDestroy() { this.destroy$.next(true); this.destroy$.unsubscribe(); diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts index 737d8f024..3a47abf53 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts @@ -19,7 +19,7 @@ import { ComponentStore } from '@ngrx/component-store'; import { tap, withLatestFrom } from 'rxjs/operators'; import { exhaustMap } from 'rxjs'; import { TestRunService } from '../../services/test-run.service'; -import { Profile } from '../../model/profile'; +import { Profile, ProfileFormat } from '../../model/profile'; import { FocusManagerService } from '../../services/focus-manager.service'; import { Store } from '@ngrx/store'; import { AppState } from '../../store/state'; @@ -28,15 +28,25 @@ import { setRiskProfiles } from '../../store/actions'; export interface AppComponentState { profiles: Profile[]; + profileFormat: ProfileFormat[]; } @Injectable() export class RiskAssessmentStore extends ComponentStore { profiles$ = this.store.select(selectRiskProfiles); + profileFormat$ = this.select(state => state.profileFormat); viewModel$ = this.select({ profiles: this.profiles$, + profileFormat: this.profileFormat$, }); + updateProfileFormat = this.updater( + (state, profileFormat: ProfileFormat[]) => ({ + ...state, + profileFormat, + }) + ); + deleteProfile = this.effect(trigger$ => { return trigger$.pipe( exhaustMap((name: string) => { @@ -69,6 +79,18 @@ export class RiskAssessmentStore extends ComponentStore { } ); + getProfilesFormat = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunService.fetchProfilesFormat().pipe( + tap((profileFormat: ProfileFormat[]) => { + this.updateProfileFormat(profileFormat); + }) + ); + }) + ); + }); + private removeProfile(name: string, current: Profile[]): void { const profiles = current.filter(profile => profile.name !== name); this.updateProfiles(profiles); @@ -85,6 +107,7 @@ export class RiskAssessmentStore extends ComponentStore { ) { super({ profiles: [], + profileFormat: [], }); } } diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 4cf4a140d..9aa3ae367 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -28,7 +28,7 @@ import { } from '../model/testrun-status'; import { Version } from '../model/version'; import { Certificate } from '../model/certificate'; -import { Profile } from '../model/profile'; +import { Profile, ProfileFormat } from '../model/profile'; const API_URL = `http://${window.location.hostname}:8000`; export const SYSTEM_STOP = '/system/stop'; @@ -260,4 +260,8 @@ export class TestRunService { downloadZip(url: string, profile: string) { return this.http.post(url, JSON.stringify({ profile })); } + + fetchProfilesFormat(): Observable { + return this.http.get(`${API_URL}/profiles/format`); + } } From 544d3c9b4a99f5c3989c5042006a58d6f6ca13a7 Mon Sep 17 00:00:00 2001 From: kurilova Date: Thu, 13 Jun 2024 12:20:52 +0000 Subject: [PATCH 2/3] Fix design issues --- .../profile-form/profile-form.component.html | 4 ++-- .../profile-form/profile-form.component.scss | 18 ++++++++++++++---- .../profile-form/profile-form.component.ts | 12 +++++++----- .../risk-assessment.component.html | 4 +++- .../risk-assessment.component.scss | 10 +--------- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html index 8558cdb61..d6716fa61 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html @@ -122,7 +122,7 @@ formControlName: formControlName, options: options, description: description, - label: label, + label: label } "> @@ -236,7 +236,7 @@ class="risk-assessment-form-field" [formGroup]="profileForm"> Risk assessment
- +
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss index c8417256b..1f8926b2e 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.scss @@ -45,8 +45,6 @@ } .risk-assessment-content { - container-name: risk-assessment-content; - container-type: inline-size; display: flex; flex-direction: column; gap: 14px; @@ -64,13 +62,7 @@ .main-content { padding: 16px 32px; overflow: scroll; - max-width: 694px; -} - -@container risk-assessment-content (width < 1078px) { - .main-content { - width: calc(100% - $profiles-drawer-width); - } + width: calc(100% - $profiles-drawer-width); } .profiles-drawer { From 2d335fd2cd73e8cd4866ba7cce9e801dad6b9c1a Mon Sep 17 00:00:00 2001 From: kurilova Date: Fri, 14 Jun 2024 08:39:21 +0000 Subject: [PATCH 3/3] Add tests --- modules/ui/src/app/mocks/profile.mock.ts | 4 +- .../profile-form/profile-form.component.html | 16 +- .../profile-form.component.spec.ts | 146 +++++++++++++++++- .../risk-assessment.component.spec.ts | 6 +- .../risk-assessment.store.spec.ts | 41 ++++- .../src/app/services/test-run.service.spec.ts | 18 ++- 6 files changed, 217 insertions(+), 14 deletions(-) diff --git a/modules/ui/src/app/mocks/profile.mock.ts b/modules/ui/src/app/mocks/profile.mock.ts index 207f6e3f1..000be4337 100644 --- a/modules/ui/src/app/mocks/profile.mock.ts +++ b/modules/ui/src/app/mocks/profile.mock.ts @@ -30,14 +30,14 @@ export const PROFILE_MOCK_2: Profile = { export const PROFILE_FORM: ProfileFormat[] = [ { question: 'Email', - type: FormControlType.TEXT, + type: FormControlType.EMAIL_MULTIPLE, validation: { required: true, }, }, { question: 'What type of device do you need reviewed?', - type: FormControlType.TEXT, + type: FormControlType.TEXTAREA, validation: { required: true, }, diff --git a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html index d6716fa61..1738d5ef0 100644 --- a/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html +++ b/modules/ui/src/app/pages/risk-assessment/profile-form/profile-form.component.html @@ -15,8 +15,12 @@ -->
+

Profile name *

- + Specify risk assessment profile name

{ let component: ProfileFormComponent; let fixture: ComponentFixture; + let compiled: HTMLElement; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -29,6 +36,10 @@ describe('ProfileFormComponent', () => { fixture = TestBed.createComponent(ProfileFormComponent); component = fixture.componentInstance; + component.profileFormat = PROFILE_FORM; + component.profiles = [PROFILE_MOCK, PROFILE_MOCK_2]; + compiled = fixture.nativeElement as HTMLElement; + fixture.detectChanges(); }); @@ -36,5 +47,138 @@ describe('ProfileFormComponent', () => { expect(component).toBeTruthy(); }); - // TODO: Add more unit tests + describe('DOM tests', () => { + describe('Profile name input', () => { + it('should be present', () => { + const name: HTMLInputElement = compiled.querySelector( + '.form-name' + ) as HTMLInputElement; + + expect(name).toBeTruthy(); + }); + + it('should not contain errors when input is correct', () => { + const name: HTMLInputElement = compiled.querySelector( + '.form-name' + ) as HTMLInputElement; + ['name', 'Gebäude', 'jardín'].forEach(value => { + name.value = value; + name.dispatchEvent(new Event('input')); + + const errors = component.nameControl.errors; + const uiValue = name.value; + const formValue = component.nameControl.value; + + expect(uiValue).toEqual(formValue); + expect(errors).toBeNull(); + }); + }); + + it('should have "invalid_format" error when field does not satisfy validation rules', () => { + [ + 'very long value very long value very long value very long value very long value very long value very long value', + 'as&@3$', + ].forEach(value => { + const name: HTMLInputElement = compiled.querySelector( + '.form-name' + ) as HTMLInputElement; + name.value = value; + name.dispatchEvent(new Event('input')); + component.nameControl.markAsTouched(); + fixture.detectChanges(); + + const nameError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.nameControl.hasError('invalid_format'); + + expect(error).toBeTruthy(); + expect(nameError).toContain( + 'Please, check. The Profile name must be a maximum of 28 characters. Only letters, numbers, and accented letters are permitted.' + ); + }); + }); + + it('should have "required" error when field is not filled', () => { + const name: HTMLInputElement = compiled.querySelector( + '.form-name' + ) as HTMLInputElement; + name.value = ''; + name.dispatchEvent(new Event('input')); + component.nameControl.markAsTouched(); + fixture.detectChanges(); + + const nameError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.nameControl.hasError('required'); + + expect(error).toBeTruthy(); + expect(nameError).toContain('The Profile name is required'); + }); + + it('should have "required" error when field is not filled', () => { + const name: HTMLInputElement = compiled.querySelector( + '.form-name' + ) as HTMLInputElement; + name.value = 'Profile name'; + name.dispatchEvent(new Event('input')); + component.nameControl.markAsTouched(); + fixture.detectChanges(); + + const nameError = compiled.querySelector('mat-error')?.innerHTML; + const error = component.nameControl.hasError('has_same_profile_name'); + + expect(error).toBeTruthy(); + expect(nameError).toContain( + 'This Profile name is already used for another Risk Assessment profile' + ); + }); + }); + + PROFILE_FORM.forEach((item, index) => { + const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i + + it(`should have form field with specific type"`, () => { + const fields = compiled.querySelectorAll('.profile-form-field'); + + if (item.type === FormControlType.SELECT) { + const select = fields[uiIndex].querySelector('mat-select'); + expect(select).toBeTruthy(); + } else if (item.type === FormControlType.SELECT_MULTIPLE) { + const select = fields[uiIndex].querySelector('mat-checkbox'); + expect(select).toBeTruthy(); + } else { + const input = fields[uiIndex]?.querySelector('input'); + expect(input).toBeTruthy(); + } + }); + + it('should have label', () => { + const labels = compiled.querySelectorAll('.field-label'); + const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i + + const label = item?.validation?.required + ? item.question + ' *' + : item.question; + expect(labels[uiIndex].textContent?.trim()).toEqual(label); + }); + + it('should have hint', () => { + const fields = compiled.querySelectorAll('.profile-form-field'); + const uiIndex = index + 1; // as Profile name is at 0 position, the json items start from 1 i + const hint = fields[uiIndex].querySelector('mat-hint'); + + if (item.description) { + expect(hint?.textContent?.trim()).toEqual(item.description); + } else { + expect(hint).toBeNull(); + } + }); + + if (item.type === FormControlType.SELECT) { + it(`should have default value if provided`, () => { + const fields = compiled.querySelectorAll('.profile-form-field'); + const select = fields[uiIndex].querySelector('mat-select'); + expect(select?.textContent?.trim()).toEqual(item.default || ''); + }); + } + }); + }); }); diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts index 059f9888e..4dda189b7 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts @@ -29,7 +29,7 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { PROFILE_MOCK } from '../../mocks/profile.mock'; import { of } from 'rxjs'; import { Component, Input } from '@angular/core'; -import { Profile } from '../../model/profile'; +import { Profile, ProfileFormat } from '../../model/profile'; import { MatDialogRef } from '@angular/material/dialog'; import { DeleteFormComponent } from '../../components/delete-form/delete-form.component'; import { FocusManagerService } from '../../services/focus-manager.service'; @@ -54,6 +54,7 @@ describe('RiskAssessmentComponent', () => { mockRiskAssessmentStore = jasmine.createSpyObj('RiskAssessmentStore', [ 'deleteProfile', 'setFocus', + 'getProfilesFormat', ]); await TestBed.configureTestingModule({ @@ -87,6 +88,7 @@ describe('RiskAssessmentComponent', () => { beforeEach(() => { component.viewModel$ = of({ profiles: [] as Profile[], + profileFormat: [], }); mockRiskAssessmentStore.profiles$ = of([]); fixture.detectChanges(); @@ -130,6 +132,7 @@ describe('RiskAssessmentComponent', () => { beforeEach(() => { component.viewModel$ = of({ profiles: [PROFILE_MOCK, PROFILE_MOCK], + profileFormat: [], }); fixture.detectChanges(); }); @@ -188,4 +191,5 @@ class FakeProfileItemComponent { }) class FakeProfileFormComponent { @Input() profiles!: Profile[]; + @Input() profileFormat!: ProfileFormat[]; } diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts index a9add3bc4..e04a3121a 100644 --- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts +++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts @@ -14,13 +14,17 @@ * limitations under the License. */ import { TestBed } from '@angular/core/testing'; -import { of, take } from 'rxjs'; +import { of, skip, take } from 'rxjs'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { TestRunService } from '../../services/test-run.service'; import SpyObj = jasmine.SpyObj; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RiskAssessmentStore } from './risk-assessment.store'; -import { PROFILE_MOCK, PROFILE_MOCK_2 } from '../../mocks/profile.mock'; +import { + 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'; @@ -33,7 +37,11 @@ describe('RiskAssessmentStore', () => { let mockFocusManagerService: SpyObj; beforeEach(() => { - mockService = jasmine.createSpyObj(['fetchProfiles', 'deleteProfile']); + mockService = jasmine.createSpyObj([ + 'fetchProfiles', + 'deleteProfile', + 'fetchProfilesFormat', + ]); mockFocusManagerService = jasmine.createSpyObj([ 'focusFirstElementInContainer', ]); @@ -65,11 +73,23 @@ describe('RiskAssessmentStore', () => { expect(riskAssessmentStore).toBeTruthy(); }); + describe('updaters', () => { + it('should update activeFiler', (done: DoneFn) => { + riskAssessmentStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.profileFormat).toEqual(PROFILE_FORM); + done(); + }); + + riskAssessmentStore.updateProfileFormat(PROFILE_FORM); + }); + }); + describe('selectors', () => { it('should select state', done => { riskAssessmentStore.viewModel$.pipe(take(1)).subscribe(store => { expect(store).toEqual({ profiles: [PROFILE_MOCK, PROFILE_MOCK_2], + profileFormat: [], }); done(); }); @@ -136,5 +156,20 @@ describe('RiskAssessmentStore', () => { ).toHaveBeenCalledWith(); }); }); + + describe('getProfilesFormat', () => { + it('should update store', done => { + mockService.fetchProfilesFormat.and.returnValue(of(PROFILE_FORM)); + + riskAssessmentStore.viewModel$ + .pipe(skip(1), take(1)) + .subscribe(store => { + expect(store.profileFormat).toEqual(PROFILE_FORM); + done(); + }); + + riskAssessmentStore.getProfilesFormat(); + }); + }); }); }); diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts index fcf00f02b..c8c5a74b7 100644 --- a/modules/ui/src/app/services/test-run.service.spec.ts +++ b/modules/ui/src/app/services/test-run.service.spec.ts @@ -34,7 +34,7 @@ import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from '../store/state'; import { Certificate } from '../model/certificate'; import { certificate } from '../mocks/certificate.mock'; -import { PROFILE_MOCK } from '../mocks/profile.mock'; +import { PROFILE_FORM, PROFILE_MOCK } from '../mocks/profile.mock'; const MOCK_SYSTEM_CONFIG: SystemConfig = { network: { @@ -590,4 +590,20 @@ describe('TestRunService', () => { req.flush(true); }); + + describe('fetchProfilesFormat', () => { + it('should get system status data with no changes', () => { + const result = { ...PROFILE_FORM }; + + service.fetchProfilesFormat().subscribe(res => { + expect(res).toEqual(result); + }); + + const req = httpTestingController.expectOne( + 'http://localhost:8000/profiles/format' + ); + expect(req.request.method).toBe('GET'); + req.flush(result); + }); + }); });