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
29 changes: 15 additions & 14 deletions modules/ui/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -231,20 +231,6 @@ <h1 class="main-heading">Testrun</h1>
>Please update your Devices to continue testing.
</ng-container>
</app-callout>
<app-callout
[type]="CalloutType.Info"
*ngIf="
hasConnectionSettings === true &&
hasDevices &&
(!systemStatus || systemStatus === StatusOfTestrun.Idle) &&
isStatusLoaded === true &&
!reports.length &&
!isAllDevicesOutdated
"
action="Testing"
(onAction)="navigateToRuntime()">
Step 3: Once device is created, you are able to start testing.
</app-callout>
<app-callout
role="alert"
aria-live="assertive"
Expand Down Expand Up @@ -296,5 +282,20 @@ <h1 class="main-heading">Testrun</h1>
(onCLoseTip)="onCLoseTip($event)"
(onAction)="navigateToAddDevice()">
</app-help-tip>
<app-help-tip
*ngIf="
hasConnectionSettings === true &&
hasDevices &&
(!systemStatus || systemStatus === StatusOfTestrun.Idle) &&
isStatusLoaded === true &&
!reports.length &&
!isAllDevicesOutdated
"
[class.closed-tip]="isClosedTip"
[data]="HelpTips.step3"
[target]="testingTipTarget"
(onCLoseTip)="onCLoseTip($event)"
(onAction)="navigateToRuntime()">
</app-help-tip>
</div>
</ng-template>
147 changes: 66 additions & 81 deletions modules/ui/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -446,9 +447,7 @@ describe('AppComponent', () => {
);
}));
});
});

describe('Callout component visibility', () => {
describe('with system status as "Idle"', () => {
beforeEach(() => {
component.appStore.updateIsStatusLoaded(true);
Expand All @@ -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);
Expand Down Expand Up @@ -542,76 +568,35 @@ 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'
) as HTMLAnchorElement;
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');
});
});

Expand Down
15 changes: 15 additions & 0 deletions modules/ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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();
Expand Down
26 changes: 26 additions & 0 deletions modules/ui/src/app/components/help-tip/help-tip.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HelpTipComponent>;
let compiled: HTMLElement;

const mockLiveAnnouncer: SpyObj<LiveAnnouncer> = jasmine.createSpyObj([
'announce',
'clear',
]);

const mockFocusManagerService: SpyObj<FocusManagerService> =
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);
Expand All @@ -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');
Expand Down
17 changes: 17 additions & 0 deletions modules/ui/src/app/components/help-tip/help-tip.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ChangeDetectionStrategy,
Component,
HostListener,
inject,
input,
OnInit,
output,
Expand All @@ -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',
Expand All @@ -40,6 +44,8 @@ export class HelpTipComponent implements OnInit {
onAction = output<void>();
onCLoseTip = output<boolean>();
tipPosition = { top: 0, left: 0 };
private readonly liveAnnouncer = inject(LiveAnnouncer);
private readonly focusManagerService = inject(FocusManagerService);

@HostListener('window:resize')
onResize() {
Expand All @@ -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) {
Expand Down
Loading
Loading