- + Testrun + *ngIf="vm.hasConnectionSettings === false"> Step 1: To perform a device test, please, select ports in Testrun + *ngIf="vm.hasConnectionSettings === true && vm.hasDevices === false"> Step 2: To perform a device test please Testrun Step 3: Once device is created, you are able to { 'focusFirstElementInContainer', ]); - (mockService.systemStatus$ as unknown) = of({}); - mockService.isTestrunStarted$ = of(true); - TestBed.configureTestingModule({ imports: [ RouterTestingModule, @@ -135,6 +134,9 @@ describe('AppComponent', () => { { selector: selectError, value: null }, { selector: selectMenuOpened, value: false }, { selector: selectHasDevices, value: false }, + { selector: selectIsTestrunStarted, value: false }, + { selector: selectSystemStatus, value: null }, + { selector: selectIsOpenStartTestrun, value: false }, ], }), { provide: FocusManagerService, useValue: mockFocusManagerService }, @@ -386,8 +388,7 @@ describe('AppComponent', () => { describe('Callout component visibility', () => { describe('with no connection settings', () => { beforeEach(() => { - component.hasConnectionSetting$ = of(false); - component.ngOnInit(); + store.overrideSelector(selectHasConnectionSettings, false); fixture.detectChanges(); }); @@ -426,11 +427,12 @@ describe('AppComponent', () => { describe('with system status as "Idle"', () => { beforeEach(() => { - component.hasConnectionSetting$ = of(true); + component.appStore.updateIsStatusLoaded(true); + store.overrideSelector(selectHasConnectionSettings, true); store.overrideSelector(selectHasDevices, true); - mockService.systemStatus$ = of(MOCK_PROGRESS_DATA_IDLE); - mockService.isTestrunStarted$ = of(false); - component.ngOnInit(); + store.overrideSelector(selectSystemStatus, MOCK_PROGRESS_DATA_IDLE); + store.overrideSelector(selectIsTestrunStarted, false); + fixture.detectChanges(); }); @@ -504,7 +506,11 @@ describe('AppComponent', () => { describe('with devices setted but without systemStatus data', () => { beforeEach(() => { store.overrideSelector(selectHasDevices, true); - mockService.isTestrunStarted$ = of(false); + store.overrideSelector(selectIsTestrunStarted, false); + component.appStore.updateIsStatusLoaded(true); + store.overrideSelector(selectHasConnectionSettings, true); + store.overrideSelector(selectSystemStatus, null); + fixture.detectChanges(); }); @@ -544,7 +550,7 @@ describe('AppComponent', () => { describe('with devices setted, without systemStatus data, but run the tests ', () => { beforeEach(() => { store.overrideSelector(selectHasDevices, true); - mockService.isTestrunStarted$ = of(true); + store.overrideSelector(selectIsTestrunStarted, true); fixture.detectChanges(); }); @@ -558,7 +564,10 @@ describe('AppComponent', () => { describe('with devices setted and systemStatus data', () => { beforeEach(() => { store.overrideSelector(selectHasDevices, true); - mockService.systemStatus$ = of(MOCK_PROGRESS_DATA_IN_PROGRESS); + store.overrideSelector( + selectSystemStatus, + MOCK_PROGRESS_DATA_IN_PROGRESS + ); fixture.detectChanges(); }); @@ -572,12 +581,11 @@ describe('AppComponent', () => { describe('error', () => { describe('with settingMissedError with one port is missed', () => { beforeEach(() => { - component.settingMissedError$ = of({ + store.overrideSelector(selectError, { isSettingMissed: true, devicePortMissed: true, internetPortMissed: false, }); - component.ngOnInit(); fixture.detectChanges(); }); @@ -592,12 +600,11 @@ describe('AppComponent', () => { describe('with settingMissedError with two ports are missed', () => { beforeEach(() => { - component.settingMissedError$ = of({ + store.overrideSelector(selectError, { isSettingMissed: true, devicePortMissed: true, internetPortMissed: true, }); - component.ngOnInit(); fixture.detectChanges(); }); @@ -612,7 +619,7 @@ describe('AppComponent', () => { describe('with no settingMissedError', () => { beforeEach(() => { - component.settingMissedError$ = of(null); + store.overrideSelector(selectError, null); store.overrideSelector(selectHasDevices, true); fixture.detectChanges(); }); diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index e1791764b..6b0fe8f79 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -13,33 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, ViewChild } from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; import { MatDrawer } from '@angular/material/sidenav'; -import { TestRunService } from './services/test-run.service'; -import { Observable } from 'rxjs'; -import { TestrunStatus, StatusOfTestrun } from './model/testrun-status'; +import { StatusOfTestrun } from './model/testrun-status'; import { Router } from '@angular/router'; import { CalloutType } from './model/callout-type'; -import { tap, shareReplay } from 'rxjs/operators'; import { Routes } from './model/routes'; import { FocusManagerService } from './services/focus-manager.service'; import { State, Store } from '@ngrx/store'; import { AppState } from './store/state'; -import { - selectError, - selectHasConnectionSettings, - selectInterfaces, - selectMenuOpened, -} from './store/selectors'; import { setIsOpenAddDevice, toggleMenu, updateFocusNavigation, } from './store/actions'; import { appFeatureKey } from './store/reducers'; -import { SettingMissedError, SystemInterfaces } from './model/setting'; import { GeneralSettingsComponent } from './pages/settings/general-settings.component'; import { AppStore } from './app.store'; @@ -56,22 +46,11 @@ const CLOSE_URL = '/assets/icons/close.svg'; styleUrls: ['./app.component.scss'], providers: [AppStore], }) -export class AppComponent implements OnInit { +export class AppComponent { public readonly CalloutType = CalloutType; public readonly StatusOfTestrun = StatusOfTestrun; public readonly Routes = Routes; - systemStatus$!: Observable; - isTestrunStarted$!: Observable; - hasConnectionSetting$: Observable = this.store.select( - selectHasConnectionSettings - ); - isStatusLoaded = false; private openedSettingFromToggleBtn = true; - isMenuOpen: Observable = this.store.select(selectMenuOpened); - interfaces: Observable = - this.store.select(selectInterfaces); - settingMissedError$: Observable = - this.store.select(selectError); @ViewChild('settingsDrawer') public settingsDrawer!: MatDrawer; @ViewChild('toggleSettingsBtn') public toggleSettingsBtn!: HTMLButtonElement; @@ -82,15 +61,14 @@ export class AppComponent implements OnInit { constructor( private matIconRegistry: MatIconRegistry, private domSanitizer: DomSanitizer, - private testRunService: TestRunService, private route: Router, private store: Store, private state: State, private readonly focusManagerService: FocusManagerService, - private appStore: AppStore + public appStore: AppStore ) { this.appStore.getDevices(); - this.testRunService.getSystemStatus(); + this.appStore.getSystemStatus(); this.matIconRegistry.addSvgIcon( 'devices', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_LOGO_URL) @@ -117,15 +95,6 @@ export class AppComponent implements OnInit { ); } - ngOnInit(): void { - this.systemStatus$ = this.testRunService.systemStatus$.pipe( - tap(() => (this.isStatusLoaded = true)), - shareReplay({ refCount: true, bufferSize: 1 }) - ); - - this.isTestrunStarted$ = this.testRunService.isTestrunStarted$; - } - navigateToDeviceRepository(): void { this.route.navigate([Routes.Devices]); this.store.dispatch(setIsOpenAddDevice({ isOpenAddDevice: true })); @@ -133,7 +102,7 @@ export class AppComponent implements OnInit { navigateToRuntime(): void { this.route.navigate([Routes.Testing]); - this.testRunService.setIsOpenStartTestrun(true); + this.appStore.setIsOpenStartTestrun(); } async closeSetting(hasDevices: boolean): Promise { diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 93e7c8113..d53840850 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -18,11 +18,20 @@ import { of, skip, take } from 'rxjs'; import { AppStore, CONSENT_SHOWN_KEY } from './app.store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './store/state'; -import { selectHasDevices } from './store/selectors'; +import { + selectError, + selectHasConnectionSettings, + selectHasDevices, + selectInterfaces, + selectIsTestrunStarted, + selectMenuOpened, + selectSystemStatus, +} from './store/selectors'; import { TestRunService } from './services/test-run.service'; import SpyObj = jasmine.SpyObj; import { device } from './mocks/device.mock'; -import { setDevices } from './store/actions'; +import { setDevices, setTestrunStatus } from './store/actions'; +import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/progress.mock'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -48,7 +57,7 @@ describe('AppStore', () => { let mockService: SpyObj; beforeEach(() => { - mockService = jasmine.createSpyObj(['fetchDevices']); + mockService = jasmine.createSpyObj(['fetchDevices', 'fetchSystemStatus']); TestBed.configureTestingModule({ providers: [ @@ -62,6 +71,13 @@ describe('AppStore', () => { appStore = TestBed.inject(AppStore); store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectHasConnectionSettings, true); + store.overrideSelector(selectMenuOpened, true); + store.overrideSelector(selectInterfaces, {}); + store.overrideSelector(selectError, null); + store.overrideSelector(selectSystemStatus, MOCK_PROGRESS_DATA_IN_PROGRESS); + store.overrideSelector(selectIsTestrunStarted, false); + spyOn(store, 'dispatch').and.callFake(() => {}); }); @@ -82,6 +98,15 @@ describe('AppStore', () => { appStore.updateConsent(true); }); + + it('should update isStatusLoaded', (done: DoneFn) => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.isStatusLoaded).toEqual(true); + done(); + }); + + appStore.updateIsStatusLoaded(true); + }); }); describe('selectors', () => { @@ -90,6 +115,13 @@ describe('AppStore', () => { expect(store).toEqual({ consentShown: false, hasDevices: true, + isTestrunStarted: false, + isStatusLoaded: false, + systemStatus: MOCK_PROGRESS_DATA_IN_PROGRESS, + hasConnectionSettings: true, + isMenuOpen: true, + interfaces: {}, + settingMissedError: null, }); done(); }); @@ -127,5 +159,30 @@ describe('AppStore', () => { expect(store.dispatch).toHaveBeenCalledWith(setDevices({ devices })); }); }); + + describe('getSystemStatus', () => { + const status = MOCK_PROGRESS_DATA_IN_PROGRESS; + + beforeEach(() => { + mockService.fetchSystemStatus.and.returnValue(of(status)); + }); + + it('should dispatch action setTestrunStatus', () => { + appStore.getSystemStatus(); + + expect(store.dispatch).toHaveBeenCalledWith( + setTestrunStatus({ systemStatus: status }) + ); + }); + + it('should update store', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.systemStatus).toEqual(status); + done(); + }); + + appStore.getSystemStatus(); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 86d3560c6..d214d4848 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -17,26 +17,61 @@ import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { tap } from 'rxjs/operators'; -import { selectHasDevices } from './store/selectors'; +import { + selectError, + selectHasConnectionSettings, + selectHasDevices, + selectInterfaces, + selectIsTestrunStarted, + selectMenuOpened, + selectSystemStatus, +} from './store/selectors'; import { Store } from '@ngrx/store'; import { AppState } from './store/state'; import { TestRunService } from './services/test-run.service'; -import { exhaustMap } from 'rxjs'; +import { exhaustMap, Observable } from 'rxjs'; import { Device } from './model/device'; -import { setDevices } from './store/actions'; +import { + setDevices, + setTestrunStatus, + setIsOpenStartTestrun, +} from './store/actions'; +import { TestrunStatus } from './model/testrun-status'; +import { SettingMissedError, SystemInterfaces } from './model/setting'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; export interface AppComponentState { consentShown: boolean; + isStatusLoaded: boolean; + isTestrunStarted: boolean; + systemStatus: TestrunStatus | null; } @Injectable() export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); + private isStatusLoaded$ = this.select(state => state.isStatusLoaded); private hasDevices$ = this.store.select(selectHasDevices); + private hasConnectionSetting$ = this.store.select( + selectHasConnectionSettings + ); + private isMenuOpen$ = this.store.select(selectMenuOpened); + private interfaces$: Observable = + this.store.select(selectInterfaces); + private settingMissedError$: Observable = + this.store.select(selectError); + private systemStatus$ = this.store.select(selectSystemStatus); + private isTestrunStarted$ = this.store.select(selectIsTestrunStarted); viewModel$ = this.select({ consentShown: this.consentShown$, hasDevices: this.hasDevices$, + isTestrunStarted: this.isTestrunStarted$, + isStatusLoaded: this.isStatusLoaded$, + systemStatus: this.systemStatus$, + hasConnectionSettings: this.hasConnectionSetting$, + isMenuOpen: this.isMenuOpen$, + interfaces: this.interfaces$, + settingMissedError: this.settingMissedError$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -44,6 +79,11 @@ export class AppStore extends ComponentStore { consentShown, })); + updateIsStatusLoaded = this.updater((state, isStatusLoaded: boolean) => ({ + ...state, + isStatusLoaded, + })); + setContent = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -65,12 +105,38 @@ export class AppStore extends ComponentStore { ); }); + getSystemStatus = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunService.fetchSystemStatus().pipe( + tap((res: TestrunStatus) => { + this.updateIsStatusLoaded(true); + this.store.dispatch(setTestrunStatus({ systemStatus: res })); + }) + ); + }) + ); + }); + + setIsOpenStartTestrun = this.effect(trigger$ => { + return trigger$.pipe( + tap(() => { + this.store.dispatch( + setIsOpenStartTestrun({ isOpenStartTestrun: true }) + ); + }) + ); + }); + constructor( private store: Store, private testRunService: TestRunService ) { super({ consentShown: sessionStorage.getItem(CONSENT_SHOWN_KEY) !== null, + isStatusLoaded: false, + isTestrunStarted: false, + systemStatus: null, }); } } diff --git a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.html b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.html index 0cbb2d3d3..93563201e 100644 --- a/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.html +++ b/modules/ui/src/app/pages/testrun/components/progress-initiate-form/progress-initiate-form.component.html @@ -76,7 +76,7 @@ type="button"> Change Device - - + + + + +
+
+ +
+
+

+ {{ data.device.manufacturer }} {{ data.device.model }} v{{ + data.device.firmware + }} +

- + + + +
-