diff --git a/CHANGELOG.md b/CHANGELOG.md index 709a8e447..9f0c588df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# v10.0.0-rc.2 (2020-10-28) +* **grid** differentiate between user sort and programmatic sort events +* **grid** a11y: announce only user `sort` events +* **grid** a11y: expose translateable aria-label for checkboxes +* **grid** added `matTooltip` for checkboxes +* **suggest** a11y: fixes to title, specify `role` attributes for list +* **suggest** a11y: announce current `option` on open +* **suggest** announce "no results" msg if empty + # v10.0.0-rc.1 (2020-10-15) * **grid** fix multiple row selection with shift diff --git a/package-lock.json b/package-lock.json index 13546adaa..69e618ae5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "angular-components", - "version": "10.0.0-rc.1", + "version": "10.0.0-rc.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 33a0a1756..eb0915855 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-components", - "version": "10.0.0-rc.1", + "version": "10.0.0-rc.2", "author": { "name": "UiPath Inc", "url": "https://uipath.com" diff --git a/projects/angular/components/ui-grid/src/components/ui-grid-search/ui-grid-search.component.html b/projects/angular/components/ui-grid/src/components/ui-grid-search/ui-grid-search.component.html index e76c30e43..dc6c4afa3 100644 --- a/projects/angular/components/ui-grid/src/components/ui-grid-search/ui-grid-search.component.html +++ b/projects/angular/components/ui-grid/src/components/ui-grid-search/ui-grid-search.component.html @@ -31,7 +31,6 @@ { expect(manager.columns).toBe(columns); }); - describe('Event: sort change', () => { + it('should update sort event if columns change', () => { + const cols = generateColumnList('random'); + cols.forEach(column => { + column.sortable = true; + column.sort = 'asc'; + }); + + manager.columns = cols; + expect(Object.keys(manager.sort$.getValue()).length).toBe(4); + expect(manager.sort$.getValue().userEvent).toBeFalse(); + }); + + describe('Event: user sort change', () => { it(`should cycle column sort from '' to 'asc'`, () => { const column = faker.helpers.randomize(columns); column.sort = ''; @@ -110,6 +122,7 @@ describe('Component: UiGrid', () => { ).subscribe(sort => { expect(sort.field).toEqual(column.property!); expect(sort.direction).toEqual(column.sort); + expect(sort.userEvent).toBeTrue(); }); manager.changeSort(column); @@ -129,6 +142,7 @@ describe('Component: UiGrid', () => { ).subscribe(sort => { expect(sort.field).toEqual(first.property!); expect(sort.direction).toEqual(first.sort); + expect(sort.userEvent).toBeTrue(); }); manager.sort$ @@ -139,6 +153,7 @@ describe('Component: UiGrid', () => { ).subscribe(sort => { expect(sort.field).toEqual(second.property!); expect(sort.direction).toEqual(second.sort); + expect(sort.userEvent).toBeTrue(); }); manager.changeSort(first); diff --git a/projects/angular/components/ui-grid/src/managers/sort-manager.ts b/projects/angular/components/ui-grid/src/managers/sort-manager.ts index 8650c7e5f..c6cf147ff 100644 --- a/projects/angular/components/ui-grid/src/managers/sort-manager.ts +++ b/projects/angular/components/ui-grid/src/managers/sort-manager.ts @@ -48,7 +48,9 @@ export class SortManager { this._emitSort(sortedColumn); } - + /** + * Sort based on user action on column header + */ public changeSort(column: UiGridColumnDirective) { if (!column.sortable) { return; } @@ -58,18 +60,19 @@ export class SortManager { column.sort = SORT_CYCLE_MAP[column.sort]; - this._emitSort(column); + this._emitSort(column, true); } public destroy() { this.sort$.complete(); } - private _emitSort(column: UiGridColumnDirective) { + private _emitSort(column: UiGridColumnDirective, userEvent = false) { const updatedSort = { direction: column.sort, field: column.property, title: column.title, + userEvent, } as ISortModel; if (isEqual(this.sort$.getValue(), updatedSort)) { return; } diff --git a/projects/angular/components/ui-grid/src/models/sortModel.ts b/projects/angular/components/ui-grid/src/models/sortModel.ts index 917f52986..c109e0e1d 100644 --- a/projects/angular/components/ui-grid/src/models/sortModel.ts +++ b/projects/angular/components/ui-grid/src/models/sortModel.ts @@ -21,4 +21,9 @@ export interface ISortModel { * */ title: string; + /** + * Sort event as a result of direct user intent to sort by a column. + * + */ + userEvent?: boolean; } diff --git a/projects/angular/components/ui-grid/src/ui-grid.component.html b/projects/angular/components/ui-grid/src/ui-grid.component.html index 37709e83f..0a417245b 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.component.html +++ b/projects/angular/components/ui-grid/src/ui-grid.component.html @@ -126,7 +126,8 @@ [disabled]="!dataManager.length" [checked]="isEveryVisibleRowChecked" [indeterminate]="hasValueOnVisiblePage && !isEveryVisibleRowChecked" - [aria-label]="checkboxLabel()" + [matTooltip]="checkboxTooltip()" + [aria-label]="checkboxTooltip()" tabindex="0"> @@ -172,7 +173,6 @@
@@ -291,7 +291,8 @@ (keyup.space)="checkShift($event)" (change)="handleSelection(index, row)" [checked]="selectionManager.isSelected(row)" - [aria-label]="checkboxLabel(row)" + [matTooltip]="checkboxTooltip(row)" + [aria-label]="checkboxTooltip(row)" tabindex="0">
@@ -314,7 +315,8 @@ (keyup.space)="checkShift($event)" (change)="handleSelection(index, row)" [checked]="selectionManager.isSelected(row)" - [aria-label]="checkboxLabel(row)" + [matTooltip]="checkboxTooltip(row)" + [aria-label]="checkboxTooltip(row)" tabindex="0"> diff --git a/projects/angular/components/ui-grid/src/ui-grid.component.spec.ts b/projects/angular/components/ui-grid/src/ui-grid.component.spec.ts index 0f9cd0be1..e5c04c28c 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.component.spec.ts +++ b/projects/angular/components/ui-grid/src/ui-grid.component.spec.ts @@ -345,16 +345,34 @@ describe('Component: UiGrid', () => { }); describe('Feature: checkbox', () => { - it('should check all rows if the header checkbox is clicked', () => { + it('should have ariaLabel set correctly for toggle selection', () => { const checkboxHeader = fixture.debugElement.query(By.css('.ui-grid-header-cell.ui-grid-checkbox-cell')); + const matCheckbox = checkboxHeader.query(By.css('mat-checkbox')).componentInstance as MatCheckbox; + + expect(matCheckbox.checked).toEqual(false); + expect(matCheckbox.ariaLabel).toEqual('Select all'); const checkboxInput = checkboxHeader.query(By.css('input')); checkboxInput.nativeElement.dispatchEvent(EventGenerator.click); fixture.detectChanges(); + + expect(matCheckbox.checked).toEqual(true); + expect(matCheckbox.ariaLabel).toEqual('Deselect all'); + }); + + it('should check all rows if the header checkbox is clicked', () => { + const checkboxHeader = fixture.debugElement.query(By.css('.ui-grid-header-cell.ui-grid-checkbox-cell')); const matCheckbox = checkboxHeader.query(By.css('mat-checkbox')).componentInstance as MatCheckbox; + expect(matCheckbox.checked).toEqual(false); + + const checkboxInput = checkboxHeader.query(By.css('input')); + checkboxInput.nativeElement.dispatchEvent(EventGenerator.click); + + fixture.detectChanges(); + expect(matCheckbox.checked).toEqual(true); const rowCheckboxList = fixture.debugElement @@ -388,7 +406,10 @@ describe('Component: UiGrid', () => { expect(rowCheckboxList.length).toEqual(data.length); - rowCheckboxList.forEach(checkbox => expect(checkbox.checked).toEqual(true)); + rowCheckboxList.forEach((checkbox, i) => { + expect(checkbox.checked).toEqual(true); + expect(checkbox.ariaLabel).toEqual(`Deselect row ${i}`); + }); expect(grid.selectionManager.selected.length).toEqual(data.length); expect(grid.selectionManager.selected).toEqual(data); @@ -398,7 +419,10 @@ describe('Component: UiGrid', () => { fixture.detectChanges(); expect(matCheckbox.checked).toEqual(false); - rowCheckboxList.forEach(checkbox => expect(checkbox.checked).toEqual(false)); + rowCheckboxList.forEach((checkbox, i) => { + expect(checkbox.checked).toEqual(false); + expect(checkbox.ariaLabel).toEqual(`Select row ${i}`); + }); expect(grid.hasValueOnVisiblePage).toEqual(false); }); @@ -478,7 +502,7 @@ describe('Component: UiGrid', () => { rowCheckboxInputList[10].nativeElement.dispatchEvent(EventGenerator.click); - for (let i = 0; i <= 10; i ++) { + for (let i = 0; i <= 10; i++) { expect(grid.selectionManager.isSelected(data[i])).toBeTrue(); } }); diff --git a/projects/angular/components/ui-grid/src/ui-grid.component.ts b/projects/angular/components/ui-grid/src/ui-grid.component.ts index 0a81c71b5..959a9d325 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.component.ts +++ b/projects/angular/components/ui-grid/src/ui-grid.component.ts @@ -32,9 +32,9 @@ import { import { debounceTime, distinctUntilChanged, + filter, map, observeOn, - skip, switchMap, take, takeUntil, @@ -114,7 +114,7 @@ export class UiGridComponent extends ResizableGrid * */ public get isEveryVisibleRowChecked() { - return this.dataManager.length && + return !!this.dataManager.length && this.dataManager.every(row => this.selectionManager.isSelected(row!)); } @@ -499,7 +499,9 @@ export class UiGridComponent extends ResizableGrid msg => this._queuedAnnouncer.enqueue(msg), this.intl, this.dataManager.data$, - this.sortManager.sort$.pipe(skip(1)), + this.sortManager.sort$.pipe( + filter(({ userEvent }) => !!userEvent), + ), this.refresh, this.footer && this.footer.pageChange, ); @@ -638,10 +640,23 @@ export class UiGridComponent extends ResizableGrid } /** - * Determines the `checkbox` `aria-label`. + * Determines the `checkbox` `matToolTip`. * * @param [row] The row for which the label is computed. */ + public checkboxTooltip(row?: T): string { + if (!row) { + return this.intl.checkboxTooltip(this.isEveryVisibleRowChecked); + } + + return this.intl.checkboxTooltip(this.selectionManager.isSelected(row), this.dataManager.indexOf(row)); + } + + /** + * Determines the `checkbox` aria-label`. + * **DEPRECATED** + * @param [row] The row for which the label is computed. + */ public checkboxLabel(row?: T): string { if (!row) { return `${this.isEveryVisibleRowChecked ? 'select' : 'deselect'} all`; diff --git a/projects/angular/components/ui-grid/src/ui-grid.intl.ts b/projects/angular/components/ui-grid/src/ui-grid.intl.ts index a69bccbcf..6143eaaef 100644 --- a/projects/angular/components/ui-grid/src/ui-grid.intl.ts +++ b/projects/angular/components/ui-grid/src/ui-grid.intl.ts @@ -103,6 +103,18 @@ export class UiGridIntl implements OnDestroy { */ public descending = 'descending'; + /** + * Determines the `checkbox` `matToolTip`. + * + * @param [rowIndex] The rowIndex for which the label is computed. + */ + public checkboxTooltip(selected: boolean, rowIndex?: number): string { + if (rowIndex == null) { + return `${selected ? 'Deselect' : 'Select'} all`; + } + return `${selected ? 'Deselect' : 'Select'} row ${rowIndex}`; + } + /** * Generates a selection label for the given count. diff --git a/projects/angular/components/ui-suggest/src/test/index.ts b/projects/angular/components/ui-suggest/src/test/index.ts index 53016b43c..cfcc78e0b 100644 --- a/projects/angular/components/ui-suggest/src/test/index.ts +++ b/projects/angular/components/ui-suggest/src/test/index.ts @@ -1 +1,2 @@ export * from './suggestionItem'; +export * from './ui-suggest-assert'; diff --git a/projects/angular/components/ui-suggest/src/test/ui-suggest-assert.ts b/projects/angular/components/ui-suggest/src/test/ui-suggest-assert.ts new file mode 100644 index 000000000..875ab6115 --- /dev/null +++ b/projects/angular/components/ui-suggest/src/test/ui-suggest-assert.ts @@ -0,0 +1,50 @@ +import { DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; + +import { UiSuggestComponent } from '../ui-suggest.component'; + +export class UiSuggestAssert { + constructor( + private _root: DebugElement, + private _suggest: UiSuggestComponent, + ) { } + + public isOpen(): void { + this._assertOpenState('open'); + } + + public isClosed(): void { + this._assertOpenState('closed'); + } + + public isDisabled(): void { + this._assertDisableState('disabled'); + } + + public isEnabled(): void { + this._assertDisableState('enabled'); + } + + private _assertOpenState(expected: 'open' | 'closed'): void { + const expectedIsOpen = expected === 'open'; + + expect(this._suggest.isOpen).toBe(expectedIsOpen); + + const combo = this._root.query(By.css('[role=combobox]')).nativeElement; + expect(combo).toHaveAttr('aria-expanded', expectedIsOpen.toString()); + + const itemList = this._root.query(By.css('.item-list-container')); + const itemListClasses = itemList.nativeElement.classList; + expect(itemListClasses.contains('item-list-container-state-open')).toBe(expectedIsOpen); + expect(itemListClasses.contains('item-list-container-state-closed')).toBe(!expectedIsOpen); + } + + private _assertDisableState(expected: 'enabled' | 'disabled'): void { + const expectedIsDisabled = expected === 'disabled'; + + expect(this._suggest.disabled).toBe(expectedIsDisabled); + + const combo = this._root.query(By.css('[role=combobox]')).nativeElement; + expect(combo).toHaveAttr('aria-disabled', expectedIsDisabled.toString()); + } +} diff --git a/projects/angular/components/ui-suggest/src/ui-suggest.component.html b/projects/angular/components/ui-suggest/src/ui-suggest.component.html index 63024b65c..24a9d4467 100644 --- a/projects/angular/components/ui-suggest/src/ui-suggest.component.html +++ b/projects/angular/components/ui-suggest/src/ui-suggest.component.html @@ -1,16 +1,26 @@
- + +
+ class="display" + role="combobox">
+ class="display-title" + aria-hidden="true"> {{ placeholder }}: @@ -102,6 +114,7 @@ [refocus]="isOpen" selectionLocation="end" autocomplete="off" + aria-autocomplete="list" matInput> search @@ -113,7 +126,7 @@
- + @@ -186,6 +200,7 @@ !isCustomValueVisible && !(loading$ | async)" [matTooltip]="label" + [attr.role]="'option'" matTooltipPosition="right" class="text-ellipsis no-results-text"> {{label}} @@ -202,6 +217,7 @@ [matTooltip]="customValueLabelTranslator(inputControl.value)" [style.height.px]="itemSize" (click)="preventDefault($event); updateValue(inputControl.value, !multiple, true);" + [attr.role]="'option'" matTooltipPosition="right" class="text-ellipsis custom-item">
diff --git a/projects/angular/components/ui-suggest/src/ui-suggest.component.spec.ts b/projects/angular/components/ui-suggest/src/ui-suggest.component.spec.ts index 20d76fb30..2ccd68283 100644 --- a/projects/angular/components/ui-suggest/src/ui-suggest.component.spec.ts +++ b/projects/angular/components/ui-suggest/src/ui-suggest.component.spec.ts @@ -47,6 +47,7 @@ import { import { generateSuggestionItem, generateSuggetionItemList, + UiSuggestAssert, } from './test'; import { UiSuggestComponent } from './ui-suggest.component'; import { UiSuggestModule } from './ui-suggest.module'; @@ -102,12 +103,14 @@ const sharedSpecifications = ( let fixture: ComponentFixture; let component: UiSuggestFixtureDirective; let uiSuggest: UiSuggestComponent; + let assert: UiSuggestAssert; beforeEach(() => { const setup = beforeEachFn(); fixture = setup.fixture; component = setup.component; uiSuggest = setup.uiSuggest; + assert = new UiSuggestAssert(fixture.debugElement, uiSuggest); }); describe('Behavior: standard usage', () => { @@ -506,18 +509,13 @@ const sharedSpecifications = ( fixture.detectChanges(); - const itemList = fixture.debugElement.query(By.css('.item-list-container')); - const itemListClasses = itemList.nativeElement.classList; - - expect(uiSuggest.isOpen).toBeTruthy(); - expect(itemListClasses.contains('item-list-container-state-open')).toBeTruthy(); + assert.isOpen(); component[state] = true; fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeFalsy(); - expect(itemListClasses.contains('item-list-container-state-closed')).toBeTruthy(); + assert.isClosed(); }); it(`should not open if the component is ${state}`, () => { @@ -525,14 +523,14 @@ const sharedSpecifications = ( fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeFalsy(); + assert.isClosed(); fixture.detectChanges(); const display = fixture.debugElement.query(By.css('.display')); display.nativeElement.dispatchEvent(EventGenerator.click); - expect(uiSuggest.isOpen).toBeFalsy(); + assert.isClosed(); }); }); @@ -545,13 +543,13 @@ const sharedSpecifications = ( fixture.detectChanges(); - expect(uiSuggest.disabled).toBeTruthy(); + assert.isDisabled(); expect(uiSuggest.loading$.value).toBeFalsy(); component.disabled = false; fixture.detectChanges(); - expect(uiSuggest.disabled).toBeFalsy(); + assert.isEnabled(); const display = fixture.debugElement.query(By.css('.display')); display.nativeElement.dispatchEvent(EventGenerator.click); @@ -572,11 +570,11 @@ const sharedSpecifications = ( }; fixture.detectChanges(); - expect(uiSuggest.disabled).toBeTruthy(); + assert.isDisabled(); component.disabled = false; fixture.detectChanges(); - expect(uiSuggest.disabled).toBeFalsy(); + assert.isEnabled(); const display = fixture.debugElement.query(By.css('.display')); display.nativeElement.dispatchEvent(EventGenerator.click); @@ -590,18 +588,60 @@ const sharedSpecifications = ( it('should not open on first click and close on the second', () => { fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeFalsy(); + assert.isClosed(); fixture.detectChanges(); const display = fixture.debugElement.query(By.css('.display')); display.nativeElement.dispatchEvent(EventGenerator.click); - expect(uiSuggest.isOpen).toBeTruthy(); + fixture.detectChanges(); + + assert.isOpen(); display.nativeElement.dispatchEvent(EventGenerator.click); - expect(uiSuggest.isOpen).toBeFalsy(); + fixture.detectChanges(); + + assert.isClosed(); + }); + }); + + describe('Behavior: a11y on open', () => { + it(`should announce if empty`, () => { + const spy = spyOn(uiSuggest['_liveAnnouncer'], 'announce'); + const display = fixture.debugElement.query(By.css('.display')); + display.nativeElement.dispatchEvent(EventGenerator.click); + + fixture.detectChanges(); + + expect(spy).toHaveBeenCalledWith('No results'); + }); + + it(`should NOT annuonce a highlight if in loading state`, () => { + component.items = generateSuggetionItemList('random'); + fixture.detectChanges(); + + const spy = spyOn(uiSuggest['_liveAnnouncer'], 'announce'); + uiSuggest.loading$.next(true); + + const display = fixture.debugElement.query(By.css('.display')); + display.nativeElement.dispatchEvent(EventGenerator.click); + + fixture.detectChanges(); + expect(spy).toHaveBeenCalledTimes(0); + }); + + it(`should announce as highlighted first item`, () => { + component.items = generateSuggetionItemList('random'); + fixture.detectChanges(); + + const spy = spyOn(uiSuggest['_liveAnnouncer'], 'announce'); + const display = fixture.debugElement.query(By.css('.display')); + display.nativeElement.dispatchEvent(EventGenerator.click); + + fixture.detectChanges(); + expect(spy).toHaveBeenCalledWith(`${component.items[0].text} item 1 out of ${component.items.length}`); }); }); @@ -626,7 +666,7 @@ const sharedSpecifications = ( it('should open when pressing Enter', () => { fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeFalsy(); + assert.isClosed(); fixture.detectChanges(); const display = fixture.debugElement.query(By.css('.display')); @@ -634,13 +674,15 @@ const sharedSpecifications = ( EventGenerator.keyDown(Key.Enter), ); - expect(uiSuggest.isOpen).toBeTruthy(); + fixture.detectChanges(); + + assert.isOpen(); }); it('should open when pressing Space', () => { fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeFalsy(); + assert.isClosed(); fixture.detectChanges(); const display = fixture.debugElement.query(By.css('.display')); @@ -648,7 +690,9 @@ const sharedSpecifications = ( EventGenerator.keyUp(Key.Space), ); - expect(uiSuggest.isOpen).toBeTruthy(); + fixture.detectChanges(); + + assert.isOpen(); }); it('should have the active index set when opened', () => { @@ -814,7 +858,7 @@ const sharedSpecifications = ( fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeTruthy(); + assert.isOpen(); const itemContainer = fixture.debugElement.query(By.css('.item-list-container')); @@ -826,7 +870,9 @@ const sharedSpecifications = ( EventGenerator.keyDown(Key.Enter), ); - expect(uiSuggest.isOpen).toBeFalsy(); + fixture.detectChanges(); + + assert.isClosed(); }); it('should NOT close if selecting an item via navigation and multiple selection is ENABLED', () => { @@ -840,7 +886,7 @@ const sharedSpecifications = ( fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeTruthy(); + assert.isOpen(); const itemContainer = fixture.debugElement.query(By.css('.item-list-container')); @@ -852,7 +898,7 @@ const sharedSpecifications = ( EventGenerator.keyDown(Key.Enter), ); - expect(uiSuggest.isOpen).toBeTruthy(); + assert.isOpen(); }); it('should close if Tab is pressed', () => { @@ -864,7 +910,7 @@ const sharedSpecifications = ( fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeTruthy(); + assert.isOpen(); const itemContainer = fixture.debugElement.query(By.css('.item-list-container')); @@ -872,7 +918,9 @@ const sharedSpecifications = ( EventGenerator.keyDown(Key.Tab), ); - expect(uiSuggest.isOpen).toBeFalsy(); + fixture.detectChanges(); + + assert.isClosed(); }); it('should close if Shift + Tab is pressed', () => { @@ -884,7 +932,7 @@ const sharedSpecifications = ( fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeTruthy(); + assert.isOpen(); const itemContainer = fixture.debugElement.query(By.css('.item-list-container')); @@ -892,7 +940,9 @@ const sharedSpecifications = ( EventGenerator.keyDown(Key.Tab, Key.Shift), ); - expect(uiSuggest.isOpen).toBeFalsy(); + fixture.detectChanges(); + + assert.isClosed(); }); it('should close if Esc is pressed', () => { @@ -904,7 +954,7 @@ const sharedSpecifications = ( fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeTruthy(); + assert.isOpen(); const itemContainer = fixture.debugElement.query(By.css('.item-list-container')); @@ -912,7 +962,9 @@ const sharedSpecifications = ( EventGenerator.keyUp(Key.Escape), ); - expect(uiSuggest.isOpen).toBeFalsy(); + fixture.detectChanges(); + + assert.isClosed(); }); it('should clear selection if Esc is pressed', () => { @@ -1892,6 +1944,7 @@ describe('Component: UiSuggest', () => { let fixture: ComponentFixture; let component: UiSuggestFormControlFixtureComponent; let uiSuggest: UiSuggestComponent; + let assert: UiSuggestAssert; const beforeEachFn = () => { TestBed.configureTestingModule({ @@ -1925,6 +1978,7 @@ describe('Component: UiSuggest', () => { fixture = setup.fixture; component = setup.component; uiSuggest = setup.uiSuggest; + assert = new UiSuggestAssert(fixture.debugElement, uiSuggest); component.items = generateSuggetionItemList('random'); }); @@ -1948,7 +2002,7 @@ describe('Component: UiSuggest', () => { it('should open the list when clicking the container', () => { fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeFalsy(); + assert.isClosed(); const formFieldUnderline = fixture.debugElement.query(By.css('.test-form-field .mat-form-field-label')); @@ -1956,15 +2010,19 @@ describe('Component: UiSuggest', () => { fixture.detectChanges(); - expect(uiSuggest.isOpen).toBeTruthy(); + assert.isOpen(); }); it('should be marked as disabled when updating state via form', () => { fixture.detectChanges(); + assert.isEnabled(); + component.formControl!.disable(); - expect(uiSuggest.disabled).toBeTruthy(); + fixture.detectChanges(); + + assert.isDisabled(); }); it('should remove NULL and Undefined entries when updating value via form', () => { @@ -2012,15 +2070,15 @@ describe('Component: UiSuggest', () => { uiSuggest.required = true; fixture.detectChanges(); - const suggest = fixture.debugElement.query(By.css('ui-suggest')).nativeElement as HTMLElement; - expect(suggest.getAttribute('aria-required')).toEqual('true'); + const combobox = fixture.debugElement.query(By.css('ui-suggest [role=combobox]')); + expect(combobox.attributes['aria-required']).toEqual('true'); }); it('should have aria attribute set to false if it is NOT required', () => { fixture.detectChanges(); - const suggest = fixture.debugElement.query(By.css('ui-suggest')).nativeElement as HTMLElement; - expect(suggest.getAttribute('aria-required')).toEqual('false'); + const combobox = fixture.debugElement.query(By.css('ui-suggest [role=combobox]')); + expect(combobox.attributes['aria-required']).toEqual('false'); }); }); }); diff --git a/projects/angular/components/ui-suggest/src/ui-suggest.component.ts b/projects/angular/components/ui-suggest/src/ui-suggest.component.ts index c4ec287c3..25d293007 100644 --- a/projects/angular/components/ui-suggest/src/ui-suggest.component.ts +++ b/projects/angular/components/ui-suggest/src/ui-suggest.component.ts @@ -747,6 +747,9 @@ export class UiSuggestComponent extends UiSuggestMatFormFieldDirective ); this._scrollTo$.next(this.activeIndex); + if (!this.loading$.value) { + this._announceNavigate(); + } } /** @@ -1026,9 +1029,13 @@ export class UiSuggestComponent extends UiSuggestMatFormFieldDirective } private _announceNavigate() { - const textToAnnounce = !this._isOnCustomValueIndex ? - this.items[this.activeIndex].text : - `${this.intl.customValueLiveLabel} ${this.customValueLabelTranslator(this.inputControl.value)}`; + if (!this.items.length && !this._isOnCustomValueIndex) { + this._liveAnnouncer.announce(this.intl.noResultsLabel); + return; + } + const textToAnnounce = !this._isOnCustomValueIndex + ? this.items[this.activeIndex].text + : `${this.intl.customValueLiveLabel} ${this.customValueLabelTranslator(this.inputControl.value)}`; this._liveAnnouncer.announce(this.intl.currentItemLabel(textToAnnounce, this.activeIndex + 1, this._items.length)); } diff --git a/projects/angular/components/ui-suggest/src/ui-suggest.mat-form-field.ts b/projects/angular/components/ui-suggest/src/ui-suggest.mat-form-field.ts index 55e6edbad..cdf2a112a 100644 --- a/projects/angular/components/ui-suggest/src/ui-suggest.mat-form-field.ts +++ b/projects/angular/components/ui-suggest/src/ui-suggest.mat-form-field.ts @@ -56,7 +56,6 @@ export abstract class UiSuggestMatFormFieldDirective implements * Configure if the input should be marked as `required` inside the form field. * */ - @HostBinding('attr.aria-required') @Input() public get required() { return this._required; @@ -186,9 +185,7 @@ export abstract class UiSuggestMatFormFieldDirective implements /** * @ignore */ - @HostBinding('attr.aria-describedby') - public describedBy = ''; - + public describedBy?: string; /** * Emits the selected item value an item is selected. @@ -282,7 +279,7 @@ export abstract class UiSuggestMatFormFieldDirective implements * @ignore */ public setDescribedByIds(ids: string[]) { - this.describedBy = ids.join(' '); + this.describedBy = ids.join(' ').trim() || undefined; } /** diff --git a/projects/angular/package.json b/projects/angular/package.json index ff8772280..5f7738b2d 100644 --- a/projects/angular/package.json +++ b/projects/angular/package.json @@ -1,6 +1,6 @@ { "name": "@uipath/angular", - "version": "10.0.0-rc.1", + "version": "10.0.0-rc.2", "license": "MIT", "author": { "name": "UiPath Inc",