diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index bb6141c33..03a97b165 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -149,6 +149,7 @@ describe('AppComponent', () => { { selector: selectMenuOpened, value: false }, { selector: selectHasDevices, value: false }, { selector: selectStatus, value: null }, + { selector: selectSystemStatus, value: null }, { selector: selectIsOpenStartTestrun, value: false }, { selector: selectIsOpenWaitSnackBar, value: false }, ], diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index 3f4f7b1a6..585e05d28 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -23,6 +23,7 @@ import { selectHasConnectionSettings, selectHasDevices, selectInterfaces, + selectIsOpenWaitSnackBar, selectMenuOpened, selectStatus, } from './store/selectors'; @@ -76,7 +77,10 @@ describe('AppStore', () => { providers: [ AppStore, provideMockStore({ - selectors: [{ selector: selectStatus, value: null }], + selectors: [ + { selector: selectStatus, value: null }, + { selector: selectIsOpenWaitSnackBar, value: false }, + ], }), { provide: TestRunService, useValue: mockService }, { provide: NotificationService, useValue: mockNotificationService }, diff --git a/modules/ui/src/app/pages/testrun/progress.component.ts b/modules/ui/src/app/pages/testrun/progress.component.ts index 781715137..540f850ce 100644 --- a/modules/ui/src/app/pages/testrun/progress.component.ts +++ b/modules/ui/src/app/pages/testrun/progress.component.ts @@ -131,7 +131,6 @@ export class ProgressComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.notificationService.dismissSnackBar(); this.destroy$.next(true); this.destroy$.unsubscribe(); } diff --git a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts index 1c80a5259..fba157bdc 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.spec.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.spec.ts @@ -23,7 +23,6 @@ import { selectHasConnectionSettings, selectHasDevices, selectIsOpenStartTestrun, - selectIsOpenWaitSnackBar, selectIsStopTestrun, selectSystemStatus, } from '../../store/selectors'; @@ -46,7 +45,6 @@ import { TEST_DATA_TABLE_RESULT, } from '../../mocks/progress.mock'; import { LoaderService } from '../../services/loader.service'; -import { NotificationService } from '../../services/notification.service'; describe('TestrunStore', () => { let testrunStore: TestrunStore; @@ -56,11 +54,6 @@ describe('TestrunStore', () => { 'loaderServiceMock', ['setLoading', 'getLoading'] ); - const notificationServiceMock: jasmine.SpyObj = - jasmine.createSpyObj('NotificationService', [ - 'dismissWithTimout', - 'openSnackBar', - ]); beforeEach(() => { mockService = jasmine.createSpyObj('mockService', ['stopTestrun']); @@ -70,14 +63,12 @@ describe('TestrunStore', () => { TestrunStore, { provide: TestRunService, useValue: mockService }, { provide: LoaderService, useValue: loaderServiceMock }, - { provide: NotificationService, useValue: notificationServiceMock }, provideMockStore({ selectors: [ { selector: selectHasDevices, value: false }, { selector: selectSystemStatus, value: null }, { selector: selectHasConnectionSettings, value: true }, { selector: selectIsOpenStartTestrun, value: false }, - { selector: selectIsOpenWaitSnackBar, value: false }, { selector: selectIsStopTestrun, value: false }, ], }), diff --git a/modules/ui/src/app/pages/testrun/testrun.store.ts b/modules/ui/src/app/pages/testrun/testrun.store.ts index 1e253738f..72118b8ca 100644 --- a/modules/ui/src/app/pages/testrun/testrun.store.ts +++ b/modules/ui/src/app/pages/testrun/testrun.store.ts @@ -17,14 +17,13 @@ import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { TestRunService } from '../../services/test-run.service'; -import { exhaustMap, Subject, take, timer } from 'rxjs'; +import { exhaustMap } from 'rxjs'; import { tap, withLatestFrom } from 'rxjs/operators'; import { AppState } from '../../store/state'; import { Store } from '@ngrx/store'; import { selectHasDevices, selectIsOpenStartTestrun, - selectIsOpenWaitSnackBar, selectIsStopTestrun, selectSystemStatus, } from '../../store/selectors'; @@ -41,13 +40,10 @@ import { TestsData, TestsResponse, } from '../../model/testrun-status'; -import { takeUntil } from 'rxjs/internal/operators/takeUntil'; import { FocusManagerService } from '../../services/focus-manager.service'; import { LoaderService } from '../../services/loader.service'; -import { NotificationService } from '../../services/notification.service'; const EMPTY_RESULT = new Array(100).fill(null).map(() => ({}) as IResult); -const WAIT_TO_OPEN_SNACKBAR_MS = 60 * 1000; export interface TestrunComponentState { dataSource: IResult[] | undefined; @@ -56,7 +52,6 @@ export interface TestrunComponentState { @Injectable() export class TestrunStore extends ComponentStore { - private destroyWaitDeviceInterval$: Subject = new Subject(); private dataSource$ = this.select(state => state.dataSource); private stepsToResolveCount$ = this.select( state => state.stepsToResolveCount @@ -64,7 +59,6 @@ export class TestrunStore extends ComponentStore { private hasDevices$ = this.store.select(selectHasDevices); private systemStatus$ = this.store.select(selectSystemStatus); isStopTestrun$ = this.store.select(selectIsStopTestrun); - isOpenWaitSnackBar$ = this.store.select(selectIsOpenWaitSnackBar); isOpenStartTestrun$ = this.store.select(selectIsOpenStartTestrun); viewModel$ = this.select({ hasDevices: this.hasDevices$, @@ -93,23 +87,8 @@ export class TestrunStore extends ComponentStore { getStatus = this.effect(() => { return this.systemStatus$.pipe( - withLatestFrom(this.isOpenWaitSnackBar$), - tap(([res, isOpenWaitSnackBar]) => { - if ( - res?.status === StatusOfTestrun.WaitingForDevice && - !isOpenWaitSnackBar - ) { - this.showSnackBar(); - } - if ( - res?.status !== StatusOfTestrun.WaitingForDevice && - isOpenWaitSnackBar - ) { - this.notificationService.dismissWithTimout(); - } - }), // perform some additional actions - tap(([res]) => { + tap(res => { if ( res?.status === StatusOfTestrun.WaitingForDevice || res?.status === StatusOfTestrun.Monitoring || @@ -128,7 +107,7 @@ export class TestrunStore extends ComponentStore { } }), // update data source - tap(([res]) => { + tap(res => { const results = (res?.tests as TestsData)?.results || []; if ( res?.status === StatusOfTestrun.Monitoring || @@ -206,22 +185,6 @@ export class TestrunStore extends ComponentStore { this.loaderService.setLoading(true); } - private showSnackBar() { - timer(WAIT_TO_OPEN_SNACKBAR_MS) - .pipe( - take(1), - takeUntil(this.destroyWaitDeviceInterval$), - withLatestFrom(this.systemStatus$), - tap(([, systemStatus]) => { - if (systemStatus?.status === StatusOfTestrun.WaitingForDevice) { - this.notificationService.openSnackBar(); - this.destroyWaitDeviceInterval$.next(true); - } - }) - ) - .subscribe(); - } - private getCancellingStatus(systemStatus: TestrunStatus): TestrunStatus { const status = Object.assign({}, systemStatus); status.status = StatusOfTestrun.Cancelling; @@ -242,7 +205,6 @@ export class TestrunStore extends ComponentStore { constructor( private testRunService: TestRunService, - private notificationService: NotificationService, private store: Store, private readonly focusManagerService: FocusManagerService, private readonly loaderService: LoaderService diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts index 82f5ce1cf..5aee2fd90 100644 --- a/modules/ui/src/app/store/effects.spec.ts +++ b/modules/ui/src/app/store/effects.spec.ts @@ -28,16 +28,30 @@ import { Action } from '@ngrx/store'; import * as actions from './actions'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from './state'; -import { selectMenuOpened, selectSystemStatus } from './selectors'; +import { + selectIsOpenWaitSnackBar, + selectMenuOpened, + selectSystemStatus, +} from './selectors'; import { device } from '../mocks/device.mock'; -import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/progress.mock'; +import { + MOCK_PROGRESS_DATA_IN_PROGRESS, + MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE, +} from '../mocks/progress.mock'; import { fetchSystemStatus, setStatus, setTestrunStatus } from './actions'; +import { NotificationService } from '../services/notification.service'; describe('Effects', () => { let actions$ = new Observable(); let effects: AppEffects; let testRunServiceMock: SpyObj; let store: MockStore; let dispatchSpy: jasmine.Spy; + const notificationServiceMock: jasmine.SpyObj = + jasmine.createSpyObj('notificationServiceMock', [ + 'dismissWithTimout', + 'openSnackBar', + ]); + beforeEach(() => { testRunServiceMock = jasmine.createSpyObj('testRunServiceMock', [ 'getSystemInterfaces', @@ -56,6 +70,7 @@ describe('Effects', () => { providers: [ AppEffects, { provide: TestRunService, useValue: testRunServiceMock }, + { provide: NotificationService, useValue: notificationServiceMock }, provideMockActions(() => actions$), provideMockStore({}), ], @@ -239,6 +254,7 @@ describe('Effects', () => { describe('onFetchSystemStatusSuccess$', () => { beforeEach(() => { + store.overrideSelector(selectIsOpenWaitSnackBar, false); store.overrideSelector(selectSystemStatus, null); }); @@ -275,6 +291,53 @@ describe('Effects', () => { done(); }); }); + + it('should dispatch status and systemStatus', done => { + store.overrideSelector(selectIsOpenWaitSnackBar, true); + store.refreshState(); + + effects.onFetchSystemStatusSuccess$.subscribe(() => { + expect(dispatchSpy).toHaveBeenCalledWith( + setStatus({ status: MOCK_PROGRESS_DATA_IN_PROGRESS.status }) + ); + + expect(notificationServiceMock.dismissWithTimout).toHaveBeenCalled(); + done(); + }); + }); + }); + + describe('with status "waiting for device"', () => { + beforeEach(() => { + store.overrideSelector( + selectSystemStatus, + MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE + ); + testRunServiceMock.testrunInProgress.and.returnValue(true); + actions$ = of( + actions.fetchSystemStatusSuccess({ + systemStatus: MOCK_PROGRESS_DATA_WAITING_FOR_DEVICE, + }) + ); + }); + + it('should call fetchSystemStatus for status "waiting for device"', fakeAsync(() => { + effects.onFetchSystemStatusSuccess$.subscribe(() => { + tick(5000); + + expect(dispatchSpy).toHaveBeenCalledWith(fetchSystemStatus()); + discardPeriodicTasks(); + }); + })); + + it('should open snackbar when waiting for device is too long', fakeAsync(() => { + effects.onFetchSystemStatusSuccess$.subscribe(() => { + tick(60000); + + expect(notificationServiceMock.openSnackBar).toHaveBeenCalled(); + discardPeriodicTasks(); + }); + })); }); }); }); diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts index 372b1f683..7def46031 100644 --- a/modules/ui/src/app/store/effects.ts +++ b/modules/ui/src/app/store/effects.ts @@ -22,9 +22,13 @@ import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import * as AppActions from './actions'; import { AppState } from './state'; import { TestRunService } from '../services/test-run.service'; -import { filter, combineLatest, interval, Subject } from 'rxjs'; -import { selectMenuOpened, selectSystemStatus } from './selectors'; -import { IResult, TestsData } from '../model/testrun-status'; +import { filter, combineLatest, interval, Subject, timer, take } from 'rxjs'; +import { + selectIsOpenWaitSnackBar, + selectMenuOpened, + selectSystemStatus, +} from './selectors'; +import { IResult, StatusOfTestrun, TestsData } from '../model/testrun-status'; import { fetchSystemStatus, setStatus, @@ -32,11 +36,15 @@ import { stopInterval, } from './actions'; import { takeUntil } from 'rxjs/internal/operators/takeUntil'; +import { NotificationService } from '../services/notification.service'; + +const WAIT_TO_OPEN_SNACKBAR_MS = 60 * 1000; @Injectable() export class AppEffects { private startInterval = false; private destroyInterval$: Subject = new Subject(); + private destroyWaitDeviceInterval$: Subject = new Subject(); checkInterfacesInConfig$ = createEffect(() => combineLatest([ @@ -161,8 +169,25 @@ export class AppEffects { this.store.dispatch(stopInterval()); } }), - withLatestFrom(this.store.select(selectSystemStatus)), - tap(([{ systemStatus }, status]) => { + withLatestFrom( + this.store.select(selectIsOpenWaitSnackBar), + this.store.select(selectSystemStatus) + ), + tap(([{ systemStatus }, isOpenWaitSnackBar]) => { + if ( + systemStatus?.status === StatusOfTestrun.WaitingForDevice && + !isOpenWaitSnackBar + ) { + this.showSnackBar(); + } + if ( + systemStatus?.status !== StatusOfTestrun.WaitingForDevice && + isOpenWaitSnackBar + ) { + this.notificationService.dismissWithTimout(); + } + }), + tap(([{ systemStatus }, , status]) => { // for app - requires only status if (systemStatus.status !== status?.status) { this.ngZone.run(() => { @@ -190,6 +215,22 @@ export class AppEffects { { dispatch: false } ); + private showSnackBar() { + timer(WAIT_TO_OPEN_SNACKBAR_MS) + .pipe( + take(1), + takeUntil(this.destroyWaitDeviceInterval$), + withLatestFrom(this.store.select(selectSystemStatus)), + tap(([, systemStatus]) => { + if (systemStatus?.status === StatusOfTestrun.WaitingForDevice) { + this.notificationService.openSnackBar(); + this.destroyWaitDeviceInterval$.next(true); + } + }) + ) + .subscribe(); + } + private pullingSystemStatusData(): void { this.ngZone.runOutsideAngular(() => { this.startInterval = true; @@ -206,6 +247,7 @@ export class AppEffects { private actions$: Actions, private testrunService: TestRunService, private store: Store, - private ngZone: NgZone + private ngZone: NgZone, + private notificationService: NotificationService ) {} }