[ACS-10518]: Search input keyboard navigation accessibility fixes (#11270)

* [ACS-10518]: add permissions panel clear button updates

* [ACS-10518]: adds content-node-selector panel clear button updates

* [ACS-10518]: adds no-pointer class

* [ACS-10518]: adds no-pointer

* [ACS-10518]: minor fixes

* [ACS-10518]: fixes padding

* [ACS-10518]: unit tests refactor

* [ACS-10518]: minor fixes
This commit is contained in:
rmnvch
2025-10-27 10:50:23 +01:00
committed by GitHub
parent 5016eff847
commit 82b9b18891
5 changed files with 81 additions and 37 deletions
@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging, SitePagingList } from '@alfresco/js-api'; import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging, SitePagingList } from '@alfresco/js-api';
@@ -30,6 +30,7 @@ import { SearchQueryBuilderService } from '../../search';
import { mockSearchRequest } from '../../mock/search-query.mock'; import { mockSearchRequest } from '../../mock/search-query.mock';
import { SitesService } from '../../common/services/sites.service'; import { SitesService } from '../../common/services/sites.service';
import { NodesApiService } from '../../common/services/nodes-api.service'; import { NodesApiService } from '../../common/services/nodes-api.service';
import { UnitTestingUtils } from '../../../../../core/src/lib/testing/unit-testing-utils';
const fakeResultSetPaging: ResultSetPaging = { const fakeResultSetPaging: ResultSetPaging = {
list: { list: {
@@ -60,15 +61,18 @@ describe('ContentNodeSelectorPanelComponent', () => {
const fakeNodeEntry = new Node({ id: 'fakeId' }); const fakeNodeEntry = new Node({ id: 'fakeId' });
const nodeEntryEvent = new NodeEntryEvent(fakeNodeEntry); const nodeEntryEvent = new NodeEntryEvent(fakeNodeEntry);
let searchQueryBuilderService: SearchQueryBuilderService; let searchQueryBuilderService: SearchQueryBuilderService;
let testingUtils: UnitTestingUtils;
const typeToSearchBox = (searchTerm = 'string-to-search') => { const typeToSearchBox = (searchTerm = 'string-to-search'): void => {
const searchInput = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-input"]')); const searchInput = testingUtils.getByCSS('[data-automation-id="content-node-selector-search-input"]');
searchInput.nativeElement.value = searchTerm; searchInput.nativeElement.value = searchTerm;
component.searchInput.setValue(searchTerm); component.searchInput.setValue(searchTerm);
fixture.detectChanges(); fixture.detectChanges();
}; };
const triggerSearchResults = (searchResults: ResultSetPaging) => { const getSearchIcon = (type: string): DebugElement => testingUtils.getByCSS(`[data-automation-id="content-node-selector-search-${type}"]`);
const triggerSearchResults = (searchResults: ResultSetPaging): void => {
const service = fixture.debugElement.injector.get(SearchQueryBuilderService); const service = fixture.debugElement.injector.get(SearchQueryBuilderService);
service.executed.next(searchResults); service.executed.next(searchResults);
}; };
@@ -92,6 +96,8 @@ describe('ContentNodeSelectorPanelComponent', () => {
searchQueryBuilderService = fixture.debugElement.injector.get(SearchQueryBuilderService); searchQueryBuilderService = fixture.debugElement.injector.get(SearchQueryBuilderService);
searchQueryBuilderService.resetToDefaults(); searchQueryBuilderService.resetToDefaults();
testingUtils = new UnitTestingUtils(fixture.debugElement);
spyOn(nodeService, 'getNode').and.returnValue( spyOn(nodeService, 'getNode').and.returnValue(
of( of(
new Node({ new Node({
@@ -196,7 +202,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
spyOn(component, 'clearSearch'); spyOn(component, 'clearSearch');
fixture.detectChanges(); fixture.detectChanges();
const clearIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-clear"]')); const clearIcon = getSearchIcon('clear');
clearIcon.nativeElement.click(); clearIcon.nativeElement.click();
fixture.detectChanges(); fixture.detectChanges();
@@ -357,8 +363,8 @@ describe('ContentNodeSelectorPanelComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
tick(debounceSearch); tick(debounceSearch);
const searchIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-icon"]')); const searchIcon = getSearchIcon('icon');
const clearIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-clear"]')); const clearIcon = getSearchIcon('clear');
expect(searchIcon).not.toBeNull(); expect(searchIcon).not.toBeNull();
expect(clearIcon).toBeNull(); expect(clearIcon).toBeNull();
@@ -371,13 +377,26 @@ describe('ContentNodeSelectorPanelComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const searchIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-icon"]')); const searchIcon = getSearchIcon('icon');
const clearIcon = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-clear"]')); const clearIcon = getSearchIcon('clear');
expect(searchIcon).toBeNull(); expect(searchIcon).toBeNull();
expect(clearIcon).not.toBeNull(); expect(clearIcon).not.toBeNull();
})); }));
it('should call clear method on the X (clear) button click', fakeAsync(() => {
fixture.detectChanges();
typeToSearchBox('123');
tick(debounceSearch);
fixture.detectChanges();
spyOn(component, 'clear');
const clearButton = getSearchIcon('clear');
clearButton.nativeElement.click();
expect(component.clear).toHaveBeenCalled();
}));
it('should clear the search field, nodes and chosenNode when clicking on the X (clear) icon', async () => { it('should clear the search field, nodes and chosenNode when clicking on the X (clear) icon', async () => {
component.chosenNode = [entry]; component.chosenNode = [entry];
@@ -553,7 +572,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
})); }));
it('should show the current folder content instead of search results if search was not performed', async () => { it('should show the current folder content instead of search results if search was not performed', async () => {
const documentList = fixture.debugElement.query(By.directive(DocumentListComponent)); const documentList = testingUtils.getByDirective(DocumentListComponent);
expect(documentList).not.toBeNull(); expect(documentList).not.toBeNull();
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku'); expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
}); });
@@ -565,7 +584,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const documentList = fixture.debugElement.query(By.directive(DocumentListComponent)); const documentList = testingUtils.getByDirective(DocumentListComponent);
expect(documentList).not.toBeNull(); expect(documentList).not.toBeNull();
expect( expect(
documentList.componentInstance.rowFilter({ documentList.componentInstance.rowFilter({
@@ -593,7 +612,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const documentList = fixture.debugElement.query(By.directive(DocumentListComponent)); const documentList = testingUtils.getByDirective(DocumentListComponent);
expect(documentList).not.toBeNull(); expect(documentList).not.toBeNull();
expect(documentList.componentInstance.rowFilter).toBeTruthy(); expect(documentList.componentInstance.rowFilter).toBeTruthy();
@@ -607,7 +626,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
const documentList = fixture.debugElement.query(By.directive(DocumentListComponent)); const documentList = testingUtils.getByDirective(DocumentListComponent);
expect(documentList).not.toBeNull(); expect(documentList).not.toBeNull();
expect(documentList.componentInstance.imageResolver).toBe(resolver); expect(documentList.componentInstance.imageResolver).toBe(resolver);
}); });
@@ -619,7 +638,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
triggerSearchResults(fakeResultSetPaging); triggerSearchResults(fakeResultSetPaging);
fixture.detectChanges(); fixture.detectChanges();
const documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]')); const documentList = testingUtils.getByCSS('[data-automation-id="content-node-selector-document-list"]');
expect(documentList).not.toBeNull(); expect(documentList).not.toBeNull();
expect(component.hasValidQuery).toEqual(true); expect(component.hasValidQuery).toEqual(true);
expect(documentList.componentInstance.currentFolderId).toBeNull(); expect(documentList.componentInstance.currentFolderId).toBeNull();
@@ -664,12 +683,12 @@ describe('ContentNodeSelectorPanelComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const clearButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-clear"]')); const clearButton = getSearchIcon('clear');
expect(clearButton).not.toBeNull(); expect(clearButton).not.toBeNull();
clearButton.triggerEventHandler('click', {}); clearButton.triggerEventHandler('click', {});
fixture.detectChanges(); fixture.detectChanges();
const documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]')); const documentList = testingUtils.getByCSS('[data-automation-id="content-node-selector-document-list"]');
expect(documentList).not.toBeNull(); expect(documentList).not.toBeNull();
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku'); expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
done(); done();
@@ -696,13 +715,13 @@ describe('ContentNodeSelectorPanelComponent', () => {
component.siteChanged({ entry: { guid: 'Kame-Sennin Muten Roshi' } } as SiteEntry); component.siteChanged({ entry: { guid: 'Kame-Sennin Muten Roshi' } } as SiteEntry);
fixture.detectChanges(); fixture.detectChanges();
let documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]')); let documentList = testingUtils.getByCSS('[data-automation-id="content-node-selector-document-list"]');
expect(documentList.componentInstance.currentFolderId).toBe('Kame-Sennin Muten Roshi'); expect(documentList.componentInstance.currentFolderId).toBe('Kame-Sennin Muten Roshi');
component.siteChanged({ entry: { guid: undefined } } as SiteEntry); component.siteChanged({ entry: { guid: undefined } } as SiteEntry);
fixture.detectChanges(); fixture.detectChanges();
documentList = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-document-list"]')); documentList = testingUtils.getByCSS('[data-automation-id="content-node-selector-document-list"]');
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku'); expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
done(); done();
@@ -711,7 +730,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
describe('Pagination "Load more" button', () => { describe('Pagination "Load more" button', () => {
it('should NOT be shown by default', () => { it('should NOT be shown by default', () => {
fixture.detectChanges(); fixture.detectChanges();
const pagination = fixture.debugElement.query(By.css('[data-automation-id="adf-infinite-pagination-button"]')); const pagination = testingUtils.getByCSS('[data-automation-id="adf-infinite-pagination-button"]');
expect(pagination).toBeNull(); expect(pagination).toBeNull();
}); });
@@ -13,18 +13,26 @@
adf-auto-focus adf-auto-focus
data-automation-id="content-node-selector-search-input"> data-automation-id="content-node-selector-search-input">
<mat-icon *ngIf="searchTerm.length > 0" <button
matSuffix (click)="clear()" matSuffix
class="adf-content-node-selector-content-input-icon" mat-icon-button
data-automation-id="content-node-selector-search-clear">clear *ngIf="searchTerm.length > 0"
</mat-icon> data-automation-id="content-node-selector-search-clear"
class="adf-content-node-selector-search-clear-button"
(click)="clear()"
[attr.aria-label]="'COMMON.CLEAR' | translate"
[attr.title]="'COMMON.CLEAR' | translate"
>
<mat-icon class="adf-content-node-selector-content-input-icon">clear</mat-icon>
</button>
<mat-icon *ngIf="searchTerm.length === 0" <mat-icon
*ngIf="searchTerm.length === 0"
matSuffix matSuffix
class="adf-content-node-selector-content-input-icon" class="adf-content-node-selector-content-input-icon"
data-automation-id="content-node-selector-search-icon">search data-automation-id="content-node-selector-search-icon"
>search
</mat-icon> </mat-icon>
</mat-form-field> </mat-form-field>
<adf-sites-dropdown <adf-sites-dropdown
*ngIf="showDropdownSiteList" *ngIf="showDropdownSiteList"
@@ -75,16 +75,29 @@ h2.adf-search-results-label {
width: 100%; width: 100%;
margin-bottom: 8px; margin-bottom: 8px;
.adf-content-node-selector-content-input-icon { .adf-content-node-selector-content-input-icon:is(mat-icon) {
color: var(--adf-theme-foreground-icon-color-054); color: var(--adf-theme-foreground-icon-color-054);
cursor: pointer;
padding: 0 0 8px; padding: 0 0 8px;
width: 1em; width: 1em;
height: 1em; height: 1em;
font-size: 20px; font-size: 20px;
}
&:hover { .adf-content-node-selector-search-clear-button {
color: var(--adf-theme-foreground-base-color); padding: 0;
width: 20px;
height: 28px;
&:focus {
outline-offset: -1.5px;
}
.adf-content-node-selector-content-input-icon:is(mat-icon) {
cursor: pointer;
&:hover {
color: var(--adf-theme-foreground-base-color);
}
} }
} }
@@ -19,7 +19,8 @@
class="adf-permission-search-input-clear-button" class="adf-permission-search-input-clear-button"
(click)="clearSearch()" (click)="clearSearch()"
[attr.aria-label]="'COMMON.CLEAR' | translate" [attr.aria-label]="'COMMON.CLEAR' | translate"
[attr.title]="'COMMON.CLEAR' | translate"> [attr.title]="'COMMON.CLEAR' | translate"
>
<mat-icon class="adf-permission-search-icon">clear</mat-icon> <mat-icon class="adf-permission-search-icon">clear</mat-icon>
</button> </button>
@@ -80,11 +80,6 @@ $search-result-height: calc(100% - 60px);
width: 1em; width: 1em;
height: 1em; height: 1em;
font-size: 20px; font-size: 20px;
cursor: pointer;
&:hover {
color: var(--adf-theme-foreground-base-color);
}
} }
.adf-permission-search-input-clear-button { .adf-permission-search-input-clear-button {
@@ -92,6 +87,14 @@ $search-result-height: calc(100% - 60px);
width: 20px; width: 20px;
height: 32px; height: 32px;
margin-right: 1.5px; margin-right: 1.5px;
.adf-permission-search-icon:is(mat-icon) {
cursor: pointer;
&:hover {
color: var(--adf-theme-foreground-base-color);
}
}
} }
} }
} }