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
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ <h2 class="profiles-drawer-header-title">Saved profiles</h2>
</div>
<div class="profiles-drawer-content">
<app-profile-item
*ngFor="let profile of vm.profiles; let i = index"
*ngFor="
let profile of vm.profiles;
let i = index;
trackBy: trackByIndex
"
[profile]="profile"
class="profile-item-{{ i }}"
[ngClass]="{ selected: profile.name === vm.selectedProfile?.name }"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,13 @@ import { Component, Input } from '@angular/core';
import { Profile, ProfileFormat } from '../../model/profile';
import { MatDialogRef } from '@angular/material/dialog';
import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component';
import { FocusManagerService } from '../../services/focus-manager.service';
import { RiskAssessmentStore } from './risk-assessment.store';
import { LiveAnnouncer } from '@angular/cdk/a11y';

describe('RiskAssessmentComponent', () => {
let component: RiskAssessmentComponent;
let fixture: ComponentFixture<RiskAssessmentComponent>;
let mockService: SpyObj<TestRunService>;
let mockFocusManagerService: SpyObj<FocusManagerService>;
let mockRiskAssessmentStore: SpyObj<RiskAssessmentStore>;

const mockLiveAnnouncer: SpyObj<LiveAnnouncer> = jasmine.createSpyObj([
Expand All @@ -52,16 +50,15 @@ describe('RiskAssessmentComponent', () => {
mockService = jasmine.createSpyObj(['fetchProfiles', 'deleteProfile']);
mockService.deleteProfile.and.returnValue(of(true));

mockFocusManagerService = jasmine.createSpyObj('mockFocusManagerService', [
'focusFirstElementInContainer',
]);

mockRiskAssessmentStore = jasmine.createSpyObj('RiskAssessmentStore', [
'deleteProfile',
'setFocus',
'getProfilesFormat',
'saveProfile',
'updateSelectedProfile',
'setFocusOnCreateButton',
'setFocusOnSelectedProfile',
'setFocusOnProfileForm',
]);

await TestBed.configureTestingModule({
Expand All @@ -73,7 +70,6 @@ describe('RiskAssessmentComponent', () => {
imports: [MatToolbarModule, MatSidenavModule, BrowserAnimationsModule],
providers: [
{ provide: TestRunService, useValue: mockService },
{ provide: FocusManagerService, useValue: mockFocusManagerService },
{ provide: RiskAssessmentStore, useValue: mockRiskAssessmentStore },
{ provide: LiveAnnouncer, useValue: mockLiveAnnouncer },
],
Expand Down Expand Up @@ -213,10 +209,10 @@ describe('RiskAssessmentComponent', () => {
);
});

it('should focus first element in container', async () => {
it('should focus first element in profile form', async () => {
await component.openForm();
expect(
mockFocusManagerService.focusFirstElementInContainer
mockRiskAssessmentStore.setFocusOnProfileForm
).toHaveBeenCalled();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { RiskAssessmentStore } from './risk-assessment.store';
import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component';
import { Subject, takeUntil } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';
import { FocusManagerService } from '../../services/focus-manager.service';
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { Profile } from '../../model/profile';
import { Observable } from 'rxjs/internal/Observable';
Expand All @@ -42,7 +41,6 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy {
constructor(
private store: RiskAssessmentStore,
public dialog: MatDialog,
private focusManagerService: FocusManagerService,
private liveAnnouncer: LiveAnnouncer
) {}

Expand All @@ -59,8 +57,7 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy {
this.isOpenProfileForm = true;
this.store.updateSelectedProfile(profile);
await this.liveAnnouncer.announce('Risk assessment questionnaire');
const profileForm = window.document.querySelector('app-profile-form');
this.focusManagerService.focusFirstElementInContainer(profileForm);
this.store.setFocusOnProfileForm();
}

deleteProfile(
Expand Down Expand Up @@ -95,17 +92,23 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy {
saveProfileClicked(profile: Profile, selectedProfile: Profile | null): void {
if (!selectedProfile) {
this.saveProfile(profile);
this.store.setFocusOnCreateButton();
} else {
this.openSaveDialog(selectedProfile.name)
.pipe(takeUntil(this.destroy$))
.subscribe(saveProfile => {
if (saveProfile) {
this.saveProfile(profile);
this.store.setFocusOnSelectedProfile();
}
});
}
}

trackByIndex = (index: number): number => {
return index;
};

private closeFormAfterDelete(name: string, selectedProfile: Profile | null) {
if (selectedProfile?.name === name) {
this.isOpenProfileForm = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed } from '@angular/core/testing';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { of, skip, take } from 'rxjs';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { TestRunService } from '../../services/test-run.service';
Expand Down Expand Up @@ -69,6 +69,7 @@ describe('RiskAssessmentStore', () => {
store = TestBed.inject(MockStore);
riskAssessmentStore = TestBed.inject(RiskAssessmentStore);

mockFocusManagerService.focusFirstElementInContainer.calls.reset();
spyOn(store, 'dispatch').and.callFake(() => {});
});

Expand Down Expand Up @@ -170,6 +171,49 @@ describe('RiskAssessmentStore', () => {
});
});

describe('setFocusOnCreateButton', () => {
const container = document.createElement('div') as HTMLElement;
container.classList.add('risk-assessment-content-empty');
document.querySelector('body')?.appendChild(container);

it('should call focusFirstElementInContainer', fakeAsync(() => {
riskAssessmentStore.setFocusOnCreateButton();

tick(11);
expect(
mockFocusManagerService.focusFirstElementInContainer
).toHaveBeenCalledWith(container);
}));
});

describe('setFocusOnSelectedProfile', () => {
const container = document.createElement('div') as HTMLElement;
container.classList.add('profiles-drawer-content');
const inner = document.createElement('div') as HTMLElement;
inner.classList.add('selected');
container.appendChild(inner);
document.querySelector('body')?.appendChild(container);

it('should call focusFirstElementInContainer', () => {
riskAssessmentStore.setFocusOnSelectedProfile();

expect(
mockFocusManagerService.focusFirstElementInContainer
).toHaveBeenCalledWith(inner);
});
});

describe('setFocusOnProfileForm', () => {
const profileForm = window.document.querySelector('app-profile-form');
it('should call focusFirstElementInContainer', () => {
riskAssessmentStore.setFocusOnProfileForm();

expect(
mockFocusManagerService.focusFirstElementInContainer
).toHaveBeenCalledWith(profileForm);
});
});

describe('getProfilesFormat', () => {
it('should update store', done => {
mockService.fetchProfilesFormat.and.returnValue(of(PROFILE_FORM));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { tap, withLatestFrom } from 'rxjs/operators';
import { exhaustMap } from 'rxjs';
import { delay, exhaustMap } from 'rxjs';
import { TestRunService } from '../../services/test-run.service';
import { Profile, ProfileFormat } from '../../model/profile';
import { FocusManagerService } from '../../services/focus-manager.service';
Expand Down Expand Up @@ -88,6 +88,37 @@ export class RiskAssessmentStore extends ComponentStore<AppComponentState> {
}
);

setFocusOnCreateButton = this.effect(trigger$ => {
return trigger$.pipe(
delay(10),
tap(() => {
this.focusManagerService.focusFirstElementInContainer(
window.document.querySelector('.risk-assessment-content-empty')
);
})
);
});

setFocusOnSelectedProfile = this.effect(trigger$ => {
return trigger$.pipe(
tap(() => {
this.focusManagerService.focusFirstElementInContainer(
window.document.querySelector('.profiles-drawer-content .selected')
);
})
);
});

setFocusOnProfileForm = this.effect(trigger$ => {
return trigger$.pipe(
tap(() => {
this.focusManagerService.focusFirstElementInContainer(
window.document.querySelector('app-profile-form')
);
})
);
});

getProfilesFormat = this.effect(trigger$ => {
return trigger$.pipe(
exhaustMap(() => {
Expand Down
3 changes: 1 addition & 2 deletions modules/ui/src/app/services/focus-manager.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export class FocusManagerService {
const dialogOpened = window.document.querySelector('.mdc-dialog--open');
const parentElem = dialogOpened ? dialogOpened : container;
const firstInteractiveElem = this.findFirstInteractiveElem(parentElem);

if (firstInteractiveElem) {
firstInteractiveElem.focus();
}
Expand All @@ -22,7 +21,7 @@ export class FocusManagerService {
parentEl: Document | Element | null
): HTMLElement | undefined | null {
return parentEl?.querySelector(
'button:not([disabled="true"]):not([tabindex="-1"]), a:not([disabled="true"]), input:not([disabled="true"]), table'
'button:not([disabled="true"]):not([tabindex="-1"]), a:not([disabled="true"]), input:not([disabled="true"]), table, [tabindex="0"]'
);
}
}