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
79 changes: 59 additions & 20 deletions modules/ui/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ <h1 class="main-heading">Testrun</h1>
<span class="spacer"></span>
<div class="app-bar-buttons">
<button
class="app-toolbar-button app-toolbar-button-help-tips"
mat-icon-button
aria-label="Help tips"
matTooltip="Help tips"
(click)="onCLoseTip(false)">
<mat-icon>help</mat-icon>
</button>
<button
#settingButton
class="app-toolbar-button app-toolbar-button-general-settings"
mat-icon-button
aria-label="System settings"
Expand Down Expand Up @@ -131,6 +140,21 @@ <h1 class="main-heading">Testrun</h1>
*ngIf="vm.isTestingComplete"
[data]="vm.testrunStatus"
[profiles]="vm.riskProfiles"></app-testing-complete>
<ng-container
*ngTemplateOutlet="
tips;
context: {
systemStatus: vm.systemStatus,
calloutState: vm.calloutState,
hasConnectionSettings: vm.hasConnectionSettings,
hasRiskProfiles: vm.hasRiskProfiles,
isAllDevicesOutdated: vm.isAllDevicesOutdated,
isStatusLoaded: vm.isStatusLoaded,
reports: vm.reports,
hasDevices: vm.hasDevices,
}
">
</ng-container>
</ng-container>

<ng-template
Expand Down Expand Up @@ -202,26 +226,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 === false"
action="System settings"
(onAction)="navigateToSettings()">
Step 1: To perform a device test, please, select ports in System settings
panel.
</app-callout>
<app-callout
[type]="CalloutType.Info"
*ngIf="
hasConnectionSettings === true &&
(hasDevices === false ||
(!!calloutState.get('outdated_devices_callout') &&
isAllDevicesOutdated))
"
action="Devices"
(onAction)="navigateToAddDevice()">
Step 2: To perform a device test please Create a Device first.
</app-callout>
<app-callout
[type]="CalloutType.Info"
*ngIf="
Expand Down Expand Up @@ -254,3 +258,38 @@ <h1 class="main-heading">Testrun</h1>
</app-callout>
</div>
</ng-template>

<ng-template
#tips
let-systemStatus="systemStatus"
let-calloutState="calloutState"
let-hasConnectionSettings="hasConnectionSettings"
let-isAllDevicesOutdated="isAllDevicesOutdated"
let-isStatusLoaded="isStatusLoaded"
let-reports="reports"
let-hasRiskProfiles="hasRiskProfiles"
let-hasDevices="hasDevices">
<div class="tips-container">
<app-help-tip
*ngIf="hasConnectionSettings === false"
[class.closed-tip]="isClosedTip"
[data]="HelpTips.step1"
[target]="settingTipTarget"
(onCLoseTip)="onCLoseTip($event)"
(onAction)="navigateToSettings()">
</app-help-tip>
<app-help-tip
*ngIf="
hasConnectionSettings === true &&
(hasDevices === false ||
(!!calloutState.get('outdated_devices_callout') &&
isAllDevicesOutdated))
"
[class.closed-tip]="isClosedTip"
[data]="HelpTips.step2"
[target]="deviceTipTarget"
(onCLoseTip)="onCLoseTip($event)"
(onAction)="navigateToAddDevice()">
</app-help-tip>
</div>
</ng-template>
7 changes: 6 additions & 1 deletion modules/ui/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ $nav-width: 96px;
min-width: 24px;
}

.app-sidebar-button-active {
.app-sidebar-button-active,
:host:has(app-help-tip) .app-toolbar-button-help-tips {
.material-symbols-outlined {
font-variation-settings:
'FILL' 1,
Expand Down Expand Up @@ -230,3 +231,7 @@ app-version {
padding-top: 82px;
}
}

.closed-tip {
display: none;
}
145 changes: 75 additions & 70 deletions modules/ui/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import { SpinnerComponent } from './components/spinner/spinner.component';
import { ShutdownAppComponent } from './components/shutdown-app/shutdown-app.component';
import { TestingCompleteComponent } from './components/testing-complete/testing-complete.component';
import { VersionComponent } from './components/version/version.component';
import { MOCK_MODULES } from './mocks/device.mock';

const windowMock = {
location: {
Expand Down Expand Up @@ -201,6 +202,8 @@ describe('AppComponent', () => {
},
});

mockService.fetchDevices.and.returnValue(of([]));
mockService.getTestModules.and.returnValue(of([...MOCK_MODULES]));
mockMqttService.getNetworkAdapters.and.returnValue(of(MOCK_ADAPTERS));
store = TestBed.inject(MockStore);
fixture = TestBed.createComponent(AppComponent);
Expand Down Expand Up @@ -360,32 +363,92 @@ describe('AppComponent', () => {
});
});

describe('Callout component visibility', () => {
describe('Help tip component visibility', () => {
describe('with no connection settings', () => {
beforeEach(() => {
store.overrideSelector(selectHasConnectionSettings, false);
fixture.detectChanges();
});

it('should have callout component with "Step 1" text', () => {
const callout = compiled.querySelector('app-callout');
const calloutContent = callout?.innerHTML.trim();
it('should have help tip component with "Step 1" text', () => {
const helpTip = compiled.querySelector('app-help-tip');
const helpTipTitle = compiled.querySelector('app-help-tip .title');
const helpTipContent = helpTipTitle?.innerHTML.trim();

expect(callout).toBeTruthy();
expect(calloutContent).toContain('Step 1');
expect(helpTip).toBeTruthy();
expect(helpTipContent).toContain('Step 1');
});

it('should have callout content with "System settings" link ', () => {
const calloutLinkEl = compiled.querySelector(
'.callout-action-link'
it('should have help tip content with "Go to Settings" link ', () => {
const helpTipLinkEl = compiled.querySelector(
'.tip-action-link'
) as HTMLAnchorElement;
const calloutLinkContent = calloutLinkEl.innerHTML.trim();
const helpTipLinkContent = helpTipLinkEl.innerHTML.trim();

expect(helpTipLinkEl).toBeTruthy();
expect(helpTipLinkContent).toContain('Go to Settings');
});
});

describe('with no devices set', () => {
beforeEach(() => {
store.overrideSelector(selectHasDevices, false);
fixture.detectChanges();
});

it('should have helpTip component', () => {
const helpTip = compiled.querySelector('app-help-tip');

expect(helpTip).toBeTruthy();
});

it('should have help tip component with "Step 2" text', () => {
const helpTipTitle = compiled.querySelector('app-help-tip .title');
const helpTipTitleContent = helpTipTitle?.innerHTML.trim();

expect(helpTipTitleContent).toContain('Step 2');
});

it('should have help tip content with "Create Device" link ', () => {
const helpTipLinkEl = compiled.querySelector(
'.tip-action-link'
) as HTMLAnchorElement;
const helpTipLinkContent = helpTipLinkEl.innerHTML.trim();

expect(helpTipLinkEl).toBeTruthy();
expect(helpTipLinkContent).toContain('Device');
});

keyboardCases.forEach(testCase => {
it(`should navigate to the device-repository on keydown ${testCase.name} "Create Device" link`, fakeAsync(() => {
const helpTipLinkEl = compiled.querySelector(
'.tip-action-link'
) as HTMLAnchorElement;

helpTipLinkEl.dispatchEvent(testCase.event);
flush();

expect(calloutLinkEl).toBeTruthy();
expect(calloutLinkContent).toContain('System settings');
expect(router.url).toBe(Routes.Devices);
}));
});

it('should navigate to the device-repository on click "Create a Device" link', fakeAsync(() => {
const helpTipLinkEl = compiled.querySelector(
'.tip-action-link'
) as HTMLAnchorElement;

helpTipLinkEl.click();
flush();

expect(router.url).toBe(Routes.Devices);
expect(store.dispatch).toHaveBeenCalledWith(
setIsOpenAddDevice({ isOpenAddDevice: true })
);
}));
});
});

describe('Callout component visibility', () => {
describe('with system status as "Idle"', () => {
beforeEach(() => {
component.appStore.updateIsStatusLoaded(true);
Expand Down Expand Up @@ -479,64 +542,6 @@ describe('AppComponent', () => {
});
});

describe('with no devices setted', () => {
beforeEach(() => {
store.overrideSelector(selectHasDevices, false);
fixture.detectChanges();
});

it('should have callout component', () => {
const callout = compiled.querySelector('app-callout');

expect(callout).toBeTruthy();
});

it('should have callout component with "Step 2" text', () => {
const callout = compiled.querySelector('app-callout');
const calloutContent = callout?.innerHTML.trim();

expect(callout).toBeTruthy();
expect(calloutContent).toContain('Step 2');
});

it('should have callout content with "Create a Device" link ', () => {
const calloutLinkEl = compiled.querySelector(
'.callout-action-link'
) as HTMLAnchorElement;
const calloutLinkContent = calloutLinkEl.innerHTML.trim();

expect(calloutLinkEl).toBeTruthy();
expect(calloutLinkContent).toContain('Devices');
});

keyboardCases.forEach(testCase => {
it(`should navigate to the device-repository on keydown ${testCase.name} "Create a Device" link`, fakeAsync(() => {
const calloutLinkEl = compiled.querySelector(
'.callout-action-link'
) as HTMLAnchorElement;

calloutLinkEl.dispatchEvent(testCase.event);
flush();

expect(router.url).toBe(Routes.Devices);
}));
});

it('should navigate to the device-repository on click "Create a Device" link', fakeAsync(() => {
const calloutLinkEl = compiled.querySelector(
'.callout-action-link'
) as HTMLAnchorElement;

calloutLinkEl.click();
flush();

expect(router.url).toBe(Routes.Devices);
expect(store.dispatch).toHaveBeenCalledWith(
setIsOpenAddDevice({ isOpenAddDevice: true })
);
}));
});

describe('with devices setted but without systemStatus data', () => {
beforeEach(() => {
store.overrideSelector(selectHasDevices, true);
Expand Down
24 changes: 23 additions & 1 deletion modules/ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
ElementRef,
viewChild,
inject,
ViewChild,
ChangeDetectorRef,
} from '@angular/core';
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
Expand All @@ -46,7 +48,7 @@ import { BypassComponent } from './components/bypass/bypass.component';
import { ShutdownAppComponent } from './components/shutdown-app/shutdown-app.component';
import { SpinnerComponent } from './components/spinner/spinner.component';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatButtonModule } from '@angular/material/button';
import { MatButton, MatButtonModule } from '@angular/material/button';
import { VersionComponent } from './components/version/version.component';
import { MatSelectModule } from '@angular/material/select';
import { WifiComponent } from './components/wifi/wifi.component';
Expand All @@ -57,6 +59,8 @@ import { CommonModule } from '@angular/common';
import { SideButtonMenuComponent } from './components/side-button-menu/side-button-menu.component';
import { Observable } from 'rxjs/internal/Observable';
import { of } from 'rxjs/internal/observable/of';
import { HelpTipComponent } from './components/help-tip/help-tip.component';
import { HelpTips } from './model/tip-config';

export interface AddMenuItem {
icon?: string;
Expand Down Expand Up @@ -96,6 +100,7 @@ const QUALIFICATION_URL = '/assets/icons/qualification.svg';
BypassComponent,
VersionComponent,
CalloutComponent,
HelpTipComponent,
ShutdownAppComponent,
WifiComponent,
TestingCompleteComponent,
Expand All @@ -112,15 +117,21 @@ export class AppComponent implements AfterViewInit {
private store = inject<Store<AppState>>(Store);
private readonly focusManagerService = inject(FocusManagerService);
private testRunService = inject(TestRunService);
private cdr = inject(ChangeDetectorRef);
appStore = inject(AppStore);

public readonly CalloutType = CalloutType;
public readonly StatusOfTestrun = StatusOfTestrun;
public readonly HelpTips = HelpTips;
public readonly Routes = Routes;
viewModel$ = this.appStore.viewModel$;

readonly riskAssessmentLink = viewChild<ElementRef>('riskAssessmentLink');
private skipCount = 0;
@ViewChild('settingButton', { static: false }) settingButton!: MatButton;
settingTipTarget!: HTMLElement;
deviceTipTarget!: HTMLElement;
isClosedTip = false;

navigateToRuntime = () => {
this.route.navigate([Routes.Testing]);
Expand Down Expand Up @@ -206,6 +217,11 @@ export class AppComponent implements AfterViewInit {
}

ngAfterViewInit() {
this.settingTipTarget = this.settingButton._elementRef.nativeElement;
this.deviceTipTarget = document.querySelector(
'.app-sidebar-button.app-sidebar-button-devices'
) as HTMLElement;

this.viewModel$
.pipe(
filter(({ isStatusLoaded }) => isStatusLoaded === true),
Expand All @@ -217,6 +233,8 @@ export class AppComponent implements AfterViewInit {
this.skipCount = 1;
}
});

this.cdr.detectChanges();
}

get isRiskAssessmentRoute(): boolean {
Expand All @@ -242,6 +260,10 @@ export class AppComponent implements AfterViewInit {
this.appStore.setFocusOnPage();
});
}

onCLoseTip(isClosed: boolean): void {
this.isClosedTip = isClosed;
}
consentShown() {
this.appStore.setContent();
}
Expand Down
Loading
Loading