diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index b52462b92..b6262e58d 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -231,20 +231,6 @@

Testrun

>Please update your Devices to continue testing. - - Step 3: Once device is created, you are able to start testing. - Testrun (onCLoseTip)="onCLoseTip($event)" (onAction)="navigateToAddDevice()"> + + diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index 41f1780b4..c9ecd93ba 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -77,6 +77,7 @@ import { ShutdownAppComponent } from './components/shutdown-app/shutdown-app.com import { TestingCompleteComponent } from './components/testing-complete/testing-complete.component'; import { VersionComponent } from './components/version/version.component'; import { MOCK_MODULES } from './mocks/device.mock'; +import { HelpTips } from './model/tip-config'; const windowMock = { location: { @@ -446,9 +447,7 @@ describe('AppComponent', () => { ); })); }); - }); - describe('Callout component visibility', () => { describe('with system status as "Idle"', () => { beforeEach(() => { component.appStore.updateIsStatusLoaded(true); @@ -459,57 +458,84 @@ describe('AppComponent', () => { fixture.detectChanges(); }); - it('should have callout component with "Step 3" text', () => { - const callout = compiled.querySelector('app-callout'); - const calloutContent = callout?.innerHTML.trim(); + it('should have help tip with "Step 3" title', () => { + const helpTipTitle = compiled.querySelector('app-help-tip .title'); + const helpTipTitleContent = helpTipTitle?.innerHTML.trim(); - expect(callout).toBeTruthy(); - expect(calloutContent).toContain('Step 3'); + expect(helpTipTitleContent).toContain('Step 3'); }); - it('should NOT have callout component with "Step 3" if has reports', () => { + it('should NOT have help tip with "Step 3" if has reports', () => { store.overrideSelector(selectReports, [...HISTORY]); store.refreshState(); fixture.detectChanges(); - const callout = compiled.querySelector('app-callout'); + const helpTip = compiled.querySelector('app-help-tip'); - expect(callout).toBeFalsy(); + expect(helpTip).toBeFalsy(); }); }); - describe('with systemStatus data IN Progress and without riskProfiles', () => { + describe('with devices set but without systemStatus data', () => { beforeEach(() => { - store.overrideSelector(selectHasConnectionSettings, true); store.overrideSelector(selectHasDevices, true); - store.overrideSelector(selectHasRiskProfiles, false); - store.overrideSelector( - selectStatus, - MOCK_PROGRESS_DATA_IN_PROGRESS.status - ); + component.appStore.updateIsStatusLoaded(true); + store.overrideSelector(selectHasConnectionSettings, true); + store.overrideSelector(selectSystemStatus, null); + fixture.detectChanges(); }); - it('should have callout component with "The device is now being tested" text', () => { - const callout = compiled.querySelector('app-callout'); - const calloutContent = callout?.innerHTML.trim(); + it('should have help tip with "Step 3" text', () => { + const helpTipTitle = compiled.querySelector('app-help-tip .title'); + const helpTipTitleContent = helpTipTitle?.innerHTML.trim(); - expect(callout).toBeTruthy(); - expect(calloutContent).toContain('The device is now being tested'); + expect(helpTipTitleContent).toContain('Step 3'); }); - it('should have callout component with "Risk Assessment" link', () => { - const callout = compiled.querySelector('app-callout'); - const calloutLinkEl = compiled.querySelector( - '.callout-action-link' + it('should have help tip with "Start Testrun" link', () => { + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' ) as HTMLAnchorElement; - const calloutLinkContent = calloutLinkEl.innerHTML.trim(); + const helpTipLinkContent = helpTipLinkEl.innerHTML.trim(); - expect(callout).toBeTruthy(); - expect(calloutLinkContent).toContain('Create risk Assessment'); + expect(helpTipLinkEl).toBeTruthy(); + expect(helpTipLinkContent).toContain(HelpTips.step3.action); + }); + + keyboardCases.forEach(testCase => { + it(`should navigate to the testing on keydown ${testCase.name} "Start Testrun" link`, fakeAsync(() => { + const helpTipLinkEl = compiled.querySelector( + '.tip-action-link' + ) as HTMLAnchorElement; + + helpTipLinkEl.dispatchEvent(testCase.event); + flush(); + + expect(router.url).toBe(Routes.Testing); + })); + }); + }); + + describe('with devices set and systemStatus data', () => { + beforeEach(() => { + store.overrideSelector(selectHasDevices, true); + store.overrideSelector( + selectSystemStatus, + MOCK_PROGRESS_DATA_IN_PROGRESS + ); + fixture.detectChanges(); + }); + + it('should not have help tip', () => { + const helpTip = compiled.querySelector('app-help-tip'); + + expect(helpTip).toBeNull(); }); }); + }); + describe('Callout component visibility', () => { describe('with systemStatus data IN Progress and without riskProfiles', () => { beforeEach(() => { store.overrideSelector(selectHasConnectionSettings, true); @@ -542,25 +568,27 @@ describe('AppComponent', () => { }); }); - describe('with devices setted but without systemStatus data', () => { + describe('with systemStatus data IN Progress and without riskProfiles', () => { beforeEach(() => { - store.overrideSelector(selectHasDevices, true); - component.appStore.updateIsStatusLoaded(true); store.overrideSelector(selectHasConnectionSettings, true); - store.overrideSelector(selectSystemStatus, null); - + store.overrideSelector(selectHasDevices, true); + store.overrideSelector(selectHasRiskProfiles, false); + store.overrideSelector( + selectStatus, + MOCK_PROGRESS_DATA_IN_PROGRESS.status + ); fixture.detectChanges(); }); - it('should have callout component with "Step 3" text', () => { + it('should have callout component with "The device is now being tested" text', () => { const callout = compiled.querySelector('app-callout'); const calloutContent = callout?.innerHTML.trim(); expect(callout).toBeTruthy(); - expect(calloutContent).toContain('Step 3'); + expect(calloutContent).toContain('The device is now being tested'); }); - it('should have callout component with "testing" link', () => { + it('should have callout component with "Risk Assessment" link', () => { const callout = compiled.querySelector('app-callout'); const calloutLinkEl = compiled.querySelector( '.callout-action-link' @@ -568,50 +596,7 @@ describe('AppComponent', () => { const calloutLinkContent = calloutLinkEl.innerHTML.trim(); expect(callout).toBeTruthy(); - expect(calloutLinkContent).toContain('Testing'); - }); - - keyboardCases.forEach(testCase => { - it(`should navigate to the runtime on keydown ${testCase.name} "Run the Test" link`, fakeAsync(() => { - const calloutLinkEl = compiled.querySelector( - '.callout-action-link' - ) as HTMLAnchorElement; - - calloutLinkEl.dispatchEvent(testCase.event); - flush(); - - expect(router.url).toBe(Routes.Testing); - })); - }); - }); - - describe('with devices setted, without systemStatus data, but run the tests', () => { - beforeEach(() => { - store.overrideSelector(selectHasDevices, true); - fixture.detectChanges(); - }); - - it('should not have callout component', () => { - const callout = compiled.querySelector('app-callout'); - - expect(callout).toBeNull(); - }); - }); - - describe('with devices setted and systemStatus data', () => { - beforeEach(() => { - store.overrideSelector(selectHasDevices, true); - store.overrideSelector( - selectSystemStatus, - MOCK_PROGRESS_DATA_IN_PROGRESS - ); - fixture.detectChanges(); - }); - - it('should not have callout component', () => { - const callout = compiled.querySelector('app-callout'); - - expect(callout).toBeNull(); + expect(calloutLinkContent).toContain('Create risk Assessment'); }); }); diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index a6ed281c1..27a22cd5d 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -134,6 +134,7 @@ export class AppComponent implements AfterViewInit { @ViewChild('settingButton', { static: false }) settingButton!: MatButton; settingTipTarget!: HTMLElement; deviceTipTarget!: HTMLElement; + testingTipTarget!: HTMLElement; isClosedTip = false; @HostListener('mousedown') @@ -234,6 +235,9 @@ export class AppComponent implements AfterViewInit { this.deviceTipTarget = document.querySelector( '.app-sidebar-button.app-sidebar-button-devices' ) as HTMLElement; + this.testingTipTarget = document.querySelector( + '.app-sidebar-button.app-sidebar-button-testrun' + ) as HTMLElement; this.viewModel$ .pipe( @@ -282,6 +286,17 @@ export class AppComponent implements AfterViewInit { onCLoseTip(isClosed: boolean): void { this.isClosedTip = isClosed; + const helpTipButton = window.document.querySelector( + '.app-toolbar-button-help-tips' + ) as HTMLButtonElement; + const helpTipEl = window.document.querySelector('app-help-tip'); + timer(100).subscribe(() => { + if (isClosed) { + helpTipButton.focus(); + } else { + this.focusManagerService.focusFirstElementInContainer(helpTipEl); + } + }); } consentShown() { this.appStore.setContent(); diff --git a/modules/ui/src/app/components/help-tip/help-tip.component.spec.ts b/modules/ui/src/app/components/help-tip/help-tip.component.spec.ts index b3d846ea1..413a86849 100644 --- a/modules/ui/src/app/components/help-tip/help-tip.component.spec.ts +++ b/modules/ui/src/app/components/help-tip/help-tip.component.spec.ts @@ -7,15 +7,32 @@ import { import { HelpTipComponent } from './help-tip.component'; import { HelpTips } from '../../model/tip-config'; +import SpyObj = jasmine.SpyObj; +import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { FocusManagerService } from '../../services/focus-manager.service'; describe('HelpTipComponent', () => { let component: HelpTipComponent; let fixture: ComponentFixture; let compiled: HTMLElement; + const mockLiveAnnouncer: SpyObj = jasmine.createSpyObj([ + 'announce', + 'clear', + ]); + + const mockFocusManagerService: SpyObj = + jasmine.createSpyObj('mockFocusManagerService', [ + 'focusFirstElementInContainer', + ]); + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [HelpTipComponent], + providers: [ + { provide: LiveAnnouncer, useValue: mockLiveAnnouncer }, + { provide: FocusManagerService, useValue: mockFocusManagerService }, + ], }).compileComponents(); fixture = TestBed.createComponent(HelpTipComponent); @@ -29,6 +46,15 @@ describe('HelpTipComponent', () => { expect(component).toBeTruthy(); }); + it('should set focus to first focusable elem', fakeAsync(() => { + component.ngOnInit(); + tick(200); + + expect( + mockFocusManagerService.focusFirstElementInContainer + ).toHaveBeenCalled(); + })); + it('should have provided data', () => { const tipTitle = compiled.querySelector('.tip-container .title'); const tipContent = compiled.querySelector('.tip-container .tip-content'); diff --git a/modules/ui/src/app/components/help-tip/help-tip.component.ts b/modules/ui/src/app/components/help-tip/help-tip.component.ts index 6bfc5a627..444a82ee2 100644 --- a/modules/ui/src/app/components/help-tip/help-tip.component.ts +++ b/modules/ui/src/app/components/help-tip/help-tip.component.ts @@ -17,6 +17,7 @@ import { ChangeDetectionStrategy, Component, HostListener, + inject, input, OnInit, output, @@ -25,6 +26,9 @@ import { CommonModule } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { TipConfig } from '../../model/tip-config'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { FocusManagerService } from '../../services/focus-manager.service'; +import { timer } from 'rxjs/internal/observable/timer'; @Component({ selector: 'app-help-tip', @@ -40,6 +44,8 @@ export class HelpTipComponent implements OnInit { onAction = output(); onCLoseTip = output(); tipPosition = { top: 0, left: 0 }; + private readonly liveAnnouncer = inject(LiveAnnouncer); + private readonly focusManagerService = inject(FocusManagerService); @HostListener('window:resize') onResize() { @@ -48,6 +54,17 @@ export class HelpTipComponent implements OnInit { ngOnInit() { this.updateTipPosition(this.target()); + this.liveAnnouncer.announce( + `${this.data()?.title} ${this.data()?.content}` + ); + this.setFocus(); + } + + private setFocus(): void { + const helpTipEl = window.document.querySelector('.tip'); + timer(200).subscribe(() => { + this.focusManagerService.focusFirstElementInContainer(helpTipEl); + }); } updateTipPosition(target: HTMLElement | undefined | null) { diff --git a/modules/ui/src/app/model/tip-config.ts b/modules/ui/src/app/model/tip-config.ts index eab024c0d..e465f027d 100644 --- a/modules/ui/src/app/model/tip-config.ts +++ b/modules/ui/src/app/model/tip-config.ts @@ -38,4 +38,11 @@ export const HelpTips = { position: 'right', arrowPosition: 'left', } as TipConfig, + step3: { + title: 'Step 3:', + content: 'You can now start your first test attempt your new device.', + action: 'Start Testrun', + position: 'right', + arrowPosition: 'left', + } as TipConfig, };