Skip to content
Open
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 @@ -22,15 +22,17 @@
{{'dso-selector.no-results' | translate: { type: typesString } }}
</button>
}
@for (listEntry of (listEntries$ | async); track listEntry) {
@for (listEntry of (listEntries$ | async); track listEntry; let i = $index) {
<button
tabindex="0"
class="list-group-item list-group-item-action border-0 list-entry"
[ngClass]="{'bg-primary': listEntry['id'] === currentDSOId}"
[ngClass]="{ 'active': isCurrentDso(listEntry) }"
[attr.aria-current]="isCurrentDso(listEntry) ? 'true' : null"
title="{{ getName(listEntry) }}"
dsHoverClass="ds-hover"
(click)="onClick(listEntry)" #listEntryElement>
(click)="onClick(listEntry)" (keydown)="onListEntryKeydown($event, i)" #listEntryElement>
<ds-listable-object-component-loader [object]="listEntry" [viewMode]="viewMode"
[linkType]=linkTypes.None [context]="getContext(listEntry['id'])"></ds-listable-object-component-loader>
[linkType]=linkTypes.None [context]="getContext(getDsoId(listEntry))"></ds-listable-object-component-loader>
</button>
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
DebugElement,
ElementRef,
NO_ERRORS_SCHEMA,
QueryList,
} from '@angular/core';
import {
ComponentFixture,
Expand Down Expand Up @@ -110,6 +112,66 @@ describe('DSOSelectorComponent', () => {
expect(component).toBeTruthy();
});

describe('getDsoId', () => {
it('should return the id of the indexable object', () => {
expect(component.getDsoId(searchResult)).toEqual(searchResult.indexableObject.id);
});
});

describe('isCurrentDso', () => {
it('should return true when the list entry represents the current DSO', () => {
component.currentDSOId = searchResult.indexableObject.id;

expect(component.isCurrentDso(searchResult)).toBeTrue();
});

it('should return false when the list entry does not represent the current DSO', () => {
component.currentDSOId = 'another-id';

expect(component.isCurrentDso(searchResult)).toBeFalse();
});
});

describe('keyboard navigation', () => {
let firstElement: HTMLButtonElement;
let secondElement: HTMLButtonElement;

beforeEach(() => {
firstElement = document.createElement('button');
secondElement = document.createElement('button');

const listElements = new QueryList<ElementRef>();
listElements.reset([
new ElementRef(firstElement),
new ElementRef(secondElement),
]);

component.listElements = listElements;
});

it('should move focus to the next list entry when pressing ArrowDown', () => {
const event = new KeyboardEvent('keydown', { key: 'ArrowDown' });
spyOn(event, 'preventDefault');
spyOn(secondElement, 'focus');

component.onListEntryKeydown(event, 0);

expect(event.preventDefault).toHaveBeenCalled();
expect(secondElement.focus).toHaveBeenCalled();
});

it('should move focus to the previous list entry when pressing ArrowUp', () => {
const event = new KeyboardEvent('keydown', { key: 'ArrowUp' });
spyOn(event, 'preventDefault');
spyOn(firstElement, 'focus');

component.onListEntryKeydown(event, 1);

expect(event.preventDefault).toHaveBeenCalled();
expect(firstElement.focus).toHaveBeenCalled();
});
});

describe('populating listEntries', () => {
it('should not be empty', (done) => {
component.listEntries$.subscribe((listEntries) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
AsyncPipe,
NgClass,
NgClass

Check failure on line 3 in src/app/shared/dso-selector/dso-selector/dso-selector.component.ts

View workflow job for this annotation

GitHub Actions / tests (20.x)

Missing trailing comma

Check failure on line 3 in src/app/shared/dso-selector/dso-selector/dso-selector.component.ts

View workflow job for this annotation

GitHub Actions / tests (22.x)

Missing trailing comma
} from '@angular/common';
import {
Component,
Expand Down Expand Up @@ -335,6 +335,56 @@
}
}

/**
* Get the DSO id from the given listable object
* @param listableObject The {@link ListableObject} to evaluate
*/
getDsoId(listableObject: ListableObject): string {
const searchResult = listableObject as SearchResult<DSpaceObject>;
return hasValue(searchResult.indexableObject) ? searchResult.indexableObject.id : null;
}

/**
* Check if the given list entry represents the currently selected DSO
* @param listableObject The {@link ListableObject} to evaluate
*/
isCurrentDso(listableObject: ListableObject): boolean {
return this.getDsoId(listableObject) === this.currentDSOId;
}

/**
* Handles keyboard navigation between list entries
* @param event The keyboard event
* @param index The index of the current list entry
*/
onListEntryKeydown(event: KeyboardEvent, index: number): void {
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
this.focusListEntry(index + 1);
break;
case 'ArrowUp':
event.preventDefault();
this.focusListEntry(index - 1);
break;
}
}

/**
* Set focus on the list entry at the given index
* @param index The index of the list entry to focus
*/
private focusListEntry(index: number): void {
if (this.listElements.length > 0) {
const boundedIndex = Math.max(0, Math.min(index, this.listElements.length - 1));
const element = this.listElements.get(boundedIndex);

if (hasValue(element)) {
element.nativeElement.focus();
}
}
}

/**
* Unsubscribe from all subscriptions
*/
Expand Down
Loading